JavaScript模块化
认识 ES Module
-
JavaScript没有模块化一直是它的痛点,所以才会产生我们前面学习的社区规范:CommonJS、AMD、CMD等, 所以在ES推出自己的模块化系统时,大家也是兴奋异常
-
ES Module和CommonJS的模块化有一些不同之处: 一方面它使用了import和export关键字; 另一方面它采用编译期的静态分析,并且也加入了动态引用的方式
-
ES Module模块采用export和import关键字来实现模块化:
export负责将模块内的内容导出;
import负责从其他模块导入内容;
-
了解:采用ES Module将自动采用严格模式:use strict
下载Live Serve 插件
这里我在浏览器中演示ES6的模块化开发:
如果直接在浏览器中运行代码,会报如下错误;
报错原因:由于JavaScript模块的安全需要,如果通过本地加载html文件(如:file://路径的文件),就会遇到CORS错误。如上图所示。
解决方法:需要通过一个服务器来测试,这里我们使用vscode的一个插件来辅助开发——Live Server
安装好Live Server后,右键Open with Live Server启动本地服务器。控制台出现Live reload enabled.说明启动成功了
exports关键字
我们希望把一个模块中的变量、函数、类导出给其他模块使用时。需要用到export关键字
可以有如下三种方式:
- 方式一:在语句声明的前面直接加上export关键字
- 方式二:将所有需要导出的标识符,放到export后面的 {}中
- 注意:这里的 {}里面不是ES6的对象字面量的增强写法,{}也不是表示一个对象的; 所以: export {name: name},是错误的写法;
- 方式三:导出时给标识符起一个别名
import关键字
当我们希望从外部模块导入变量、函数、类给本模块使用时。需要用到import关键字
可以有以下三种方式:
方式一:import {标识符列表} from ‘模块’;
注意:这里的{}也不是一个对象,里面只是存放导入的标识符列表内容;
方式二:导入时给标识符起别名
方式三:通过 * 将模块功能放到一个模块功能对象(a module object)上
export-import用法
基本使用
建立文件目录如下:
代码示例:
foo.js使用exports导出
-
方式一: export 声明语句
export const name = "ikun" export const age = 18 export function foo() { console.log("只因你太美") } // export class Person { // }
-
方式二: export 导出 和 声明分开
const name = "ikun" const age = "18" function foo(){ console.log("只因你太美") } export { name, age, foo }
-
方式三: 第二方式导出时起别名
const name = "ikun" const age = "18" function foo(){ console.log("只因你太美") } export { name as fName, age as fAge, foo as fFoo }
main.js使用import 导入
-
方式一: 普通的导入
import { name, age, foo } from "./foo.js" // import { fName, fAge, fFoo } from './foo.js' //export导出的时候设置了别名就使用别名导入 console.log(name) console.log(age) foo() // console.log(fName) // console.log(fAge) // fFoo()
-
方式二: 导入时起别名
import { name as fName, age as fAge, foo as fFoo } from "./foo.js" console.log(fName) console.log(fAge) fFoo()
-
方式三: 将导出的所有内容放到一个标识符中
import * as foo from "./foo.js" console.log(foo.name) console.log(foo.age) foo.foo()
index.html执行main.js的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script src="./main.js" type="module"></script>
</html>
输出结果;
export-import结合使用
为什么要这样做呢?在开发和封装一个功能库时,通常我们希望将暴露的所有接口放到一个文件中。这样方便指定统一的接口规范,也方便阅读; 这个时候,我们就可以使用export和import结合使用。如果在vue项目中封装过axios请求的小伙伴,会深有体会。
下面我们看具体的代码示例:
目录结构如下:
format.js
function timeFormat() {
return "2222-12-12"
}
function priceFormat() {
return "222.22"
}
export {
timeFormat,
priceFormat
}
math.js
function add(num1, num2) {
return num1 + num2
}
function sub(num1, num2) {
return num1 - num2
}
export {
add,
sub
}
index.js(接口集中到这个文件管理)
方式一:
import { add, sub } from './math.js'
import { timeFormat, priceFormat } from './format.js'
export {
add,
sub,
timeFormat,
priceFormat
}
方式二:export、import结合使用
export { add, sub } from './math.js'
export { timeFormat, priceFormat } from './format.js'
方式三:使用*全部导出
export * from './math.js'
export * from './format.js'
request.js
export function request() {
// 在这里封装一些库的请求,如axios等
}
main.js
import { add, sub,timeFormat,priceFormat } from "./utils/index.js"
console.log(add(10,20))
console.log(sub(20,10))
console.log(timeFormat())
console.log(priceFormat())
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./main.js" type="module"></script>
</body>
</html>
default用法
前面我们学习的导出的功能都是有名字的导出(named exports),在导出export时指定了名字; 在导入import时需要知道具体的名字。
还有一种导出叫默认导出(default export)
-
默认导出export时可以不需要指定名字;
-
在导入时不需要使用 {},并且可以自己来指定名字;
-
它也方便我们和现有的CommonJS等规范相互操作
注意:在一个模块中,只能有一个默认导出(default export);
下面我们代码演示一下具体的用法:
foo.js
const name = "ikun"
const foo = "只因你太美"
export {
name
}
export default foo
main.js
import {name} from "./foo.js"
console.log(name)
// 导入语句: 导入的默认的导出,可以自己来指定名字
import ikun from './foo.js'
console.log(ikun)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script src="./main.js" type="module"></script>
</html>
输出结果:
import函数
当我们通过import加载一个模块,是不可以在其放到逻辑代码中的
为什么会出现这个情况呢?
因为ES Module在被JS引擎解析时,就必须知道它的依赖关系;
由于这个时候js代码没有任何的运行,所以无法在进行类似于if判断中根据代码的执行情况;
甚至下面的这种写法也是错误的:因为我们必须到运行时能确定path的值
但是某些情况下,我们确确实实希望动态的来加载某一个模块,根据不同的条件,动态来选择加载模块的路径,这个时候我们就需要使用import()函数来动态加载;
下面我们看具体代码示例:
目录结构:
boo.js
const foo = "唱、跳、rap、篮球"
const name = "ikun"
export {
name,
foo
}
foo.js
const name = "ikun"
const massage = "只因你太美"
function foo(){
return this.massage
}
export {
name,
massage,
foo
}
mian.js
let flag = true
// let flag = false
// import函数返回的结果是一个Promise
if (flag) {
import("./foo.js").then(res => {
console.log("name:", res.name)
console.log(res.foo())
})
}else{
import("./boo.js").then(res => {
console.log("name:", res.name)
console.log(res.foo)
})
}
index.html
tml lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script src="./main.js" type="module"></script>
</html
输出结果: