ES6 Module
CommonJS模块就是对象,输入的时候必须查找对象属性。
let { name, age, job } = require('./person')
等同于
const person = require('./person')
const name = person.name
const age = person.age
const job = person.job
上面就是整体加载person模块,生成一个对象person,然后再从这个对象上面读取三个方法。这种加载被称为运行时加载
ES6模块不是对象,而是通过export命令显示的指定输出的代码,再通过import命令输入。
严格模式
ES6的模块自动采用严格模式,即在模块头部加上"use strict"
- 变量 在使用之前必须声明,不然会报错
"use strict"
a = 5;
alert(a); // referenceerror a is not defined
- 函数的参数不能有同名属性,不然会报错
"use strict"
function test(a, a) {
alert(a + a)
}
test(7, 7) // Duplicate parameter name not allowed in this context
- 不能使用with语句
with语句(为逐级的对象访问对象命名空间式的速写方式),也就是在指定的代码区域,直接通过节点的名称调用对象。
width通常被当做重复隐痛同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身
var obj = {
a: 1,
b: 2,
c: 3
};
// 如果要改变obj中每个属性的值,一般这样做
obj.a = 2;
obj.b = 3;
obj.c = 4;
//width语句这样做
with (obj) {
a = 3;
b = 4;
c = 5;
}
每个变量首先被认为是一个局部变量,如果局部变量与Obj对象的某个属性同名,则这个局部变量会指向obj对象属性。
with的弊端
- 导致数据泄露
function change(obj) {
with (obj) {
name1 = "grow";
}
}
var a1 = {
name1: "jack"
};
var a2 = {
name2: "jone"
}
change(a1);
console.log(a1); // { name1: "grow" }
change(a2);
console.log(a2); // { name1: "jone" }
console.log(name1); // grow
因为a2中的作用域,没有标识符name1,在非严格模式下,会自动在全局作用域创建一个全局变量,在严格模式下,会出现错误
- 性能下降
with会在运行时候修改或者创建新的作用域。
- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
export命令
模块的功能主要由两个命令构成export和import
。
export
命令用于规定模块的对外接口
import
命令用于输入其他模块提供的功能
- 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。如果想让外部能够读取到内部的某个变量,就必须使用
export
关键字输出该变量。
name.js
export var firstName = "Jone"
index.html
<script type="module">
import { firstName } from './name.js'
console.log(firstName); // Jone
</script>
如果要导出多个变量,可以对export做优化
name.js
var firstName = 'Jone';
var lastName = 'ma';
var age = 17;
export { firstName, lastName, age };
index.html
<script type="module">
import { firstName } from './name.js'
console.log(firstName); // Jone
</script>
- 使用大括号指定要输出的一组变量。
- export命令除了输出变量,还可以输出函数或者类(class)
- export输出的变量就是本来的名字,但是可以使用as关键字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
以上代码使用了as关键字,重命名了函数v1和v2的对外接口,重命名后,v2可以用不同的名字输出两次。(因为v2被重命名了两次)。
- export命令规定的是对外的接口,必须与模块内部的变量建立一一对应的关系。
export 1; // 报错
const m = 1
export m // 报错
- 没有提供对外的接口。第一种写法直接输出1,第二种写法通过变量m, 还是直接输出1。1只是一个值,不是接口,
正确的写法
export var m = 1
const m = 7
export { m }
const m = 77
export { m as n }
其他的脚本可以通过这个接口,取到值(在接口名与模块内部的变量之间,建立了一一对应的关系)
-
function
和class
的输出,也必须这样写
// 错误写法
function sayName(name) {
return `hello ${name}`
}
export sayName // 报错 Unexpected token 'export'
// 正确写法
function sayName(name) {
return `hello ${name}`
}
export { sayName }
// 正确写法2
export function sayName(name) {
return `hello ${name}`
}
- export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
export var name = "Jack"
setTimeout(() => name = 'Jone', 500);
// 也就是在调用的文件里面 1秒之后再次调用
import { name } from "./name.js";
console.log(name)
setTimeout(() => {
console.log(name)
}, 1000)
与CommonJS规范完全不同,CommonJS模块输出的是值的缓存,不存在动态更新。
import命令
使用export
命令定义了模块的对外接口以后,其他JS文件就可以通过import
命令加载这个模块。
name.js
const firstName = "Jone"
const lastName = "mi"
export { firstName, lastName }
index.html
<div id="test"></div>
import { firstName, lastName } from "./name.js";
const nameDom = document.getElementById("test")
function setName(element) {
element.textContent = `${firstName} - ${lastName}`
}
setName(test)
结果:Jone mi 显示在dom
-
import
命令接收一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块name.js
对外接口的名称相同。
如果将name.js中的lastName重命名为last,但是在index.html中依旧引入lastName变量 则会报错。
简略代码解释:
name.js:
export { firstName, lastName as last }
index.html:
(错误)
import { firstName, lastName } from './name.js'
console.log(lastName)
//Uncaught SyntaxError: The requested module './name.js' does not provide an export named 'lastName'
(正确)
import { firstName, last } from './name.js'
或者在import导入的时候使用as重命名
import { firstName, lastName as last } from './name.js'
-
import
命令输入的变量都是只读的,因为他的本质是输入接口,也就是说,不允许在加载模块的脚本里面,改写接口。
import { a } from './xxx.js'
a = {}; // Syntax Error : 'a' is read-only;
- 脚本加载了变量a,对其重新赋值就会报错,因为a是一个只读的接口。但是如果a是一个对象,改写a的属性是允许的。
name.js
var firstName = {
a: "Jone",
b: "Jack"
}
export { firstName }
index.html
import { firstName } from './name.js'
firstName.a = "haha"
console.log(JSON.stringify(firstName)) // {"a":"haha","b":"Jack"}
- 其他模块可以读到改写后的值,不过,这种写法很难查错,建议凡是输入的变量,都当做完全只读,不要轻易改变它的属性。
-
import
后面的from
指定模块文件的位置,可以使相对路径,也可以是绝对路径,.js
后缀可以省略,如果只是模块名,不带有路径,那么必须有配置文件 -
import
命令具有提升效果,会提升到整个 模块 的头部。(import
命令是编译阶段执行的,在代码运行之前)。 - 由于import是静态执行的,所以不能使用表达式和变量,这些只用在运行时才能得到结果的语法结构。
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}
用到了表达式、变量和if语句 在静态分析阶段,这些语法都是没法得到值的
-
import
语句会执行所加载的模块。
import 'loadsh'
仅仅执行loadsh模块,不输入任何值。
import 'loadsh'
import 'loadsh'
// 如果重复执行同一句import语句,那么只会执行一次,而不会执行多次。
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
// import语句是 Singleton 模式。
- 通过 Babel 转码,CommonJS 模块的require命令和 ES6 模块的import命令,可以写在同一个模块里面,但是最好不要这样做。因为import在静态解析阶段执行,所以它是一个模块之中最早执行的。下面的代码可能不会得到预期结果。
require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';
模块的整体加载
使用 * 星号指定一个对象,所有输出的值都加载在这个对象上面。
circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
index.html
import * as circle from "./circle.js";
console.log(circle.area(7)) // 153.93804002589985
console.log(circle.circumference(7)) // 43.982297150257104
模块整体所在的那个对象,应该是可以静态分析的。所以不允许运行时改变
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {};
export default 命令
- 使用
export default
输出模块的时候,在被导入的模块可以使用任意名称指向export default
输出的模块的方法,这时候不需要知道原模块输出的函数名。(import
命令可以为该匿名函数指定任意名字)
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
- 这个时候
import
命令后面,不使用大括号。 -
export default
命令用在非匿名函数前
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
foo
函数的函数名是foo
,在模块外部是无效的。在使用import
命令加载的时候,视同匿名函数加载。
export 和 export default 导出方法的对比
对比1:
export function sayName() {
}
import { sayName} from './name.js'
对比2
export default function sayName() {
}
import sayName from './name.js'
-
export default
命令用于指定模块的默认输出,也就是一个模块只有一个默认输出,也就是export default
命令只能使用一次 -
export default
就是输出一个叫做default
的变量或者方法,然后允许为它取任意名字
// modules.js
function add(x, y) {
return x * y;
}
export {add as default};
// 等同于
// export default add;
// app.js
import { default as foo } from 'modules';
// 等同于
// import foo from 'modules';
- 因为
export default
就是输出一个变量名为default
的变量,所以它后面不能再跟变量了
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
-
export default
本质就是将值赋给default
变量,可以直接将一个值写在export default
后。
export default "jone" // 正确 赋值给default变量
export "Jone" // 错误 不能直接输出值
- 如果想在一条import语句中,同时输入默认方法和其他接口,可以写成下面这样。
import _, { each, forEach } from 'lodash';
被导入模块的输出方法
export default function (obj) {
// ···
}
export function each(obj, iterator, context) {
// ···
}
export { each as forEach };