「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。
Reflect
概述
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。Reflect对象的设计目的有这样几个。
-
将
Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在Object和Reflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法 -
修改某些
Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false// 老写法 try { Object.defineProperty(target, property, attributes); // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure } -
让
Object操作都变成函数行为。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)让它们变成了函数行为。// 老写法 "assign" in Object; // true // 新写法 Reflect.has(Object, "assign"); // true
静态方法
-
Reflect.get
-
Reflect.get(target, name, receiver),Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefinedvar myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; Reflect.get(myObject, "foo"); // 1 Reflect.get(myObject, "bar"); // 2 Reflect.get(myObject, "baz"); // 3 -
如果
name属性部署了读取函数(getter),则读取函数的this绑定receiver。var myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, }; var myReceiverObject = { foo: 4, bar: 4, }; Reflect.get(myObject, "baz", myReceiverObject); // 8 -
如果第一个参数不是对象,
Reflect.get方法会报错。Reflect.get(1, "foo"); // 报错 Reflect.get(false, "foo"); // 报错
-
-
Reflect.set
-
Reflect.set(target, name, value, receiver),Reflect.set方法设置target对象的name属性等于valuevar myObject = { foo: 1, }; myObject.foo; // 1 Reflect.set(myObject, "foo", 2); myObject.foo; // 2 -
如果
name属性设置了赋值函数,则赋值函数的this绑定receiver。var myObject = { foo: 4, set bar(value) { return (this.foo = value); }, }; var myReceiverObject = { foo: 0, }; Reflect.set(myObject, "bar", 1, myReceiverObject); myObject.foo; // 4 myReceiverObject.foo; // 1
-
-
Reflect.has
-
Reflect.has(obj, name),Reflect.has方法对应name in obj里面的in运算符var myObject = { foo: 1, }; // 旧写法 "foo" in myObject; // true // 新写法 Reflect.has(myObject, "foo"); // true如果
Reflect.has()方法的第一个参数不是对象,会报错。
-
-
Reflect.deleteProperty
-
Reflect.deleteProperty(obj, name),
Reflect.deleteProperty方法等同于delete obj[name],用于删除对象的属性const myObj = { foo: "bar" }; // 旧写法 delete myObj.foo; // 新写法 Reflect.deleteProperty(myObj, "foo");该方法返回一个布尔值。如果删除成功,或者被删除的属性不存在,返回
true;删除失败,被删除的属性依然存在,返回false。如果Reflect.deleteProperty()方法的第一个参数不是对象,会报错.
-
Promise
概述
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
promise 特点
Promise对象有以下两个特点。
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
promise 使用方法
-
ES6 规定,
Promise对象是一个构造函数,用来生成Promise实例。 -
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。var p = new Promise(function (resolve, reject) { if (true) { resolve(data); } else { reject(data); } });resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。 -
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。p.then( function (value) { // success业务处理 }, function (error) { // failure } );then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。-
看一个简单的例子
function time(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms); }); } time(1000).then((value) => { console.log(value); });
-
-
Promise 新建后就会立即执行。
let promise = new Promise(function (resolve, reject) { console.log("Promise"); resolve(); }); promise.then(function () { console.log("resolved."); }); console.log("Hi!"); //Promise //Hi //resolved
class
概述
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class改写,就是下面这样。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
基本介绍
-
constructor()constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。
-
类的实例
-
生成类的实例的写法,与 ES5 完全一样,也是使用
new命令。前面说过,如果忘记加上new,像函数那样调用Class,将会报错。class Point { // ... } // 报错 var point = Point(2, 3); // 正确 var point = new Point(2, 3); -
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在
this对象上),否则都是定义在原型上(即定义在class上)。class Point { 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 -
与 ES5 一样,类的所有实例共享一个原型对象
var p1 = new Point(2, 3); var p2 = new Point(3, 2); p1.__proto__ === p2.__proto__; //true
-
-
getter 和 setter
-
与 ES5 一样,在“类”的内部可以使用
get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。class MyClass { constructor() { // ... } get prop() { return "getter"; } set prop(value) { console.log("setter: " + value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop; // 'getter'
-
-
属性表达式
-
类的属性名,可以采用表达式
let methodName = "getArea"; class Square { constructor(length) { // ... } [methodName]() { // ... } }上面代码中,
Square类的方法名getArea,是从表达式得到的
-
static
-
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上
static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”class Foo { static classMethod() { return "hello"; } } Foo.classMethod(); // 'hello' var foo = new Foo(); foo.classMethod(); // TypeError: foo.classMethod is not a function解说:上面代码中,
Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。 -
注意,如果静态方法包含
this关键字,这个this指的是类,而不是实例class Foo { static bar() { this.baz(); } static baz() { console.log("hello"); } baz() { console.log("world"); } } Foo.bar(); // hello解说:上面代码中,静态方法
bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。 -
父类的静态方法,可以被子类继承
class Foo { static classMethod() { return "hello"; } } class Bar extends Foo {} Bar.classMethod(); // 'hello' -
静态属性
静态属性指的是 Class 本身的属性,即
Class.propName,而不是定义在实例对象(this)上的属性ES6 明确规定,Class 内部只有静态方法,没有静态属性。现在有一个提案提供了类的静态属性,写法是在实例属性的前面,加上
static关键字。class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } }
继承
-
简介
-
Class 可以通过
extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。class Point {} class ColorPoint extends Point {}解说:上面代码定义了一个
ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point类 -
子类必须在
constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。class Point { /* ... */ } class ColorPoint extends Point { constructor() {} } let cp = new ColorPoint(); // ReferenceError -
在子类的构造函数中,只有调用
super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正确 } }
-
-
super
-
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同-
第一种情况,
super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。class A {} class B extends A { constructor() { super(); } }解说:上面代码中,子类
B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例,因此super()在这里相当于A.prototype.constructor.call(this)` -
第二种情况,
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B(); -
由于
super指向父类的原型对象(prototype),所以定义在父类实例上的方法或属性,是无法通过super调用的class A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m; // undefined
-
-
Set
基本用法
-
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
-
Set本身是一个构造函数,用来生成 Set 数据结构。 -
Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。const set = new Set([1, 2, 3, 4, 4]); console.log(set); //[1,2,3,4]-
数组去重
[...new Set([1, 2, 3, 2, 4, 5])]; //[1,2,3,4,5] -
字符串去重
[...new Set("ababbc")].join(""); //"abc"
-
属性和方法
-
Set 结构的实例有以下属性。
Set.prototype.constructor:构造函数,默认就是Set函数。Set.prototype.size:返回Set实例的成员总数。
-
Set 实例的方法分为两大类
-
操作方法(用于操作数据)
-
Set.prototype.add(value):添加某个值,返回 Set 结构本身const items = new Set([]); items.add(1).add(2).add(3); console.dir(items); //[1,2,3] -
Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功const items = new Set([12, 23, 34]); var b = items.delete(12); console.log(b); //true console.log(items); //Set(2) {23, 34} -
Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。 -
Set.prototype.clear():清除所有成员,没有返回值const items = new Set([12, 23, 34]); var b = items.clear(12); console.log(b); //undefined console.log(items); //Set(0) {}
-
-
遍历方法(用于遍历成员)
-
Set.prototype.keys():返回键名的遍历器 -
Set.prototype.values():返回键值的遍历器 -
Set.prototype.entries()
let set = new Set(["red", "green", "blue"]); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"] -
Set.prototype.forEach()
let set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + " : " + value)); // 1 : 1 // 4 : 4 // 9 : 9
-
-
Map
概述
JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
基本用法
-
作为构造函数,Map 也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组
const map = new Map([ ["name", "张三"], ["title", "Author"], ]); map.size; // 2 map.has("name"); // true map.get("name"); // "张三" map.has("title"); // true map.get("title"); // "Author"
属性和方法
-
size 属性,
size属性返回 Map 结构的成员总数。const map = new Map(); map.set("foo", true); map.set("bar", false); map.size; // 2 -
Map.prototype.set(key, value)
set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键, set()方法返回的是 set 对象可以采用链式写法const m = new Map(); m.set("edition", 6); // 键是字符串 m.set(262, "standard"); // 键是数值 m.set(undefined, "nah"); // 键是 undefined -
Map.prototype.get(key)
get方法读取key对应的键值,如果找不到key,返回undefined。const m = new Map(); const hello = function () { console.log("hello"); }; m.set(hello, "Hello ES6!"); // 键是函数 m.get(hello); // Hello ES6! -
Map.prototype.has(key)
has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中const m = new Map(); m.set("edition", 6); m.set(262, "standard"); m.set(undefined, "nah"); m.has("edition"); // true m.has("years"); // false m.has(262); // true m.has(undefined); // true
-
Map.prototype.delete(key)
delete方法删除某个键,返回true。如果删除失败,返回false。const m = new Map(); m.set(undefined, "nah"); m.has(undefined); // true m.delete(undefined); m.has(undefined); // false -
Map.prototype.clear()
clear方法清除所有成员,没有返回值let map = new Map(); map.set("foo", true); map.set("bar", false); map.size; // 2 map.clear(); map.size; // 0
遍历
-
Map.prototype.keys():返回键名的遍历器。 -
Map.prototype.values():返回键值的遍历器 -
Map.prototype.entries():返回所有成员的遍历器 -
Map.prototype.forEach():遍历 Map 的所有成员const map = new Map([ ["F", "no"], ["T", "yes"], ]); for (let key of map.keys()) { console.log(key); } // "F" // "T" for (let value of map.values()) { console.log(value); } // "no" // "yes" for (let item of map.entries()) { console.log(item[0], item[1]); } // "F" "no" // "T" "yes" // 或者 for (let [key, value] of map.entries()) { console.log(key, value); } // "F" "no" // "T" "yes" map.forEach(function (value, key, map) { console.log("Key: %s, Value: %s", key, value); });forEach方法还可以接受第二个参数,用来绑定this。
Symbol
概述
ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
var obj = {
say: "lagou",
};
var say = Symbol(); //say 是symbol类型
obj[say] = "web";
console.log(obj); //{say: "lagou", Symbol(): "web"}
语法
-
Symbol函数前不能使用new命令,否则会报错 -
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分 -
每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性
var a = Symbol(); var b = Symbol(); console.log(a === b); //false var a = Symbol("a"); var b = Symbol("b"); console.log(a === b); //false注意:
Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的var a = Symbol("a"); var b = Symbol("a"); console.log(a === b); //false -
Symbol 值不能与其他类型的值进行运算,会报错
let sym = Symbol("My symbol"); "your symbol is " + sym // TypeError: can't convert symbol to string `your symbol is ${sym}`; // TypeError: can't convert symbol to string -
Symbol 值作为对象属性名时,不能用点运算符
const mySymbol = Symbol(); const a = {}; a.mySymbol = "Hello!"; console.log(a[mySymbol]); //undefined console.log(a["mySymbol"]); //hello
-
Symbol 作为属性名,遍历对象的时候,该属性不会出现在
for...in、for...of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。const obj = {}; let a = Symbol("a"); let b = Symbol("b"); obj[a] = "Hello"; obj[b] = "World"; const objectSymbols = Object.getOwnPropertySymbols(obj); console.log(objectSymbols); //[Symbol(a), Symbol(b)] -
有时,我们希望重新使用同一个 Symbol 值,
Symbol.for()方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。let s1 = Symbol.for("foo"); let s2 = Symbol.for("foo"); s1 === s2; // trueSymbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值Symbol.for("bar") === Symbol.for("bar"); // true Symbol("bar") === Symbol("bar");由于
Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。Symbol.for()为 Symbol 值登记的名字,是全局环境的,不管有没有在全局环境运行function foo() { return Symbol.for("bar"); } const x = foo(); const y = Symbol.for("bar"); console.log(x === y); // true
可迭代接口
Iterater 的概念
-
简单介绍
JavaScript 原有的表示“集合”的数据结构,主要是数组(
Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令
for...of循环,Iterator 接口主要供for...of消费。 -
Iterator 的遍历过程
-
创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
-
第一次调用指针对象的
next方法,可以将指针指向数据结构的第一个成员 -
第二次调用指针对象的
next方法,指针就指向数据结构的第二个成员 -
不断调用指针对象的
next方法,直到它指向数据结构的结束位置每一次调用
next方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value和done两个属性的对象。其中,value属性是当前成员的值,done属性是一个布尔值,表示遍历是否结束。简单的Iterator遍历器的实现; var it = easyIterator(["a", "b"]); it.next(); // { value: "a", done: false } it.next(); // { value: "b", done: false } it.next(); // { value: undefined, done: true } function easyIterator(array) { var nextIndex = 0; return { next: function () { return nextIndex < array.length ? { value: array[nextIndex++], done: false } : { value: undefined, done: true }; }, }; }
-
Iterater 接口
-
字符串 数组 set map arguments 都有 iterater 接口,nodelist 集合,都可以用 for of 遍历
var st = "lagou"; for (i of st) { console.log(i); // l a g o u } var arr = [1, 2]; for (v of arr) { console.log(v); //1 2 } function fn(a, b, c) { for (i of arguments) { console.log(i); //1 2 3 } } fn(1, 2, 3);
Modules
概述
- JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的
require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
语法
-
export
-
export命令用于规定模块的对外接口 -
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用
export关键字输出该变量。下面是一个 JS 文件,里面使用export命令输出变量//demo.js export var firstName = "Michael"; export var lastName = "Jackson"; export var year = 1958; //或者 var firstName = "Michael"; var lastName = "Jackson"; var year = 1958; export { firstName, lastName, year };
-
-
import
-
import命令用于输入其他模块提供的功能 -
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js)对外接口的名称相同// main.js import { firstName, lastName, year } from "./profile.js"; function setName(element) { element.textContent = firstName + " " + lastName; }
-
-
export default
-
为了给用户提供方便,就要用到
export default命令,为模块指定默认输出 -
本质上,
export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字 -
在
import命令后面,不再使用大括号 -
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次// export-default.js export default function () { console.log("foo"); }// import-default.js import customName from "./export-default"; customName(); // 'foo'
-
浏览器端加载实现
-
浏览器加载 ES6 模块,也使用标签,但是要加入
type="module"属性// 01.js export var a = 123;//demo.html <script type="module">import {a} from "./01.js"; console.log(a)//123</script>
-
脚本异步加载
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>解说:上面代码中,标签打开
defer或async属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。defer 与 async 的区别是:defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,defer 是“渲染完再执行”,async 是“下载完就执行”。另外,如果有多个 defer 脚本,会按照它们在页面出现的顺序加载,而多个 async 脚本是不能保证加载顺序的。