let s = Symbol();
console.log(s); // Symbol()
typeof s; // "symbol"
复制代码
let s = new Symbol();
// Uncaught TypeError: Symbol is not a constructor(…)
复制代码
let s1 = Symbol('s1');
let s2 = Symbol('s2');
console.log(s1); // Symbol(s1)
console.log(s2); // Symbol(s2)
s1 === s2; // false
let s3 = Symbol('s2');
s2 === s3; // false
复制代码
给Symbol函数加了参数以后,控制台输出的时候能够区分究竟是哪个值。Symbol函数的参数只是对当前Symbol值的描述,所以相同参数的Symbol函数返回值是不相等的。html
var mysym1 = Symbol('my symbol');
mysym1.toString() // 'Symbol('my symbol')'
String(mysym1) // 'Symbol('my symbol')'
var mysym2 = Symbol();
Boolean(mysym2); // true
Number(mysym2) // TypeError: Cannot convert a Symbol value to a number(…)
复制代码
let a = {};
let s4 = Symbol();
// 第一种写法
a[s4] = 'mySymbol';
// 第二种写法
a = {
[s4]: 'mySymbol'
}
// 第三种写法
Object.defineProperty(a, s4, {value: 'mySymbol1'});
a.s4; // undefined
a.s4 = 'mySymbol2';
a[s4] // mySymbol1
a['s4'] // 'mySymbol2'
复制代码
使用对象的Symbol值做为属性名时,获取相应的属性值不能用点运算符; 若是用点运算符来给对象的属性赋Symbol类型的值,实际上属性名会变成一个字符串,而不是一个Symbol值; 在对象内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中,不然只是一个字符串。 6. Symbol值做为属性名的遍历。 使用for...in和for...of都没法遍历到Symbol值的属性,Symbol值做为对象的属性名,也没法经过Object.keys()、Object.getOwnPropertyNames()来获取了。可是,不一样担忧,这种日常的需求确定是会有解决办法的。咱们可使用Object.getOwnPropertySymbols()方法获取一个对象上的Symbol属性名。也可使用Reflect.ownKeys()返回全部类型的属性名,包括常规属性名和 Symbol属性名。git
let s5 = Symbol('s5');
let s6 = Symbol('s6');
let a = {
[s5]: 's5',
[s6]: 's6'
}
Object.getOwnPropertySymbols(a); // [Symbol(s5), Symbol(s6)]
a.hello = 'hello';
Reflect.ownKeys(a); // ["hello", Symbol(s5), Symbol(s6)]
复制代码
利用Symbol值做为对象属性的名称时,不会被常规方法遍历到这一特性,能够为对象定义一些非私有的可是又但愿只有内部可用的方法。 7. Symbol.for()和Symbol.keyFor()。 Symbol.for()函数也能够用来生成Symbol值,但该函数有一个特殊的用处,就是能够重复使用一个Symbol值。es6
let s1 = Symbol.for("s11");
let s2 = Symbol.for("s22");
console.log(s1===s2)//false
let s3 = Symbol("s33");
let s4 = Symbol("s33");
console.log(s3===s4)//false
console.log(Symbol.keyFor(s3))//undefined
console.log(Symbol.keyFor(s2))//"s22"
console.log(Symbol.keyFor(s1))//"s11"
复制代码
Symbol.for()
函数要接受一个字符串做为参数,先搜索有没有以该参数做为名称的Symbol值,若是有,就直接返回这个Symbol值,不然就新建并返回一个以该字符串为名称的Symbol值。 Symbol.keyFor()
函数是用来查找一个Symbol值的登记信息的,Symbol()写法没有登记机制,因此返回undefined;而Symbol.for()函数会将生成的Symbol值登记在全局环境中,因此Symbol.keyFor()函数能够查找到用Symbol.for()函数生成的Symbol值。 8. 内置Symbol值。 ES6提供了11个内置的Symbol值,分别是Symbol.hasInstance 、Symbol.isConcatSpreadable 、Symbol.species 、Symbol.match 、Symbol.replace 、Symbol.search 、Symbol.split 、Symbol.iterator 、Symbol.toPrimitive 、Symbol.toStringTag 、Symbol.unscopables 等。github
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4
//经过add方法向 Set 结构加入成员,结果代表 Set 结构不会添加剧复的值。
复制代码
Set 函数能够接受一个数组做为参数,用来初始化。算法
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5
// 例三
function divs () {
return [...document.querySelectorAll('div')];
}
const set = new Set(divs());
set.size // 56
// 相似于
divs().forEach(div => set.add(div));
set.size // 56
复制代码
利用Set数据结构的成员都是惟一的这个特性,能够轻松对数组去重。编程
// 去除数组的重复成员
[...new Set(array)]
复制代码
向 Set 加入值的时候,不会发生类型转换,因此5
和"5"
是两个不一样的值。 Set 内部判断两个值是否不一样,使用的算法叫作“Same-value equality”,它相似于精确相等运算符(===
),主要的区别是NaN
等于自身,而精确相等运算符认为NaN
不等于自身。 两个对象老是不相等的。 Set 结构的实例有如下属性:json
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。 Set 实例的方法分为两大类:操做方法(用于操做数据)和遍历方法(用于遍历成员)。四个操做方法:数组
add(value)
:添加某个值,返回 Set 结构自己。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除全部成员,没有返回值。 Array.from()能够将 Set 结构转为数组。const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
复制代码
Set 结构的实例有四个遍历方法,能够用于遍历成员。promise
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每一个成员 keys
方法、values
方法、entries
方法返回的都是遍历器对象。因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),因此keys
方法和values
方法的行为彻底一致。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 结构的实例默承认遍历,它的默认遍历器生成函数就是它的values
方法。 Set 结构的实例与数组同样,也拥有forEach
方法,用于对每一个成员执行某种操做,没有返回值。浏览器
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9
复制代码
forEach
方法还能够有第二个参数,表示绑定处理函数内部的this
对象。 2. WeakSet WeakSet 结构与 Set 相似,也是不重复的值的集合。可是,它与 Set 有两个区别:
const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}
//下面的写法不行
const b = [3, 4];
const ws = new WeakSet(b);
// Uncaught TypeError: Invalid value used in weak set(…)
复制代码
WeakSet 结构有如下三个方法。
WeakSet.prototype.add(value)
:向 WeakSet 实例添加一个新成员。WeakSet.prototype.delete(value)
:清除 WeakSet 实例的指定成员。WeakSet.prototype.has(value)
:返回一个布尔值,表示某个值是否在 WeakSet 实例之中。 WeakSet 没有size
属性,没有办法遍历它的成员。const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false
复制代码
做为构造函数,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"
复制代码
只有对同一个对象的引用,Map 结构才将其视为同一个键。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
复制代码
若是 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,好比0
和-0
就是一个键,布尔值true
和字符串true
则是两个不一样的键。另外,undefined
和null
也是两个不一样的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。
实例的属性和操做方法
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回全部成员的遍历器。forEach()
:遍历 Map 的全部成员。 Map 的遍历顺序就是插入顺序。遍历行为基本与set的一致。 Map能够转为数组。const myMap = new Map()
.set(true, 7)
.set({foo: 3}, ['abc']);
[...myMap] //[[true, 7], [{foo: 3},["abc"]]]
复制代码
数组也能够转为Map
new Map([
[true, 7],
[{foo: 3}, ['abc']]
])
// Map {
// true => 7,
// Object {foo: 3} => ['abc']
// }
复制代码
若是全部 Map 的键都是字符串,它能够转为对象。
function strMapToObj(strMap) {
let obj = Object.create(null);
for (let [k,v] of strMap) {
obj[k] = v;
}
return obj;
}
const myMap = new Map()
.set('yes', true)
.set('no', false);
strMapToObj(myMap)
// { yes: true, no: false }
复制代码
对象也能够转为Map
function objToStrMap(obj) {
let strMap = new Map();
for (let k of Object.keys(obj)) {
strMap.set(k, obj[k]);
}
return strMap;
}
objToStrMap({yes: true, no: false})
// Map {"yes" => true, "no" => false}
复制代码
Map能够转为JSON,可是要分两种状况。 一种状况是,Map 的键名都是字符串,这时能够选择转为对象 JSON。
function strMapToJson(strMap) {
return JSON.stringify(strMapToObj(strMap));
}
let myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap)
// '{"yes":true,"no":false}'
复制代码
另外一种状况是,Map 的键名有非字符串,这时能够选择转为数组 JSON。
function mapToArrayJson(map) {
return JSON.stringify([...map]);
}
let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
复制代码
JSON能够转为Map 正常状况下,全部键名都是字符串。
function jsonToStrMap(jsonStr) {
return objToStrMap(JSON.parse(jsonStr));
}
jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
复制代码
可是,有一种特殊状况,整个 JSON 就是一个数组,且每一个数组成员自己,又是一个有两个成员的数组。这时,它能够一一对应地转为 Map。这每每是数组转为 JSON 的逆操做。
function jsonToMap(jsonStr) {
return new Map(JSON.parse(jsonStr));
}
jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
复制代码
WeakMap
结构与Map
结构相似,也是用于生成键值对的集合。 WeakMap
与Map
的区别有两点:WeakMap
只接受对象做为键名(null
除外),不接受其余类型的值做为键名。WeakMap
的键名所指向的对象,不计入垃圾回收机制。 WeakMap
的设计目的在于,有时咱们想在某个对象上面存放一些数据,可是这会造成对于这个对象的引用。const e1 = document.getElementById('foo');
const e2 = document.getElementById('bar');
const arr = [
[e1, 'foo 元素'],
[e2, 'bar 元素'],
];
//e1和e2是两个对象,咱们经过arr数组对这两个对象添加一些文字说明。这就造成了arr对e1和e2的引用。
//一旦再也不须要这两个对象,咱们就必须手动删除这个引用,不然垃圾回收机制就不会释放e1和e2占用的内存。
复制代码
WeakMap 就是为了解决这个问题而诞生的,它的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
复制代码
WeakMap
只有四个方法可用:get()
、set()
、has()
、delete()
。 没法被遍历,由于没有size。没法被清空,由于没有clear(),跟WeakSet类似。
let myElement = document.getElementById('logo');
let myWeakmap = new WeakMap();
myWeakmap.set(myElement, {timesClicked: 0});
myElement.addEventListener('click', function() {
let logoData = myWeakmap.get(myElement);
logoData.timesClicked++;
}, false);
复制代码
上面代码中,myElement
是一个 DOM 节点,每当发生click
事件,就更新一下状态。咱们将这个状态做为键值放在 WeakMap 里,对应的键名就是myElement
。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
//上面代码对一个空对象架设了一层拦截,重定义了属性的读取(get)和设置(set)行为
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
复制代码
上面代码说明,Proxy 实际上重载(overload)了点运算符,即用本身的定义覆盖了语言的原始定义。 ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
let proxy = new Proxy(target, handler);
复制代码
Proxy 对象的全部用法,都是上面这种形式,不一样的只是handler
参数的写法。其中,new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
复制代码
若是handler
没有设置任何拦截,那就等同于直接通向原对象。
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
复制代码
上面代码中,handler
是一个空对象,没有任何拦截效果,访问proxy
就等同于访问target
。 同一个拦截器函数,能够设置拦截多个操做。 对于能够设置、但没有设置拦截的操做,则直接落在目标对象上,按照原先的方式产生结果。 下面是 Proxy 支持的拦截操做一览,一共 13 种:
proxy.foo
和proxy['foo']
。proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。propKey in proxy
的操做,返回一个布尔值。delete proxy[propKey]
的操做,返回一个布尔值。Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
,返回一个数组。该方法返回目标对象全部自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。Object.preventExtensions(proxy)
,返回一个布尔值。Object.getPrototypeOf(proxy)
,返回一个对象。Object.isExtensible(proxy)
,返回一个布尔值。Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。若是目标对象是函数,那么还有两种额外操做能够拦截。proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。new proxy(...args)
。 deleteProperty
方法用于拦截delete
操做,若是这个方法抛出错误或者返回false
,当前属性就没法被delete
命令删除。 apply
方法拦截函数的调用、call
和apply
操做。 get
方法用于拦截某个属性的读取操做。let obj2 = new Proxy(obj,{
get(target,property,a){
//return 35;
/*console.log(target) console.log(property)*/
let Num = ++wkMap.get(obj).getPropertyNum;
console.log(`当前访问对象属性次数为:${Num}`)
return target[property]
},
deleteProperty(target,property){
return false;
},
apply(target,ctx,args){
return Reflect.apply(...[target,[],args]);;
}
})
复制代码
Proxy.revocable
方法返回一个可取消的 Proxy 实例。let target = {};
let handler = {};
let {proxy, revoke} = Proxy.revocable(target, handler);
proxy.foo = 123;
proxy.foo // 123
revoke();
proxy.foo // TypeError: Revoked
复制代码
Proxy.revocable
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,能够取消Proxy
实例。上面代码中,当执行revoke
函数以后,再访问Proxy
实例,就会抛出一个错误。 Proxy.revocable
的一个使用场景是,目标对象不容许直接访问,必须经过代理访问,一旦访问结束,就收回代理权,不容许再次访问。 3. this问题。 虽然 Proxy 能够代理针对目标对象的访问,但它不是目标对象的透明代理,即不作任何拦截的状况下,也没法保证与目标对象的行为一致。主要缘由就是在 Proxy 代理的状况下,目标对象内部的this
关键字会指向 Proxy 代理。
const target = {
m: function () {
console.log(this === proxy);
}
};
const handler = {};
const proxy = new Proxy(target, handler);
target.m() // false
proxy.m() // true
//一旦proxy代理target.m,后者内部的this就是指向proxy,而不是target。
复制代码
Reflect
对象与Proxy
对象同样,也是 ES6 为了操做对象而提供的新 API。 设计目的:
Object
对象的一些明显属于语言内部的方法(好比Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,将来的新方法将只部署在Reflect
对象上。Object
方法的返回结果,让其变得更合理。好比,Object.defineProperty(obj, name, desc)
在没法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。Object
操做都变成函数行为。某些Object
操做是命令式,好比name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象能够方便地调用对应的Reflect
方法,完成默认行为,做为修改行为的基础。也就是说,无论Proxy
怎么修改默认行为,你总能够在Reflect
上获取默认行为。Promise
对象表明一个异步操做,有三种状态: pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。 只有异步操做的结果,能够决定当前是哪种状态,任何其余操做都没法改变这个状态。Promise
,一旦新建它就会当即执行,没法中途取消。Promise
内部抛出的错误,不会反应到外部。pending
状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。let p = new Promise((resolve,reject)=>{
//一些异步操做
setTimeout(()=>{
console.log("123")
resolve("abc");
},0)
})
.then(function(data){
//resolve状态
console.log(data)
},function(err){
//reject状态
})
//'123'
//'abc'
复制代码
Promise
实例生成之后,能够用then
方法分别指定resolved
状态和rejected
状态的回调函数。 也就是说,状态由实例化时的参数(函数)执行来决定的,根据不一样的状态,看看须要走then的第一个参数仍是第二个。 resolve()和reject()的参数会传递到对应的回调函数的data或err。 6. 链式操做的用法。 从表面上看,Promise只是可以简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数可以及时调用,它比传递callback函数要简单、灵活的多。因此使用Promise的正确场景是这样的:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return runAsync3();
})
.then(function(data){
console.log(data);
});
//异步任务1执行完成
//随便什么数据1
//异步任务2执行完成
//随便什么数据2
//异步任务3执行完成
//随便什么数据3
function runAsync1(){
var p = new Promise(function(resolve, reject){
//作一些异步操做
setTimeout(function(){
console.log('异步任务1执行完成');
resolve('随便什么数据1');
}, 1000);
});
return p;
}
function runAsync2(){
var p = new Promise(function(resolve, reject){
//作一些异步操做
setTimeout(function(){
console.log('异步任务2执行完成');
resolve('随便什么数据2');
}, 2000);
});
return p;
}
function runAsync3(){
var p = new Promise(function(resolve, reject){
//作一些异步操做
setTimeout(function(){
console.log('异步任务3执行完成');
resolve('随便什么数据3');
}, 2000);
});
return p;
}
复制代码
在then方法中,你也能够直接return数据而不是Promise对象,在后面的then中也能够接收到数据:
runAsync1()
.then(function(data){
console.log(data);
return runAsync2();
})
.then(function(data){
console.log(data);
return '直接返回数据'; //这里直接返回数据
})
.then(function(data){
console.log(data);
});
//异步任务1执行完成
//随便什么数据1
//异步任务2执行完成
//随便什么数据2
//直接返回数据
复制代码
let num = 10;
let p1 = function() {
return new Promise((resolve,reject)=>{
if (num <= 5) {
resolve("<=5,走resolce")
console.log('resolce不能结束Promise')
}else{
reject(">5,走reject")
console.log('reject不能结束Promise')
}
})
}
p1()
.then(function(data){
console.log(data)
},function(err){
console.log(err)
})
//reject不能结束Promise
//>5,走reject
复制代码
resolve和reject永远会在当前环境的最后执行,因此后面的同步代码会先执行。 若是resolve和reject以后还有代码须要执行,最好放在then里。 而后在resolve和reject前面写上return。 8. Promise.prototype.catch
。 Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误时的回调函数。
p1()
.then(function(data){
console.log(data)
})
.catch(function(err){
console.log(err)
})
//reject不能结束Promise
//>5,走reject
复制代码
Promise.all()
。 Promise.all
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.all([p1, p2, p3]);
复制代码
p
的状态由p1
、p2
、p3
决定,分红两种状况。
p1
、p2
、p3
的状态都变成resolve
,p
的状态才会变成resolve
。 此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。 promises
是包含 3 个 Promise 实例的数组,只有这 3 个实例的状态都变成resolve
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。 若是做为参数的 Promise 实例,本身定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法,若是没有参数没有定义本身的catch,就会调用Promise.all()
的catch
方法。Promise.race
。 Promise.race
方法一样是将多个 Promise 实例,包装成一个新的 Promise 实例。const p = Promise.race([p1, p2, p3]);
复制代码
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。 11. Promise.resolve()
。 有时须要将现有对象转为 Promise 对象,Promise.resolve
方法就起到这个做用。下面代码将123转为一个 Promise 对象。
const jsPromise = Promise.resolve('123');
复制代码
Promise.resolve
等价于下面的写法。
Promise.resolve('123')
// 等价于
new Promise(resolve => resolve('123'))
复制代码
Promise.resolve
方法的参数分红四种状况。
Promise.resolve
将不作任何修改、原封不动地返回这个实例。thenable
对象指的是具备then
方法的对象,好比下面这个对象。let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
复制代码
Promise.resolve
方法会将这个对象转为 Promise 对象,而后就当即执行thenable
对象的then
方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
复制代码
上面代码中,thenable
对象的then
方法执行后,对象p1
的状态就变为resolved
,从而当即执行最后那个then
方法指定的回调函数,输出 42。
then
方法的对象,则Promise.resolve
方法返回一个新的 Promise 对象,状态为resolved
。const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
复制代码
上面代码生成一个新的 Promise 对象的实例p
。因为字符串Hello
不属于异步操做(判断方法是字符串对象不具备 then 方法),返回 Promise 实例的状态从一辈子成就是resolved
,因此回调函数会当即执行。Promise.resolve
方法的参数,会同时传给回调函数。
Promise.resolve
方法容许调用时不带参数,直接返回一个resolved
状态的 Promise 对象。 因此,若是但愿获得一个 Promise 对象,比较方便的方法就是直接调用Promise.resolve
方法。const p = Promise.resolve();
p.then(function () {
// ...
});
复制代码
上面代码的变量p
就是一个 Promise 对象。 须要注意的是,当即resolve
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
setTimeout(function () {
console.log('three');
}, 0);
Promise.resolve().then(function () {
console.log('two');
});
console.log('one');
// one
// two
// three
复制代码
上面代码中,setTimeout(fn, 0)
在下一轮“事件循环”开始时执行,Promise.resolve()
在本轮“事件循环”结束时执行,console.log('one')
则是当即执行,所以最早输出。 12. Promise.reject()
。 Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了
复制代码
上面代码生成一个 Promise 对象的实例p
,状态为rejected
,回调函数会当即执行。 注意,Promise.reject()
方法的参数,会原封不动地做为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。
const thenable = {
then(resolve, reject) {
reject('出错了');
}
};
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable)
})
// true
复制代码
上面代码中,Promise.reject
方法的参数是一个thenable
对象,执行之后,后面catch
方法的参数不是reject
抛出的“出错了”这个字符串,而是thenable
对象。
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'json';
xhr.onload = function(){
console.log(xhr.response);
};
xhr.onerror = function(){
console.log("Oops, error");
};
xhr.send();
复制代码
使用Fetch后:
fetch(url)
.then(function(response){
return response.json();
}).then(function(data){
console.log(data);
}).catch(function(e){
console.log("Oops, error");
});
复制代码
使用ES6的箭头函数后:
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
复制代码
使用async/await来作最终优化:
try{
let response = await fetch(url);
let data = await response.json();
console.log(data);
}catch(e){
console.log("Oops, error", e);
}
//注:这段代码若是想运行,外面须要包一个async function
复制代码
fetch(url, options).then(function(response){
//handle HTTP response
}, function(error){
//handle network error
})
复制代码
url:定义要获取的资源。这多是:
USVString
字符串,包含要获取资源的URL。Request
对象。 options(可选) 一个配置项对象,包括全部对请求的设置,可选的参数有:method
:请求使用的方法,如GET、POST。headers
:请求的头信息,形式为Headers对象或ByteString。body
:请求的body信息,多是一个Blob、BufferSource、FormData、URLSearchParams或者USVString对象。注意GET或HEAD方法的请求不能包含body信息。mode
:请求的模式,如cors、no-cors或者same-origin。credentials
:请求的credentials,如omit、same-origin或者include。cache
:请求的cache模式:default,no-store,reload,no-cache,force-cache,或者only-if-cached。response: 一个Promise,resolve时回传Response
对象: 属性:
fetch('/users.html')
.then(function(response){
return response.text();
}).then(function(body){
document.body.innerHTML = body
})
复制代码
post
fetch('/users', {
method:'POST',
headers:{
'Accept':'application/json',
'Content-Type':'application/json'
},
body:JSON.stringify({
name:'Hubot',
login:'hubot',
})
})
复制代码
迭代器是一种接口、是一种机制。 为各类不一样的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就能够完成遍历操做(即依次处理该数据结构的全部成员)。 Iterator 的做用有三个:
for...of
消费。 Iterator本质上,就是一个指针对象。 过程是这样的: (1)建立一个指针对象,指向当前数据结构的起始位置。 (2)第一次调用指针对象的next
方法,能够将指针指向数据结构的第一个成员。 (3)第二次调用指针对象的next
方法,指针就指向数据结构的第二个成员。 (4)不断调用指针对象的next
方法,直到它指向数据结构的结束位置。 普通函数实现Iterator。function myIter(obj){
let i = 0;
return {
next(){
let done = (i>=obj.length);
let value = !done ? obj[i++] : undefined;
return {
value,
done,
}
}
}
}
复制代码
原生具有 Iterator 接口的数据结构以下:Array、Map、Set、String、函数的arguments对象、NodeList对象。 下面的例子是数组的Symbol.iterator
属性。
let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }
复制代码
下面是另外一个相似数组的对象调用数组的Symbol.iterator
方法的例子。
let iterable = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // 'a', 'b', 'c'
}
复制代码
注意,普通对象部署数组的Symbol.iterator
方法,并没有效果。
let iterable = {
a: 'a',
b: 'b',
c: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
for (let item of iterable) {
console.log(item); // undefined, undefined, undefined
}
复制代码
字符串是一个相似数组的对象,也原生具备 Iterator 接口。
var someString = "hi";
typeof someString[Symbol.iterator]
// "function"
var iterator = someString[Symbol.iterator]();
iterator.next() // { value: "h", done: false }
iterator.next() // { value: "i", done: false }
iterator.next() // { value: undefined, done: true }
复制代码
function
关键字与函数名之间有一个星号;yield
表达式,定义不一样的内部状态。function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
复制代码
上面代码定义了一个 Generator 函数helloWorldGenerator
,它内部有两个yield
表达式(hello
和world
),即该函数有三个状态:hello,world 和 return 语句(结束执行)。 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是遍历器对象。 下一步,必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句)为止。换言之,Generator 函数是分段执行的,yield
表达式是暂停执行的标记,而next
方法能够恢复执行。 ES6 没有规定,function
关键字与函数名之间的星号,写在哪一个位置。这致使下面的写法都能经过。
function * foo(x, y) { ··· }
function *foo(x, y) { ··· }
function* foo(x, y) { ··· }
function*foo(x, y) { ··· }
复制代码
next
方法才会遍历下一个内部状态,因此其实提供了一种能够暂停执行的函数。yield
表达式就是暂停标志。 遍历器对象的next
方法的运行逻辑以下。 (1)遇到yield
表达式,就暂停执行后面的操做,并将紧跟在yield
后面的那个表达式的值,做为返回的对象的value
属性值。 (2)下一次调用next
方法时,再继续往下执行,直到遇到下一个yield
表达式。 (3)若是没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,做为返回的对象的value
属性值。 (4)若是该函数没有return
语句,则返回的对象的value
属性值为undefined
。 yield表达式与return语句的相同之处: 都能返回紧跟在语句后面的那个表达式的值。 yield表达式与return语句的不一样之处: 每次遇到yield
,函数暂停执行,下一次再从该位置继续向后执行,而return
语句不具有位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return
语句,可是能够执行屡次(或者说多个)yield
表达式。正常函数只能返回一个值,由于只能执行一次return
;Generator 函数能够返回一系列的值,由于能够有任意多个yield
。 yield
表达式只能用在 Generator 函数里面,用在其余地方都会报错。 另外,yield
表达式若是用在另外一个表达式之中,必须放在圆括号里面。console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield 123)); // OK
复制代码
Symbol.iterator
属性,从而使得该对象具备 Iterator 接口。Object.prototype[Symbol.iterator] = function* (){
for(let i in this){
yield this[i];
}
}
//--------------
function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
复制代码
yield
表达式自己没有返回值,或者说老是返回undefined
。next
方法能够带一个参数,该参数就会被看成上一个yield
表达式的返回值。function* f() {
for(var i = 0; true; i++) {
var reset = yield i;
if(reset) { i = -1; }
}
}
var g = f();
g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }
复制代码
Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。经过next
方法的参数,就有办法在 Generator 函数开始运行以后,继续向函数体内部注入值。
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
复制代码
for...of
循环能够自动遍历 Generator 函数时生成的Iterator
对象,且此时再也不须要调用next
方法。function *foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
复制代码
function* fibonacci() {
let [prev, curr] = [1, 1];
while(true){
[prev, curr] = [curr, prev + curr];
yield curr;
}
}
for (let n of fibonacci()) {
if (n > 10000000) break;
console.log(n);
}
复制代码
return
方法,能够返回给定的值,而且终结遍历 Generator 函数。function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
g.next() // { value: 1, done: false }
g.return('foo') // { value: "foo", done: true }
g.next() // { value: undefined, done: true }
复制代码
function* foo() {
yield 'a';
yield 'b';
}
function* bar() {
yield 'x';
foo();
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "y"
复制代码
foo
和bar
都是 Generator 函数,在bar
里面调用foo
,是不会有效果的。 这个就须要用到yield*
表达式,用来在一个 Generator 函数里面执行另外一个 Generator 函数。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
// "x"
// "a"
// "b"
// "y"
复制代码
再来看一个对比的例子。
function* inner() {
yield 'hello!';
}
function* outer1() {
yield 'open';
yield inner();
yield 'close';
}
var gen = outer1()
gen.next().value // "open"
gen.next().value // 返回一个遍历器对象
gen.next().value // "close"
function* outer2() {
yield 'open'
yield* inner()
yield 'close'
}
var gen = outer2()
gen.next().value // "open"
gen.next().value // "hello!"
gen.next().value // "close"
复制代码
上面例子中,outer2
使用了yield*
,outer1
没使用。结果就是,outer1
返回一个遍历器对象,outer2
返回该遍历器对象的内部值。 从语法角度看,若是yield
表达式后面跟的是一个遍历器对象,须要在yield
表达式后面加上星号,代表它返回的是一个遍历器对象。这被称为yield*
表达式。 8. 做为对象属性的 Generator 函数 若是一个对象的属性是 Generator 函数,能够简写成下面的形式。
let obj = {
* myGeneratorMethod() {
···
}
};
复制代码
async
函数使用时就是将 Generator 函数的星号(*
)替换成async
,将yield
替换成await
,仅此而已。 async
函数对 Generator 函数的区别: (1)内置执行器。 Generator 函数的执行必须靠执行器,而async
函数自带执行器。也就是说,async
函数的执行,与普通函数如出一辙,只要一行。 (2)更好的语义。 async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操做,await
表示紧跟在后面的表达式须要等待结果。 (3)正常状况下,await
命令后面是一个 Promise 对象。若是不是,会被转成一个当即resolve
的 Promise 对象。 (4)返回值是 Promise。 async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你能够用then
方法指定下一步的操做。 进一步说,async
函数彻底能够看做多个异步操做,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。await
后面的异步操做出错,那么等同于async
函数返回的 Promise 对象被reject
。async function f() {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出错了
复制代码
上面代码中,async
函数f
执行后,await
后面的 Promise 对象会抛出一个错误对象,致使catch
方法的回调函数被调用,它的参数就是抛出的错误对象。具体的执行机制,能够参考后文的“async 函数的实现原理”。 防止出错的方法,也是将其放在try...catch
代码块之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error('出错了');
});
} catch(e) {
}
return await('hello world');
}
复制代码
若是有多个await
命令,能够统一放在try...catch
结构中。
async function main() {
try {
const val1 = await firstStep();
const val2 = await secondStep(val1);
const val3 = await thirdStep(val1, val2);
console.log('Final: ', val3);
}
catch (err) {
console.error(err);
}
}
复制代码
var fn = function (time) {
console.log("开始处理异步");
setTimeout(function () {
console.log(time);
console.log("异步处理完成");
iter.next();
}, time);
};
function* g(){
console.log("start");
yield fn(3000)
yield fn(500)
yield fn(1000)
console.log("end");
}
let iter = g();
iter.next();
复制代码
下面是async函数的写法。
var fn = function (time) {
return new Promise(function (resolve, reject) {
console.log("开始处理异步");
setTimeout(function () {
resolve();
console.log(time);
console.log("异步处理完成");
}, time);
})
};
var start = async function () {
// 在这里使用起来就像同步代码那样直观
console.log('start');
await fn(3000);
await fn(500);
await fn(1000);
console.log('end');
};
start();
复制代码
class
关键字,能够定义类。 ES6 的class
能够看做只是一个语法糖,它的绝大部分功能,ES5 均可以作到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。//es5
function Fn(x, y) {
this.x = x;
this.y = y;
}
Fn.prototype.add = function () {
return this.x + this.y;
};
//等价于
//es6
class Fn{
constructor(x,y){
this.x = x;
this.y = y;
}
add(){
return this.x + this.y;
}
}
var F = new Fn(1, 2);
console.log(F.add()) //3
复制代码
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的全部方法都定义在类的prototype
属性上面。
class Fn {
constructor() {
// ...
}
add() {
// ...
}
sub() {
// ...
}
}
// 等同于
Fn.prototype = {
constructor() {},
add() {},
sub() {},
};
复制代码
类的内部全部定义的方法,都是不可枚举的(non-enumerable),这与es5不一样。
//es5
var Fn = function (x, y) {
// ...
};
Fn.prototype.add = function() {
// ...
};
Object.keys(Fn.prototype)
// ["add"]
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
//es6
class Fn {
constructor(x, y) {
// ...
}
add() {
// ...
}
}
Object.keys(Fn.prototype)
// []
Object.getOwnPropertyNames(Fn.prototype)
// ["constructor","add"]
复制代码
use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。 考虑到将来全部的代码,其实都是运行在模块之中,因此 ES6 实际上把整个语言升级到了严格模式。constructor
方法是类的默认方法,经过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,若是没有显式定义,一个空的constructor
方法会被默认添加。class Fn {
}
// 等同于
class Fn {
constructor() {}
}
复制代码
constructor
方法默认返回实例对象(即this
),彻底能够指定返回另一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
//constructor函数返回一个全新的对象,结果致使实例对象不是Foo类的实例。
复制代码
new
调用,不然会报错。这是它跟普通构造函数的一个主要区别,后者不用new
也能够执行。class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
复制代码
const MyClass = class Me {
getClassName() {
return Me.name;
}
};
复制代码
上面代码使用表达式定义了一个类。须要注意的是,这个类的名字是MyClass
而不是Me
,Me
只在 Class 的内部代码可用,指代当前类。
let inst = new MyClass();
inst.getClassName() // Me
Me.name // ReferenceError: Me is not defined
复制代码
若是类的内部没用到的话,能够省略Me
,也就是能够写成下面的形式。
const MyClass = class { /* ... */ };
复制代码
采用 Class 表达式,能够写出当即执行的 Class。
let Person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
Person.sayName(); // "张三"
复制代码
上面代码中,person
是一个当即执行的类的实例。 6. 私有方法和私有属性 私有方法/私有属性是常见需求,但 ES6 不提供,只能经过变通方法模拟实现。 一般是在命名上加以区别。
class Fn {
// 公有方法
foo () {
//....
}
// 伪装是私有方法(其实外部仍是能够访问)
_bar() {
//....
}
}
复制代码
static
关键字,就表示该方法不会被实例继承,而是直接经过类来调用,这就称为“静态方法”。 ES6 明确规定,Class 内部只有静态方法,没有静态属性。class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
//静态属性只能手动设置
class Foo {
}
Foo.prop = 1;
Foo.prop // 1
复制代码
class Fn{
constructor(){
this.arr = []
}
get bar(){
return this.arr;
}
set bar(value){
this.arr.push(value)
}
}
let obj = new Fn();
obj.menu = 1;
obj.menu = 2;
console.log(obj.menu)//[1,2]
console.log(obj.arr)//[1,2]
复制代码
class Fn {
}
class Fn2 extends Fn {
}
复制代码
子类必须在constructor
方法中调用super
方法,不然新建实例时会报错。这是由于子类没有本身的this
对象,而是继承父类的this
对象,而后对其进行加工。若是不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
// super()//必须调用
}
}
let cp = new ColorPoint(); // ReferenceError
复制代码
父类的静态方法也会被继承。 11. Object.getPrototypeOf() Object.getPrototypeOf
方法能够用来从子类上获取父类。
Object.getPrototypeOf(Fn2) === Fn
// true
复制代码
所以,可使用这个方法判断,一个类是否继承了另外一个类。 12. super关键字 super
这个关键字,既能够看成函数使用,也能够看成对象使用。在这两种状况下,它的用法彻底不一样。 第一种状况,super
做为函数调用时,表明父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。 做为函数时,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();
复制代码
上面代码中,子类B
当中的super.p()
,就是将super
看成一个对象使用。这时,super
在普通方法之中,指向A.prototype
,因此super.p()
就至关于A.prototype.p()
。 因为this
指向子类,因此若是经过super
对某个属性赋值,这时super
就是this
,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor() {
super();
this.x = 2;
super.x = 3;
console.log(super.x); // undefined
console.log(this.x); // 3
}
}
let b = new B();
复制代码
上面代码中,super.x
赋值为3
,这时等同于对this.x
赋值为3
。而当读取super.x
的时候,读的是A.prototype.x
,因此返回undefined
。
export
和import
。 export
命令用于规定模块的对外接口。 import
命令用于输入其余模块提供的功能。 一个模块就是一个独立的文件。该文件内部的全部变量,外部没法获取。若是你但愿外部可以读取模块内部的某个变量,就必须使用export
关键字输出该变量。 export输出变量的写法:// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
复制代码
还能够一块儿导出。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {firstName, lastName, year};
//跟上面写法等价,推荐这种写法。
复制代码
export
命令除了输出变量,还能够输出函数或类(class)。
export function multiply(x, y) {
return x * y;
};
复制代码
一般状况下,export
输出的变量就是原本的名字,可是可使用as
关键字重命名。
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
复制代码
export
命令规定的是对外的接口,必须与模块内部的变量创建一一对应关系。
// 报错
export 1;
// 报错
var m = 1;
export m;
//正确写法
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {m};
// 写法三
var n = 1;
export {n as m};
复制代码
一样的,function
和class
的输出,也必须遵照这样的写法。
// 报错
function f() {}
export f;
// 正确
export function f() {};
// 正确
function f() {}
export {f};
复制代码
export
语句输出的接口,与其对应的值是动态绑定关系,即经过该接口,能够取到模块内部实时的值。可是不建议这样作。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
复制代码
上面代码输出变量foo
,值为bar
,500 毫秒以后变成baz
。 export
命令能够出如今模块的任何位置,只要处于模块顶层就能够。若是处于块级做用域内,就会报错,下面的import
命令也是如此 2. import命令 使用export
命令定义了模块的对外接口之后,其余 JS 文件就能够经过import
命令加载这个模块。
// main.js
import {firstName, lastName, year} from './profile';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
复制代码
上面代码的import
命令,用于加载profile.js
文件,并从中输入变量。import
命令接受一对大括号,里面指定要从其余模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js
)对外接口的名称相同。 若是想为输入的变量从新取一个名字,import
命令要使用as
关键字,将输入的变量重命名。
import { lastName as surname } from './profile';
复制代码
import
后面的from
指定模块文件的位置,能够是相对路径,也能够是绝对路径,.js
后缀能够省略。 注意,import
命令具备提高效果,会提高到整个模块的头部,首先执行。
foo();
import { foo } from 'my_module';
//import的执行早于foo的调用。这种行为的本质是,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';
}
复制代码
import { foo } from 'my_module';
import { bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
复制代码
除了指定加载某个输出值,还可使用总体加载,即用星号(*
)指定一个对象,全部输出值都加载在这个对象上面。 注意,模块总体加载所在的那个对象,不容许运行时改变。下面的写法都是不容许的。
import * as circle from './circle';
// 下面两行都是不容许的
circle.foo = 'hello';
circle.area = function () {};
复制代码
import
命令的时候,用户须要知道所要加载的变量名或函数名,不然没法加载。 为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default
命令,为模块指定默认输出。// export-default.js
export default function () {
console.log('foo');
}
复制代码
其余模块加载该模块时,import
命令能够为该匿名函数指定任意名字。
// 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
,在模块外部是无效的。加载的时候,视同匿名函数加载。 比较一下默认输出和正常输出。
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
复制代码
上面代码的两组写法,第一组是使用export default
时,对应的import
语句不须要使用大括号;第二组是不使用export default
时,对应的import
语句须要使用大括号。 export default
命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,所以export default
命令只能使用一次。因此,import命令后面才不用加大括号,由于只可能惟一对应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 a
的含义是将变量a
的值赋给变量default
。因此,最后一种写法会报错。 一样地,由于export default
命令的本质是将后面的值,赋给default
变量,因此能够直接将一个值写在export default
以后。
// 正确
export default 42;
// 报错
export 42;
复制代码
import
语句能够与export
语句写在一块儿。export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, bar };
复制代码
模块的接口更名和总体输出,也能够采用这种写法。
// 接口更名
export { foo as myFoo } from 'my_module';
// 总体输出
export * from 'my_module';
复制代码