由于受新型冠状病毒的影响,假期又延长了,又不能出门,只好在家认真学习了,就借此机会阅读完阮一峰老师的ECMAScript 6入门教程,顺便将一些es6中常用的特性进行一个整理。相当于读书时代的划重点
1:let和const
用来声明变量,用法类似于var,但所声明的变量只能在变量所在的代码块内有效。
相同点:
都是用于块级作用域,不存在变量提升,一定要在声明后才可以使用,在声明前使用会报错。
不同点:
let定义的变量可以修改值。const定义的变量为常量,赋值后不能在修改。
2:变量解构赋值
解构是指按照一定模式,从数组和对象中提取值,对变量进行赋值。
1.数组解构
let [a, b, c] = [1, 2, 3];
相当于
let a = 1;
let b = 2;
let c = 3;
2.对象解构
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
3.解构用途
交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
提取JSON数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
输入模块的指定方法
const { SourceMapConsumer, SourceNode } = require("source-map");
3.字符串扩展
1.使用for...of遍历字符串。
var str = 'abcd';
for (let value of str){
console.log(value);
}
2.模板字符串
模板字符串是增强版的字符串,使用`来表示字符串的使用,也可以用来定义多行字符串,或者在字符串中使用变量。
//普通字符串
let a = `this is str`;
console.log(a);
//多行字符串
let glsl = `void main(){
var a;
}`
console.log(glsl);
//包含变量的字符串
let x = 1,y = 2;
let b = `x:${x}\n y:${y}`
console.log(b);
3.includes(), startsWith(), endsWith()
之前判断一个字符串中是否包含某个字符串时,使用的是indexOf方法,在es6中新增了三个方法进行判断字符串中是否包含指定的字符。
includes():判断字符串中是否包含某个字符串。
startsWith():判断字符串是否以某个字符串开头。
endsWith():判断字符串是否以某个字符串结尾。
var string = `这是一个字符串。`
console.log(string.includes('字'));//true
console.log(string.startsWith('这'));//true
console.log(string.endsWith('。'));//true
4.repeat(n)
表示将原字符串复制n次,返回一个新的字符串。
console.log(string.repeat(3));
//这是一个字符串。这是一个字符串。这是一个字符串。
5.padStart(),padEnd()
根据指定的参数在开头或者结尾补全字符串。如:
console.log('x'.padStart(5, 'ab')) // 'ababx'
console.log('x'.padStart(4, 'ab')) // 'abax'
console.log('x'.padEnd(5, 'ab')) // 'xabab'
console.log('x'.padEnd(4, 'ab')) // 'xaba'
6.trimStart(),trimEnd()
trim()用于消除前后的空格,trimStart()用于消除字符串开头的空格。trimEnd()用于消除字符串结尾的空格。
4.数值的扩展
1.Number.isFinite(), Number.isNaN()
isFinite用于判断一个数值是否是有限的(即不是Infinity)。如果参数类型不是数值,Number.isFinite一律返回false。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
Number.isNaN()用来检查一个值是否为NaN。如果参数类型不是NaN,Number.isNaN一律返回false。
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
与全局的
isFinite()和isNaN()有什么区别?
全局的isFinite()和isNaN()会先调用Number()将非数值转成数值。而这两个新方法只对数值有效,Number.isFinite()对于非数值一律返回false,Number.isNaN()只有对于NaN才返回true,非NaN一律返回false。
1.Number.isInteger()
使用Number.isInteger()可以判断变量是否为整数类型。
console.log(Number.isInteger(12.5));//false
console.log(Number.isInteger(12));//true
2.Math.trunc()
Math.trunc()去除小数部分,返回整数部分。
console.log(Math.trunc(12.33));//12
5.函数的扩展
1.函数参数的默认值
在定义函数的时候,可以在函数中使用默认值,当调用函数时,传递的变量值为空时,如果有默认值,使用变量的时候将使用默认值,通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。如下所示:
function init(a,b,c = 10){
console.log(a);//12
console.log(b);//1
console.log(c);//10
}
init(12,1);
2.箭头函数
ES6 允许使用“箭头”(=>)定义函数。如下两个函数是一样的。
var fun = function(a,b){
console.log(a+b);
}
var fun1 = (a,b) => {
console.log(a+b);
}
fun(1,2);
fun1(1,2);
箭头函数注意点
- 函数体内的
this对象,是定义时所在的对象,不是使用是所在的对象。- 不可当作构造函数来使用,也就是不能使用
new来创建对象。- 不可使用
arguments对象,如果要使用类似的功能,可以使用rest- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
关于第一点函数体内的this对象,是定义时所在的对象,示例如下所示:
function foo() {
setTimeout(() => {
console.log('id:', this.id);//42
}, 100);
setTimeout(function(){
console.log('id:', this.id);//21
},200)
}
var id = 21;
foo.call({ id: 42 });
不适用场合
- 第一个场合是定义对象的方法,且该方法内部包括this。
- 第二个场合是需要动态this的时候,也不应使用箭头函数。
3.尾调用
尾调用就是指某个函数的最后一步是调用另一个函数。如下所示:
function f(x) {
if (x > 0) {
return m(x)
}
return n(x);
}
上面代码中,函数m和n都属于尾调用,因为它们都是函数f的最后一步操作。
6.数组扩展
1.扩展运算符
扩展运算符是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
扩展运算符的作用
- 替代函数的 apply 方法
由于扩展运算符可以展开数组,所以不再需要apply方法,将数组转为函数的参数了。
function fun(a,b,c){
console.log(a+b+c);
}
fun.apply(null,[1,2,3]);
fun(...[1,2,3])
- 复制数组
var a = [1,2,3];
var b = [...a];
b[1]= 0;
console.log(a);//1,2,3
console.log(b);//1,0,3
- 合并数组
var a = [1,2,3];
var b = [...a];
b[1]= 0;
console.log(a);//1,2,3
console.log(b);//1,0,3
var c = [...a,...b];
console.log(c);
2.Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括 ES6 新增的数据结构 Set 和 Map)。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
3.Array.of()
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
7.对象的扩展
1.属性的可枚举性和遍历
对象的每个属性都有一个描述对象(Descriptor),用来控制该属性的行为。Object.getOwnPropertyDescriptor方法可以获取该属性的描述对象。述对象的enumerable属性,称为“可枚举性”,如果该属性为false,就表示某些操作会忽略当前属性。
目前,有四个操作会忽略enumerable为false的属性。
for...in循环:只遍历对象自身的和继承的可枚举的属性。Object.keys():返回对象自身的所有可枚举的属性的键名。JSON.stringify():只串行化对象自身的可枚举的属性。Object.assign(): 忽略enumerable为false的属性,只拷贝对象自身的可枚举的属性。
2.属性遍历
对象的属性遍历一共有5种方法。
1.for...in
for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
var obj = {
a:'a1',
b:'b1',
c:12
}
ergodic();
function ergodic(){
for(let key in obj){
console.log(key + '=' + obj[key]);
}
}
2.Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
var obj = {
a:'a1',
b:'b1',
c:12
}
ergodic();
function ergodic(){
var array = Object.keys(obj);
array.forEach(key => {
console.log(key + '=' + obj[key]);
})
}
3.Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
var obj = {
a:'a1',
b:'b1',
c:12
}
ergodic();
function ergodic(){
var array = Object.getOwnPropertyNames(obj);
array.forEach(key => {
console.log(key + '=' + obj[key]);
})
}
4.Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
5.Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
var obj = {
a:'a1',
b:'b1',
c:12
}
ergodic();
function ergodic(){
var array = Reflect.ownKeys(obj);
array.forEach(key => {
console.log(key + '=' + obj[key]);
})
}
3.扩展运算符
对象的扩展运算符(...)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。对象的扩展运算符等同于使用Object.assign()方法。
var obj = {
a:'a1',
b:'b1',
c:12,
}
var obj1 = {...obj};
console.log(obj1);
4.Null 判断运算符
ES2020引入了一个新的Null判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
5.Object.is()
用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。但与===不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
6.Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
注意点
- Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
-一旦遇到同名属性,Object.assign的处理方法是替换,而不是添加。
8.Symbol
Symbol是一种新的数据类型,属性名使用Symbol类型是独一无二的,不会跟其它的属性名发生冲突。Symbol是一个原始类型的值,不是对象,不能添加给它添加属性。Symbol可以接受一个字符串作为参数,如果参数是一个对象,会先调用它的toString方法转成字符,再生成一个Symbol值。Symbol函数的参数只是当前Symbol值的一个描述,因些两个参数相同的Symbol是不相等的。Symbol值不能与其它类型的值进行运算。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2 // false
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值可以转化为字符串和布尔类型的值。
let sym = Symbol('My symbol');
String(sym) // 'Symbol(My symbol)'
sym.toString() // 'Symbol(My symbol)'
let sym = Symbol();
Boolean(sym) // true
1.Symbol.for()
Symbol.for()可以接受一个字符串作为参数,然后生成一个Symbol值,如果参数相同,生成的Symbol值也相等。
console.log(Symbol.for('s') === Symbol.for('s'));//true
console.log(Symbol('s') === Symbol('s'));//false
通过Symbol.for()创建的Symbol值可以使用Symbol.keyFor()获取参数名。
let s3 = Symbol.for('s3');
console.log(Symbol.keyFor(s3));//s3
let s4 = Symbol('s4');
console.log(Symbol.keyFor(s4));//undefined
2.Symbol.hasInstance
对象的Symbol.hasInstance属性指向一个内部方法,当其它对象在使用instanceof时,会调用这个方法。
const Even = {
[Symbol.hasInstance](obj){
return Number(obj) % 2 === 0;
}
}
console.log(1 instanceof Even) // false
console.log(2 instanceof Even) // true
3.Symbol.match
对象的Symbol.match属性指向一个函数,当执行str.match(myObject)时,如果该属性存在时,将返回该方法的返回值。
const MyMatcher = {
[Symbol.match](str){
return 'haha'
}
}
console.log('test'.match(MyMatcher));//haha
9.Set 和 Map 数据结构
1.Set
Set类似于数组,但成员的值是唯一的,没有重复的值。
Set常用方法
Set.prototype.constructor:造函数,默认就是Set函数。Set.prototype.size:返回成员的数量。Set.prototype.add(value):往Set中添加成员。Set.prototype.delete(value):删除指定的成员。Set.prototype.has(value):判断是否包含value。Set.prototype.clear():清空所有成员。Set.prototype.keys():返回键名的遍历器Set.prototype.values():返回键值的遍历器Set.prototype.entries():返回键值对的遍历器Set.prototype.forEach():使用回调函数遍历每个成员
2.WeakSet
WeakSet和Set类似,成员的值是唯一的,没有重复的值。但WeakSet只能存储对象。并且WeakSet中存储的是弱引用,当其它对象不再引用该对象时,垃圾回收器将自动回收该对象的内存。WeakSet适合临时存放一组对象,以及存放跟对象绑定的信息。只要这些对象在外部消失,它在 WeakSet 里面的引用就会自动消失。WeakSet不能被遍历。
常用方法
WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。
3.Map
标准对象的键值是字符串或者Symbol,不能使用对象作为键,如果使用对象作为键,将自动转成字符串。如果需要以对象作为键,将会用到Map。如果对同一个键多次赋值,后面的值将覆盖前面的值。
var map = new Map();
var obj = {a:'a'};
map.set(obj,'这是对象作为key');
map.set('key','haha');
map.set('key','haha1');
console.log(map.get('key'));//haha1
console.log(map.get(obj));//这是对象作为key
常用方法
Map.prototype.size():返回Map的长度。Map.prototype.set(key, value):set方法设置键名key对应的键值为value,然后返回整个Map结构。如果key已经有值,则键值会被更新,否则就新生成该键。Map.prototype.get(key):get方法读取key对应的键值,如果找不到key,返回undefined。Map.prototype.has(key):has方法返回一个布尔值,表示某个键是否在当前Map对象之中。Map.prototype.delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。Map.prototype.clear():清空所有值。Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员。
WeakMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
WeakMap与Map的区别
- 首先,WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
- 其次,WeakMap的键名所指向的对象,不计入垃圾回收机制。一旦不再需要时,我们就必须手动删除引用。
10.Proxy
Proxy是一种代理,在目标对象之前设置一层‘拦截’,在访问该对象时,都要先访问这个代理对象,通过这个代理对象可以进行过滤和修改等操作。
var proxy = new Proxy(target, handler);
new Proxy生成一个Proxy实例,target表示需要拦截的目标对象,handler用于定制拦截行为。如下所示为一个拦截get方法的代码。
var o = new Proxy({},{
get:function(target,key){
return 'haha'
}
})
console.log(o.name);//haha
console.log(o['type']);//haha
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
Proxy 可拦截的13个方法
get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo和proxy['foo']。set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。apply(target, object, args):拦截Proxy实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。construct(target, args):拦截Proxy实例作为构造函数调用的操作,比如new proxy(...args)。
11.Reflect
Reflect跟Proxy一样都可以操作对象。使用Reflect可以拿到对象原始的方法。
// 老写法
'assign' in Object // true
// 新写法
Reflect.has(Object, 'assign') // true
静态方法
Reflect.apply(target, thisArg, args)Reflect.construct(target, args)Reflect.get(target, name, receiver)Reflect.set(target, name, value, receiver)Reflect.defineProperty(target, name, desc)Reflect.deleteProperty(target, name)Reflect.has(target, name)Reflect.ownKeys(target)Reflect.isExtensible(target)Reflect.preventExtensions(target)Reflect.getOwnPropertyDescriptor(target, name)Reflect.getPrototypeOf(target)Reflect.setPrototypeOf(target, prototype)
12.Promise
Promise是一种异步编程的解决方案,相对传统的回调函数和事件要强大。Promise是一个对象,它可以获取异步操作的消息。
Promise特点
Promise的状态不受外界的影响。Promise对象代表一个操作,它包含三种状态:pending(进行中),fulfilled(已成功)和rejected(已失败)。只有异步操作的结果才能决定是哪种状态,其它操作无法改变Promise的状态。- 一旦状态改变,就不会再变, 任何时候都可以获取到这个结果。
Promise的状态变化只有两种,从
pending(进行中)变成fulfilled(已成功),或者从pending(进行中)变成rejected(已失败)。
Promise缺点Promise创建后会立即执行,无法中途取消。- 如果不设置回调函数,
Promise内部发生错误时无法反应到外部。- 当处于
pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
1.基本用法
Promise对象是一个构造函数,通过构造函数来创建Promise实例。如下所示:
let promise = new Promise(function(resolve,reject){
setTimeout(resolve,500);
});
promise.then(function(value){
console.log('promise执行结束')
},function(error){
console.log('promise执行时发生了错误')
})
Promise构造函数接受一个函数作为参数,函数包括resolve和reject两个函数,resolve的作用是将Promise对象的状态从pending(进行中)变成fulfilled(已成功)。在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
注意
因为立即 resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。,如下所示:
let p = new Promise(function(resolve,reject){
console.log(1);
resolve(2);
console.log(3);
})
p.then(function(value){
console.log(value);
})
//1
//3
//2
resolve函数的参数除了正常的值以外,还可能是另一个 Promise实例,比如像下面这样。
let p1 = new Promise(function(resolve,reject){
setTimeout(() => {
reject('执行错误')
}, 3000);
})
let p2 = new Promise(function(resolve,reject){
setTimeout(() => {
resolve(p1);
}, 1500);
})
p2.then(function(value){
console.log(value)
}).catch(error =>{
console.log(error)
})
2.Promise.prototype.then()
then方法是Promise原型对象上的一个方法,该方法的作用是Promise在发生状态改变后的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法将返回一个Promise对象,因此可以采用链式写法。如下所示:
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
p3.then(function(value){
console.log(value);
return new Promise(function(resolve,reject){
setTimeout(() => {
resolve('p3返回的Promise');
}, 1000);
})
}).then(function(value){
console.log(value)
})
//p3
//p3返回的Promise'
3.Promise.prototype.catch()
Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。如下所示:
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
p3.then(function(value){
console.log(value);
return new Promise(function(resolve,reject){
setTimeout(() => {
reject(new Error('p3的Promise返回错误信息'));
}, 1000);
})
}).then(function(value){
console.log('result'+value)
}).catch(function(error){
console.log(error);
})
//p3
//p3的Promise返回错误信息'
如果Promise的状态已经变成了resolved,再抛出异常是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。如下所示:
const promise = new Promise(function(resolve, reject) {
resolve('ok');
throw new Error('test');
});
promise.then(function(value) {
console.log(value)
})
.catch(function(error) {
console.log(error)
});
Promise对象的错误具有‘冒泡’性质,它会一直向后传递,直到捕获为止。错误总是被下一个catch捕获,一般来说,不要在then方法里面定义 Reject状态的回调函数(即then的第二个参数),总是使用catch方法。如下所示:
const promise = new Promise(function(resolve, reject) {
reject('发生了错误');
});
promise.then(function(value) {
console.log("success-"+value)
})
.catch(function(error) {
console.log("error="+error)
});
//error=发生了错误
4.Promise.prototype.finally()
finally方法是不论Promise的状态是什么样的,都会执行finally方法,如下所示:
const promise = new Promise(function(resolve, reject) {
reject('发生了错误');
});
promise.then(function(value) {
console.log("success-"+value)
})
.catch(function(error) {
console.log("error="+error)
}).finally(function(value){
console.log('finally');
});
//error=发生了错误
//finally
finally方法不接受任何参数。finally方法总是会返回原来的值。
5.Promise.all()
Promise.all()将多个Promise包装成一个Promise实例。
let p1 = new Promise(function(resolve,reject){
resolve('p1');
})
let p2 = new Promise(function(resolve,reject){
resolve('p2');
})
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
let p= Promise.all([p1,p2,p3]).then(function(value){
console.log('success:' + value)
}).catch(function(error){
console.log('fail='+error);
})
p的状态由p1、p2、p3决定,分成两种情况。
- 只有
p1、p2、p3的状态全为fulfilled时,p的状态才为fulfilled。此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。- 只要
p1、p2、p3中有一个状态为rejected时,p的状态就为rejected。此时第一个被reject的实例的返回值,会传递给p的回调函数。
注意,如果作为参数的 Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()的catch方法。如下所示:
let p1 = new Promise(function(resolve,reject){
resolve('p1');
})
let p2 = new Promise(function(resolve,reject){
reject('p2');
}).catch(function(error){
})
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
let promise = Promise.all([p1,p2,p3]).then(function(value){
console.log('success:' + value)
}).catch(function(error){
console.log('fail='+error);
})
6.Promise.race()
Promise.race()和Promise.all()类似,都可以将多个Promise包装成一个Promise实例。 不同之处是只要有一个实例率先改变状态,Promise.race()实例的结果也将改变状态,如下所示:
let p1 = new Promise(function(resolve,reject){
resolve('p1');
})
let p2 = new Promise(function(resolve,reject){
reject('p2');
}).catch(function(error){
})
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
let promise = Promise.race([p1,p2,p3]).then(function(value){
console.log('success:' + value)
}).catch(function(error){
console.log('fail='+error);
})
//success:p1
7.Promise.allSettled()
Promise.allSettled()也是接受一组 Promise 实例作为参数,包装成一个新的 Promise实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020。如下所示:
let p1 = new Promise(function(resolve,reject){
resolve('p1');
})
let p2 = new Promise(function(resolve,reject){
reject('p2');
})
let p3 = new Promise(function(resolve,reject){
resolve('p3');
})
let promise = Promise.allSettled([p1,p2,p3]).then(function(value){
debugger
console.log('success:' + value)
}).catch(function(error){
console.log('fail='+error);
})
该方法返回的新的 Promise 实例,一旦结束,状态总是fulfilled,不会变成rejected。状态变成fulfilled后,Promise 的监听函数接收到的参数是一个数组,每个成员对应一个传入Promise.allSettled()的 Promise实例。
8.Promise.resolve()
Promise.resolve()将现有对象转化为Promise对象。如下所示:
const jsPromise = Promise.resolve($.ajax('/whatever.json'));
Promise.resolve方法的参数分成四种情况。
- 参数是一个
Promise实例,如果参数是Promise实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。- 参数是一个
thenable对象。thenable对象指的是具有then方法的对象,Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。- 参数不是具有then方法的对象,或根本就不是对象。如果参数是一个原始值,或者是一个不具有
then方法的对象,则Promise.resolve方法返回一个新的Promise对象,状态为resolved。- 不带有任何参数。
Promise.resolve()方法允许调用时不带参数,直接返回一个resolved状态的Promise对象。
9.Promise.reject()
Promise.reject()返回一个新的Promise对象,该对象的状态为rejected。
13.Iterator 和 for...of 循环
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据类型只要部署了Iterator接口,就能完成遍历操作。
Iterator作用
- 为各种数据类型提供一个统一的,简便的访问接口。
- 使用数据结构的成员按照某种次序进行排列。
- 使用
for...of进行遍历。
1.默认的Iterator 接口
凡是部署了Symbol.iterator属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备 Iterator 接口的数据结构如下。
- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
如下所示:
var array = [1,2,3,4];
var iterator = array[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
14.Generator
Generator是一种异步编程的解决方案,语法行为与传统的函数不同。Generator是一种状态机函数,封装了多个内部状态。执行Generator函数会返回一个遍历器对象,可依次遍历Generator函数内部的每一个状态。
Generator 特征
function与函数名之间有一个*。如function* test(){}。- 函数内部可以使用
yield表达式,定义不同的内部状态。
function* helloGenerator(){
yield 'hello';
yield 'Generator';
return 'end'
}
var it = helloGenerator();
for(var item of it){
console.log(item);
}
Generator函数的调用跟调用普通函数一样,直接在函数名后面加一个括号,但与普通函数不一样的是,Generator函数执行后返回的是一个Iterator对象,可以调用next方法,或者使用for...of进行遍历。
1.yield 表达式
由于Generator函数返回的是一个Iterator对象,只有在执行next方法时才会遍历下一个状态,通过使用yield表达式可以设置一种暂停标注。当执行完next方法后,将yield后面的值作为value返回,执行完一条后并暂停,只有当调用下一个next方法才会往下执行并继续查找yield表达式,当查找不到yield时,将返回并结束遍历。yield只能在Generator函数中使用,在其它地方使用时将报错。
2.Generator.prototype.throw()
Generator函数返回的对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。
3.yield* 表达式
使用yield*表达式,用来在一个 Generator 函数里面执行另一个 Generator 函数。
function* foo() {
yield 'a';
yield 'b';
}
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"
4.Generator用途
-
异步操作的同步化表达
通过使用Generator可以处理异步函数,用来代替回调函数。将异步操作写在yield表达式上,等调用next方法后再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield表达式下面,反正要等到调用next方法时再执行。如下所示:
function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()
-
控制流程管理
利用for...of循环会自动依次执行yield命令的特性,提供一种更一般的控制流管理的方法。如下所示:
let steps = [step1Func, step2Func, step3Func];
function* iterateSteps(steps){
for (var i=0; i< steps.length; i++){
var step = steps[i];
yield step();
}
}
-
部署 Iterator 接口
利用Generator函数,可以在任意对象上部署Iterator接口。如下所示:
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);
}
// foo 3
// bar 7
-
作为数据结构
Generator可以看作是数据结构,更确切地说,可以看作是一个数组结构,因为Generator函数可以返回一系列的值,这意味着它可以对任意表达式,提供类似数组的接口。如下所示:
function* doStuff() {
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'world.txt');
yield fs.readFile.bind(null, 'and-such.txt');
}
for (task of doStuff()) {
// task是一个函数,可以像回调函数那样使用它
}
5.Generator 函数的异步应用
异步是指一个任务不是连续完成的,可以理解成该任务被分成为两段,先执行第一段,然后再执行其它的任务,等做好准备后再执行第二段的任务。
比如,有一个任务是读取文件进行处理,任务的第一段是向操作系统发出请求,要求读取文件。然后,程序执行其他任务,等到操作系统返回文件,再接着执行任务的第二段(处理文件)。这种不连续的执行,就叫做异步。
6.async 函数
async函数是Generator的方法糖。async就是将Generator函数的*换成了async,将yield换成了await。async函数返回一个Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
let readFile = function(){
return new Promise(function(resolve,reject){
setTimeout(() => {
resolve('文件读取成功')
}, 2000);
})
}
let asyncReadFile = async function(){
let rf1 = await readFile()
let rf2 = await readFile();
console.log(rf1.toString());
console.log(rf2.toString());
}
asyncReadFile();
7.await命令
await命令后面是一个Promise对象,如果是其它值,将直接返回对应的值。如下所示:
async function f() {
// 等同于
// return 123;
return await 123;
}
f().then(v => console.log(v))
// 123
await命令后面的Promise对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。如下所示:
async function f() {
await Promise.reject('出错了');
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了
任何一个await语句后面的Promise 对象变为reject状态,那么整个async函数都会中断执行。
async function f() {
await Promise.reject('出错了');
await Promise.resolve('hello world'); // 不会执行
}
使用try...catch结构,实现多次重复尝试。
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
上面代码中,如果await操作成功,就会使用break语句退出循环;如果失败,会被catch语句捕捉,然后进入下一轮循环。
使用注意点
await命令后面的Promise对象,运行结果可能是rejected,最好将await命令放在try...catch中。- 多个
await命令如果不存在先后顺序,最好同时时行。await命令只能在async中使用,在其它地方使用将会报错。
15.
async函数可以保留运行堆栈。
8.async函数实现原理
async的实现原理就是将Genernator函数与自动执行器包装在一个函数里。如下所示:
async function fn(args) {
// ...
}
// 等同于
function fn(args) {
return spawn(function* () {
// ...
});
}
所有的async函数都可以写成上面的第二种形式,其中的spawn函数就是自动执行器。
16.Class
ES6可以使用class关键字创建类,相比使用构造函数创建类更具有面向对象的思想,如下所示:
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
console.log("x="+this.x + " y="+this.y);
}
}
let point = new Point(12,13);
point.toString();
类的内部所有定义的方法,都是不可枚举的(non-enumerable)。如下所示:
class Point{
constructor(x,y){
this.x = x;
this.y = y;
}
toString(){
console.log("x="+this.x + " y="+this.y);
}
}
let point = new Point(12,13);
point.toString();
console.log(Object.keys(Point.prototype));//[]
console.log(Object.keys(point));// ["x", "y"]
1.取值函数(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'
2.Class表达式
函数一样,类也可以使用表达式的形式定义。如下所示:
const Circle = class C{
getRaidus(){
console.log('圆的半径为10')
}
}
const circle = new Circle();
circle.getRaidus();
3.静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
4.实例属性
Class定义的类中,添加属性可以在构造函数中添加,也可以在类的顶层定义属性,如下所示:
const Circle = class C{
raidus = 20;
constructor(){
this.x = 10;
this.y = 10;
}
getRaidus(){
console.log(`x=${this.x};x=${this.y};radius=${this.raidus};`)
}
}
const circle = new Circle();
circle.getRaidus();
5.静态属性
静态属性指的是 Class 本身的属性,即Class.propName,而不是定义在实例对象(this)上的属性。
// 老写法
class Foo {
// ...
}
Foo.prop = 1;
// 新写法
class Foo {
static prop = 1;
}
6.私有属性
为class加了私有属性。方法是在属性名之前,使用#表示。如下所示:
class Foo {
#privateValue = 42;
getPrivateValue() {
return this.#privateValue;
}
}
var foo = new Foo();
console.log(foo.getPrivateValue())
7.继承
Class可以通过extends关键字实现继承,这比 ES5的通过修改原型链实现继承,要清晰和方便很多。如下所示:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。
8.super
super既可以当作函数来使用,也可以使用对象来使用。
super作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次super函数。
class A {}
class B extends A {
constructor() {
super();
}
}
super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
super指向父类的原型对象,所以定义在父类实例上的属性和方法,无法通过super来调用的。
9.Object.getPrototypeOf()
Object.getPrototypeOf可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint) === Point
// true
可以使用这个方法判断,一个类是否继承了另一个类。
10.类的 prototype 属性和proto属性
Class创建的类,同样有prototype和__proto__属性,同样存在两条继承链。
- 子类的
__proto__属性,表示构造函数的继承,总是指向父类。。- 子类
prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
console.log(B.__proto__ === A);//true
console.log(B.prototype.__proto__ === A.prototype);//true
11.实例的 proto 属性
子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性,也就是说子类原型的原型是父类的原型。
class A {
}
class B extends A {
}
let a = new A();
let b = new B();
console.log(b.__proto__.__proto__ === a.__proto__);//true
17.Module
ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
严格模式
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with语句- 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop,会报错,只能删除属性delete global[prop]eval不会在它的外层作用域引入变量eval和arguments不能被重新赋值arguments不会自动反映函数参数的变化- 不能使用
arguments.callee- 不能使用
arguments.caller- 禁止
this指向全局对象- 不能使用
fn.caller和fn.arguments获取函数调用的堆栈- 增加了保留字(比如
protected、static和interface)
1.export 命令
模块主要由两个命令组成,export和import命令。export用于向外部导出接口。import用于引入外部导出的接口。一个单独的文件就是一个模块,模块类的变量和函数在外部无法获取,如果希望外部能够访问,需要使用export输入变量。export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。
export const url = 'http://www.gzcopright.cn';
function test (){
};
function util (){
}
export {test,util as Util};
2.import命令
使用export导出的变量,在其它JS文件中使用import可以加载模块。import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。import命令具有提升效果,会提升到整个模块的头部,首先执行,这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。
import{url,Util,log} from './module.js'
function test(){
console.log(url)
}
test();
如果多次执行import语句,只会执行一次,如下所示:
import 'lodash';
import 'lodash';
3.模块的整体加载
除了指定加载某个输出值,可以使用*来加载所有输出的变量,如下所示:
import * as obj from './module.js'
function test(){
console.log(obj.url)
}
test();
4.export default 命令
export default用于指定模块的默认输出,一个模块只能有一个默认输出。export default输出的命令在使用import时,不需要使用{},并且名称可以随便取。如下所示:
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
5.export 与 import 的复合写法
如果在同一模块中,先输入后输出同一个模块,import和export可以写在一起。
export { foo, bar } from 'my_module';
// 可以简单理解为
import { foo, bar } from 'my_module';
export { foo, bar };
6.模块的继承
模块之间是可以继承的,如下所示,circleplus模块,继承了circle模块。
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
上面代码中的export *,表示再输出circle模块的所有属性和方法。
7.import()
使用import()可以动态加载模块,使用方法跟import一样,唯一的区别就是import需要放在代码的顶层,不支持动态导入。
适用场合
- 按需加载,
import()可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
- 条件加载。
import()可以放在if代码块,根据不同的情况,加载不同的模块。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
- 动态的模块路径。
import()允许模块路径动态生成。
import(f())
.then(...);
上面代码中,根据函数f的返回结果,加载不同的模块。
18.Module 的加载实现
加载js文件,使用的是<script>,默认是同步加载,既浏览器引擎解析dom时,如果碰到了script标签时会先加载js脚本。当js文件太大时,会阻塞线程出现卡死的情况,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>
defer和async都支持异步加载脚本。但两者有一定的区别
defer加载的脚本需要等面布渲染完成后再运行,如果有多个script使用defer时,每个文件是按顺序执行了。async是加载完后就执行,如果有多个script使用async时,顺序是不固定的。
浏览器加载ES6模块,也使用<script>标签,但是要加入type="module"属性。浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。
1.ES6 模块与 CommonJS 模块的差异
ES6模块输出的是值的引用,而CommonJS模块输出的是值拷贝。ES6模块是编译时加载接口,而CommonJS模块是运行时加载。
19.编程风格
let取代var,常量使用const- 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
- 使用数组成员对变量赋值时,函数的参数如果是对象的成员,如果函数返回多个值,优先使用解构赋值。
- 单行定义的对象,最后一个成员不要以逗号结尾,多行定义的对象,最后一个成员以逗号结尾。对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用
Object.assign方法。- 使用扩展运算符
(...)拷贝数组。使用Array.from方法,将类似数组的对象转为数组。- 立即执行函数可以写成箭头函数的形式。那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了
this。- 注意区分
Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制。- 总是用
Class,取代需要prototype的操作。因为Class的写法更简洁,更易于理解。- 使用
import取代require。使用export取代module.exports。









网友评论