ES6系列目录
- 1 let 和 const命令
- 2 变量的解构赋值
- 3 字符串的拓展
- 4 正则的拓展
- 5 数值的拓展
- 6 函数的拓展
- 7 数组的拓展
- 8 对象的拓展
- 9 Symbol
- 10 Set和Map数据结构
- 11 Proxy
- 12 Promise对象
- 13 Iterator和 for...of循环
- 14 Generator函数和应用
- 15 Class语法和继承
- 16 Module语法和加载实现
所有整理的文章都收录到我《Cute-JavaScript》系列文章中,访问地址:http://js.pingan8787.com
14 Generator函数和应用
14.1 基本概念
Generator
函数是一种异步编程解决方案。
原理:
执行 Genenrator
函数会返回一个遍历器对象,依次遍历 Generator
函数内部的每一个状态。
Generator
函数是一个普通函数,有以下两个特征:
-
function
关键字与函数名之间有个星号; - 函数体内使用
yield
表达式,定义不同状态;
通过调用 next
方法,将指针移向下一个状态,直到遇到下一个 yield
表达式(或 return
语句)为止。简单理解, Generator
函数分段执行, yield
表达式是暂停执行的标记,而 next
恢复执行。
-
function * f (){
-
yield 'hi';
-
yield 'leo';
-
return 'ending';
-
}
-
let a = f();
-
a.next(); // {value: 'hi', done : false}
-
a.next(); // {value: 'leo', done : false}
-
a.next(); // {value: 'ending', done : true}
-
a.next(); // {value: undefined, done : false}
14.2 yield表达式
yield
表达式是暂停标志,遍历器对象的 next
方法的运行逻辑如下:
- 遇到
yield
就暂停执行,将这个 yield
后的表达式的值,作为返回对象的 value
属性值。 - 下次调用
next
往下执行,直到遇到下一个 yield
。 - 直到函数结束或者
return
为止,并返回 return
语句后面表达式的值,作为返回对象的 value
属性值。 - 如果该函数没有
return
语句,则返回对象的 value
为 undefined
。
注意:
-
yield
只能用在 Generator
函数里使用,其他地方使用会报错。
-
// 错误1
-
(function(){
-
yiled 1; // SyntaxError: Unexpected number
-
})()
-
// 错误2 forEach参数是个普通函数
-
let a = [1, [[2, 3], 4], [5, 6]];
-
let f = function * (i){
-
i.forEach(function(m){
-
if(typeof m !== 'number'){
-
yield * f (m);
-
}else{
-
yield m;
-
}
-
})
-
}
-
for (let k of f(a)){
-
console.log(k)
-
}
-
yield
表达式如果用于另一个表达式之中,必须放在圆括号内。
-
function * a (){
-
console.log('a' + yield); // SyntaxErro
-
console.log('a' + yield 123); // SyntaxErro
-
console.log('a' + (yield)); // ok
-
console.log('a' + (yield 123)); // ok
-
}
-
yield
表达式用做函数参数或放在表达式右边,可以不加括号。
-
function * a (){
-
f(yield 'a', yield 'b'); // ok
-
lei i = yield; // ok
-
}
14.3 next方法
yield
本身没有返回值,或者是总返回 undefined
, next
方法可带一个参数,作为上一个 yield
表达式的返回值。
-
function * f (){
-
for (let k = 0; true; k++){
-
let a = yield k;
-
if(a){k = -1};
-
}
-
}
-
let g =f();
-
g.next(); // {value: 0, done: false}
-
g.next(); // {value: 1, done: false}
-
g.next(true); // {value: 0, done: false}
这一特点,可以让 Generator
函数开始执行之后,可以从外部向内部注入不同值,从而调整函数行为。
-
function * f(x){
-
let y = 2 * (yield (x+1));
-
let z = yield (y/3);
-
return (x + y + z);
-
}
-
let a = f(5);
-
a.next(); // {value : 6 ,done : false}
-
a.next(); // {value : NaN ,done : false}
-
a.next(); // {value : NaN ,done : true}
-
// NaN因为yeild返回的是对象 和数字计算会NaN
-
let b = f(5);
-
b.next(); // {value : 6 ,done : false}
-
b.next(12); // {value : 8 ,done : false}
-
b.next(13); // {value : 42 ,done : false}
-
// x 5 y 24 z 13
14.4 for...of循环
for...of
循环会自动遍历,不用调用 next
方法,需要注意的是, for...of
遇到 next
返回值的 done
属性为 true
就会终止, return
返回的不包括在 for...of
循环中。
-
function * f(){
-
yield 1;
-
yield 2;
-
yield 3;
-
yield 4;
-
return 5;
-
}
-
for (let k of f()){
-
console.log(k);
-
}
-
// 1 2 3 4 没有 5
14.5 Generator.prototype.throw()
throw
方法用来向函数外抛出错误,并且在Generator函数体内捕获。
-
let f = function * (){
-
try { yield }
-
catch (e) { console.log('内部捕获', e) }
-
}
-
let a = f();
-
a.next();
-
try{
-
a.throw('a');
-
a.throw('b');
-
}catch(e){
-
console.log('外部捕获',e);
-
}
-
// 内部捕获 a
-
// 外部捕获 b
14.6 Generator.prototype.return()
return
方法用来返回给定的值,并结束遍历Generator函数,如果 return
方法没有参数,则返回值的 value
属性为 undefined
。
-
function * f(){
-
yield 1;
-
yield 2;
-
yield 3;
-
}
-
let g = f();
-
g.next(); // {value : 1, done : false}
-
g.return('leo'); // {value : 'leo', done " true}
-
g.next(); // {value : undefined, done : true}
14.7 next()/throw()/return()共同点
相同点就是都是用来恢复Generator函数的执行,并且使用不同语句替换 yield
表达式。
-
next()
将 yield
表达式替换成一个值。
-
let f = function * (x,y){
-
let r = yield x + y;
-
return r;
-
}
-
let g = f(1, 2);
-
g.next(); // {value : 3, done : false}
-
g.next(1); // {value : 1, done : true}
-
// 相当于把 let r = yield x + y;
-
// 替换成 let r = 1;
-
throw()
将 yield
表达式替换成一个 throw
语句。
-
g.throw(new Error('报错')); // Uncaught Error:报错
-
// 相当于将 let r = yield x + y
-
// 替换成 let r = throw(new Error('报错'));
-
next()
将 yield
表达式替换成一个 return
语句。
-
g.return(2); // {value: 2, done: true}
-
// 相当于将 let r = yield x + y
-
// 替换成 let r = return 2;
14.8 yield* 表达式
用于在一个Generator中执行另一个Generator函数,如果没有使用 yield*
会没有效果。
-
function * a(){
-
yield 1;
-
yield 2;
-
}
-
function * b(){
-
yield 3;
-
yield * a();
-
yield 4;
-
}
-
// 等同于
-
function * b(){
-
yield 3;
-
yield 1;
-
yield 2;
-
yield 4;
-
}
-
for(let k of b()){console.log(k)}
-
// 3
-
// 1
-
// 2
-
// 4
14.9 应用场景
- 控制流管理
解决回调地狱: -
// 使用前
-
f1(function(v1){
-
f2(function(v2){
-
f3(function(v3){
-
// ... more and more
-
})
-
})
-
})
-
// 使用Promise
-
Promise.resolve(f1)
-
.then(f2)
-
.then(f3)
-
.then(function(v4){
-
// ...
-
},function (err){
-
// ...
-
}).done();
-
// 使用Generator
-
function * f (v1){
-
try{
-
let v2 = yield f1(v1);
-
let v3 = yield f1(v2);
-
let v4 = yield f1(v3);
-
// ...
-
}catch(err){
-
// console.log(err)
-
}
-
}
-
function g (task){
-
let obj = task.next(task.value);
-
// 如果Generator函数未结束,就继续调用
-
if(!obj.done){
-
task.value = obj.value;
-
g(task);
-
}
-
}
-
g( f(initValue) );
- 异步编程的使用 在真实的异步任务封装的情况:
-
let fetch = require('node-fetch');
-
function * f(){
-
let url = 'http://www.baidu.com';
-
let res = yield fetch(url);
-
console.log(res.bio);
-
}
-
// 执行该函数
-
let g = f();
-
let result = g.next();
-
// 由于fetch返回的是Promise对象,所以用then
-
result.value.then(function(data){
-
return data.json();
-
}).then(function(data){
-
g.next(data);
-
})
15 Class语法和继承
15.1 介绍
ES6中的 class
可以看作只是一个语法糖,绝大部分功能都可以用ES5实现,并且,类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。
-
// ES5
-
function P (x,y){
-
this.x = x;
-
this.y = y;
-
}
-
P.prototype.toString = function () {
-
return '(' + this.x + ', ' + this.y + ')';
-
};
-
var a = new P(1, 2);
-
// ES6
-
class P {
-
constructor(x, y){
-
this.x = x;
-
this.y = y;
-
}
-
toString(){
-
return '(' + this.x + ', ' + this.y + ')';
-
}
-
}
-
let a = new P(1, 2);
值得注意:
ES6的类的所有方法都是定义在 prototype
属性上,调用类的实例的方法,其实就是调用原型上的方法。
-
class P {
-
constructor(){ ... }
-
toString(){ ... }
-
toNumber(){ ... }
-
}
-
// 等同于
-
P.prototyoe = {
-
constructor(){ ... },
-
toString(){ ... },
-
toNumber(){ ... }
-
}
-
let a = new P();
-
a.constructor === P.prototype.constructor; // true
类的属性名可以使用表达式:
-
let name = 'leo';
-
class P {
-
constructor (){ ... }
-
[name](){ ... }
-
}
Class不存在变量提升: ES6中的类不存在变量提升,与ES5完全不同:
-
new P (); // ReferenceError
-
class P{...};
Class的name属性:
name
属性总是返回紧跟在 class
后的类名。
-
class P {}
-
P.name; // 'P'
15.2 constructor()方法
constructor()
是类的默认方法,通过 new
实例化时自动调用执行,一个类必须有 constructor()
方法,否则一个空的 constructor()
会默认添加。
constructor()
方法默认返回实例对象(即 this
)。
-
class P { ... }
-
// 等同于
-
class P {
-
constructor(){ ... }
-
}
15.3 类的实例对象
与ES5一样,ES6的类必须使用 new
命令实例化,否则报错。
-
class P { ... }
-
let a = P (1,2); // 报错
-
let b = new P(1, 2); // 正确
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 this
对象上),否则都是定义在原型上(即定义在 class
上)。
-
class P {
-
constructor(x, y){
-
this.x = x;
-
this.y = y;
-
}
-
toString(){
-
return '(' + this.x + ', ' + this.y + ')';
-
}
-
}
-
var point = new Point(2, 3);
-
point.toString() // (2, 3)
-
point.hasOwnProperty('x') // true
-
point.hasOwnProperty('y') // true
-
point.hasOwnProperty('toString') // false
-
point.__proto__.hasOwnProperty('toString') // true
-
// toString是原型对象的属性(因为定义在Point类上)
15.4 Class表达式
与函数一样,类也可以使用表达式来定义,使用表达式来作为类的名字,而 class
后跟的名字,用来指代当前类,只能再Class内部使用。
-
let a = class P{
-
get(){
-
return P.name;
-
}
-
}
-
let b = new a();
-
b.get(); // P
-
P.name; // ReferenceError: P is not defined
如果类的内部没用到的话,可以省略 P
,也就是可以写成下面的形式。
-
let a = class { ... }
15.5 私有方法和私有属性
由于ES6不提供,只能变通来实现:
- 1.使用命名加以区别,如变量名前添加
_
,但是不保险,外面也可以调用到。
-
class P {
-
// 公有方法
-
f1 (x) {
-
this._x(x);
-
}
-
// 私有方法
-
_x (x){
-
return this.y = x;
-
}
-
}
- 2.将私有方法移除模块,再在类内部调用
call
方法。
-
class P {
-
f1 (x){
-
f2.call(this, x);
-
}
-
}
-
function f2 (x){
-
return this.y = x;
-
}
- 3.使用
Symbol
为私有方法命名。
-
const a1 = Symbol('a1');
-
const a2 = Symbol('a2');
-
export default class P{
-
// 公有方法
-
f1 (x){
-
this[a1](x);
-
}
-
// 私有方法
-
[a1](x){
-
return this[a2] = x;
-
}
-
}
15.6 this指向问题
类内部方法的 this
默认指向类的实例,但单独使用该方法可能报错,因为 this
指向的问题。
-
class P{
-
leoDo(thing = 'any'){
-
this.print(`Leo do ${thing}`)
-
}
-
print(text){
-
console.log(text);
-
}
-
}
-
let a = new P();
-
let { leoDo } = a;
-
leoDo(); // TypeError: Cannot read property 'print' of undefined
-
// 问题出在 单独使用leoDo时,this指向调用的环境,
-
// 但是leoDo中的this是指向P类的实例,所以报错
解决方法:
- 1.在类里面绑定
this
-
class P {
-
constructor(){
-
this.name = this.name.bind(this);
-
}
-
}
- 2.使用箭头函数
-
class P{
-
constructor(){
-
this.name = (name = 'leo' )=>{
-
this.print(`my name is ${name}`)
-
}
-
}
-
}
15.7 Class的getter和setter
使用 get
和 set
关键词对属性设置取值函数和存值函数,拦截属性的存取行为。
-
class P {
-
constructor (){ ... }
-
get f (){
-
return 'getter';
-
}
-
set f (val) {
-
console.log('setter: ' + val);
-
}
-
}
-
let a = new P();
-
a.f = 100; // setter : 100
-
a.f; // getter
15.8 Class的generator方法
只要在方法之前加个( *
)即可。
-
class P {
-
constructor (...args){
-
this.args = args;
-
}
-
*[Symbol.iterator](){
-
for (let arg of this.args){
-
yield arg;
-
}
-
}
-
}
-
for (let k of new P('aa', 'bb')){
-
console.log(k);
-
}
-
// 'aa'
-
// 'bb'
15.9 Class的静态方法
由于类相当于实例的原型,所有类中定义的方法都会被实例继承,若不想被继承,只要加上 static
关键字,只能通过类来调用,即“静态方法”。
-
class P (){
-
static f1 (){ return 'aaa' };
-
}
-
P.f1(); // 'aa'
-
let a = new P();
-
a.f1(); // TypeError: a.f1 is not a function
如果静态方法包含 this
关键字,则 this
指向类,而不是实例。
-
class P {
-
static f1 (){
-
this.f2();
-
}
-
static f2 (){
-
console.log('aaa');
-
}
-
f2(){
-
console.log('bbb');
-
}
-
}
-
P.f2(); // 'aaa'
并且静态方法可以被子类继承,或者 super
对象中调用。
-
class P{
-
static f1(){ return 'leo' };
-
}
-
class Q extends P { ... };
-
Q.f1(); // 'leo'
-
class R extends P {
-
static f2(){
-
return super.f1() + ',too';
-
}
-
}
-
R.f2(); // 'leo , too'
15.10 Class的静态属性和实例属性
ES6中明确规定,Class内部只有静态方法没有静态属性,所以只能通过下面实现。
-
// 正确写法
-
class P {}
-
P.a1 = 1;
-
P.a1; // 1
-
// 无效写法
-
class P {
-
a1: 2, // 无效
-
static a1 : 2, // 无效
-
}
-
P.a1; // undefined
新提案来规定实例属性和静态属性的新写法
- 1.类的实例属性
类的实例属性可以用等式,写入类的定义中。
-
class P {
-
prop = 100; // prop为P的实例属性 可直接读取
-
constructor(){
-
console.log(this.prop); // 100
-
}
-
}
有了新写法后,就可以不再 contructor
方法里定义。
为了可读性的目的,对于那些在 constructor
里面已经定义的实例属性,新写法允许直接列出。
-
// 之前写法:
-
class RouctCounter extends React.Component {
-
constructor(prop){
-
super(prop);
-
this.state = {
-
count : 0
-
}
-
}
-
}
-
// 新写法
-
class RouctCounter extends React.Component {
-
state;
-
constructor(prop){
-
super(prop);
-
this.state = {
-
count : 0
-
}
-
}
-
}
- 2.类的静态属性
只要在实例属性前面加上 static
关键字就可以。
-
class P {
-
static prop = 100;
-
constructor(){console.log(this.prop)}; // 100
-
}
新写法方便静态属性的表达。
-
// old
-
class P { .... }
-
P.a = 1;
-
// new
-
class P {
-
static a = 1;
-
}
15.11 Class的继承
主要通过 extends
关键字实现,继承父类的所有属性和方法,通过 super
关键字来新建父类构造函数的 this
对象。
-
class P { ... }
-
class Q extends P { ... }
-
class P {
-
constructor(x, y){
-
// ...
-
}
-
f1 (){ ... }
-
}
-
class Q extends P {
-
constructor(a, b, c){
-
super(x, y); // 调用父类 constructor(x, y)
-
this.color = color ;
-
}
-
f2 (){
-
return this.color + ' ' + super.f1();
-
// 调用父类的f1()方法
-
}
-
}
子类必须在 constructor()
调用 super()
否则报错,并且只有 super
方法才能调用父类实例,还有就是,父类的静态方法,子类也可以继承到。
-
class P {
-
constructor(x, y){
-
this.x = x;
-
this.y = y;
-
}
-
static fun(){
-
console.log('hello leo')
-
}
-
}
-
// 关键点1 调用super
-
class Q extends P {
-
constructor(){ ... }
-
}
-
let a = new Q(); // ReferenceError 因为Q没有调用super
-
// 关键点2 调用super
-
class R extends P {
-
constructor (x, y. z){
-
this.z = z; // ReferenceError 没调用super不能使用
-
super(x, y);
-
this.z = z; // 正确
-
}
-
}
-
// 关键点3 子类继承父类静态方法
-
R.hello(); // 'hello leo'
super关键字:
既可以当函数使用,还可以当对象使用。
- 1.当函数调用,代表父类的构造函数,但必须执行一次。
-
class P {... };
-
class R extends P {
-
constructor(){
-
super();
-
}
-
}
- 2.当对象调用,指向原型对象,在静态方法中指向父类。
-
class P {
-
f (){ return 2 };
-
}
-
class R extends P {
-
constructor (){
-
super();
-
console.log(super.f()); // 2
-
}
-
}
-
let a = new R()
注意: super
指向父类原型对象,所以定义在父类实例的方法和属性,是无法通过 super
调用的,但是通过调用 super
方法可以把内部 this
指向当前实例,就可以访问到。
-
class P {
-
constructor(){
-
this.a = 1;
-
}
-
print(){
-
console.log(this.a);
-
}
-
}
-
class R extends P {
-
get f (){
-
return super.a;
-
}
-
}
-
let b = new R();
-
b.a; // undefined 因为a是父类P实例的属性
-
// 先调用super就可以访问
-
class Q extends P {
-
constructor(){
-
super(); // 将内部this指向当前实例
-
return super.a;
-
}
-
}
-
let c = new Q();
-
c.a; // 1
-
// 情况3
-
class J extends P {
-
constructor(){
-
super();
-
this.a = 3;
-
}
-
g(){
-
super.print();
-
}
-
}
-
let c = new J();
-
c.g(); // 3 由于执行了super()后 this指向当前实例
16 Module语法和加载实现
16.1 介绍
ES6之前用于JavaScript的模块加载方案,是一些社区提供的,主要有 CommonJS
和 AMD
两种,前者用于服务器,后者用于浏览器。
ES6提供了模块的实现,使用 export
命令对外暴露接口,使用 import
命令输入其他模块暴露的接口。
-
// CommonJS模块
-
let { stat, exists, readFire } = require('fs');
-
// ES6模块
-
import { stat, exists, readFire } = from 'fs';
16.2 严格模式
ES6模块自动采用严格模式,无论模块头部是否有 "use strict"
。
严格模式有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
deleteprop
,会报错,只能删除属性 delete*global[prop]
-
eval
不会在它的外层作用域引入变量 -
eval
和 arguments
不能被重新赋值 -
arguments
不会自动反映函数参数的变化 - 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和 fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、 static
和 interface
)
特别是,ES6中顶层 this
指向 undefined
,即不应该在顶层代码使用 this
。
16.3 export命令
使用 export
向模块外暴露接口,可以是方法,也可以是变量。
-
// 1. 变量
-
export let a = 'leo';
-
export let b = 100;
-
// 还可以
-
let a = 'leo';
-
let b = 100;
-
export {a, b};
-
// 2. 方法
-
export function f(a,b){
-
return a*b;
-
}
-
// 还可以
-
function f1 (){ ... }
-
function f2 (){ ... }
-
export {
-
a1 as f1,
-
a2 as f2
-
}
可以使用 as
重命名函数的对外接口。
特别注意:
export
暴露的必须是接口,不能是值。
-
// 错误
-
export 1; // 报错
-
let a = 1;
-
export a; // 报错
-
// 正确
-
export let a = 1; // 正确
-
let a = 1;
-
export {a}; // 正确
-
let a = 1;
-
export { a as b}; // 正确
暴露方法也是一样:
-
// 错误
-
function f(){...};
-
export f;
-
// 正确
-
export function f () {...};
-
function f(){...};
-
export {f};
16.4 import命令
加载 export
暴露的接口,输出为变量。
-
import { a, b } from '/a.js';
-
function f(){
-
return a + b;
-
}
import
后大括号指定变量名,需要与 export
的模块暴露的名称一致。
也可以使用 as
为输入的变量重命名。
-
import { a as leo } from './a.js';
import
不能直接修改输入变量的值,因为输入变量只读只是个接口,但是如果是个对象,可以修改它的属性。
-
// 错误
-
import {a} from './f.js';
-
a = {}; // 报错
-
// 正确
-
a.foo = 'leo'; // 不报错
import
命令具有提升效果,会提升到整个模块头部最先执行,且多次执行相同 import
只会执行一次。
16.5 模块的整体加载
当一个模块暴露多个方法和变量,引用时可以用 *
整体加载。
-
// a.js
-
export function f(){...}
-
export function g(){...}
-
// b.js
-
import * as obj from '/a.js';
-
console.log(obj.f());
-
console.log(obj.g());
但是,不允许运行时改变:
-
import * as obj from '/a.js';
-
// 不允许
-
obj.a = 'leo';
-
obj.b = function(){...};
16.6 export default 命令
使用 exportdefault
命令,为模块指定默认输出,引用的时候直接指定任意名称即可。
-
// a.js
-
export default function(){console.log('leo')};
-
// b.js
-
import leo from './a.js';
-
leo(); // 'leo'
exportdefault
暴露有函数名的函数时,在调用时相当于匿名函数。
-
// a.js
-
export default function f(){console.log('leo')};
-
// 或者
-
function f(){console.log('leo')};
-
export default f;
-
// b.js
-
import leo from './a.js';
exportdefault
其实是输出一个名字叫 default
的变量,所以后面不能跟变量赋值语句。
-
// 正确
-
export let a= 1;
-
let a = 1;
-
export default a;
-
// 错误
-
export default let a = 1;
exportdefault
命令的本质是将后面的值,赋给 default
变量,所以可以直接将一个值写在 exportdefault
之后。
-
// 正确
-
export detault 1;
-
// 错误
-
export 1;
16.7 export 和 import 复合写法
常常在先输入后输出同一个模块使用,即转发接口,将两者写在一起。
-
export {a, b} from './leo.js';
-
// 理解为
-
import {a, b} from './leo.js';
-
export {a, b}
常见的写法还有:
-
// 接口改名
-
export { a as b} from './leo.js';
-
// 整体输出
-
export * from './leo.js';
-
// 默认接口改名
-
export { default as a } from './leo.js';
常常用在模块继承。
16.8 浏览器中的加载规则
ES6中,可以在浏览器使用 <script>
标签,需要加入 type="module"
属性,并且这些都是异步加载,避免浏览器阻塞,即等到整个页面渲染完,再执行模块脚本,等同于打开了 <script>
标签的 defer
属性。
-
<script type="module" src="./a.js"></script>
另外,ES6模块也可以内嵌到网页,语法与外部加载脚本一致:
-
<script type="module">
-
import a from './a.js';
-
</script>
注意点:
- 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
- 模块脚本自动采用严格模式,不管有没有声明
usestrict
。 - 模块之中,可以使用
import
命令加载其他模块( .js
后缀不可省略,需要提供 绝对UR
L 或 相对UR
L),也可以使用 export
命令输出对外接口。 - 模块之中,顶层的
this
关键字返回 undefined
,而不是指向 window
。也就是说,在模块顶层使用 this
关键字,是无意义的。 - 同一个模块如果加载多次,将只执行一次。