本文共 78689 字,大约阅读时间需要 262 分钟。
现在的网络上已经有各样关于 ECMAScript 规范介绍和分析的文章,而我自己重新学习一遍这些规范,整理出这么一份笔记,比较精简,主要内容涵盖ES6、ES7、ES8、ES9,后续会增加面试题,框架入门等笔记,欢迎吐槽交流。
这份资料的ES6部分将会参考阮一峰老师的 ,精简和整理出快速实用的内容。 另外ES7/ES8/ES9则会从网络综合参考和整理。ES全称ECMAScript:
目前JavaScript使用的ECMAScript版本为。ECMAScript版本 | 发布时间 | 新增特性 |
---|---|---|
ECMAScript 2009(ES5) | 2009年11月 | 扩展了Object、Array、Function的功能等 |
ECMAScript 2015(ES6) | 2015年6月 | 类,模块化,箭头函数,函数参数默认值等 |
ECMAScript 2016(ES7) | 2016年3月 | includes,指数操作符 |
ECMAScript 2017(ES8) | 2017年6月 | async/await,Object.values(),Object.entries(),String padding等 |
本文博客
本文开源地址 个人博客
在ES6中,我们通常实用 let
表示变量,const
表示常量,并且 let
和 const
都是块级作用域,且在当前作用域有效不能重复声明。
1.1.1 let 命令
let
命令的用法和 var
相似,但是 let
只在所在代码块内有效。
{ let a = 1; let b = 2;}复制代码
并且 let
有以下特点:
var
声明一个变量一个函数,都会伴随着变量提升的问题,导致实际开发过程经常出现一些逻辑上的疑惑,按照一般思维习惯,变量都是需要先声明后使用。// var console.log(v1); // undefinedvar v1 = 2;// 由于变量提升 代码实际如下var v1;console.log(v1)v1 = 2;// let console.log(v2); // ReferenceErrorlet v2 = 2;复制代码
let
和 const
在相同作用域下,都不能重复声明同一变量,并且不能在函数内重新声明参数。// 1. 不能重复声明同一变量// 报错function f1 (){ let a = 1; var a = 2;}// 报错function f2 (){ let a = 1; let a = 2;}// 2. 不能在函数内重新声明参数// 报错function f3 (a1){ let a1; }// 不报错function f4 (a2){ { let a2 }}复制代码
1.1.2 const 命令
const
声明一个只读的常量。
const PI = 3.1415926;console.log(PI); // 3.1415926复制代码
注意点:
const
声明后,无法修改值;const PI = 3.1415926;PI = 3; // TypeError: Assignment to constant variable.复制代码
const
声明时,必须赋值;const a ; // SyntaxError: Missing initializer in const declaration.复制代码
const
声明的常量,let
不能重复声明;const PI = 3.1415926;let PI = 0; // Uncaught SyntaxError: Identifier 'PI' has already been declared复制代码
解构赋值概念:在ES6中,直接从数组和对象中取值,按照对应位置,赋值给变量的操作。
1.2.1 数组
基础用法:
// ES6 之前let a = 1;let b = 2;// ES6 之后let [a, b] = [1, 2];复制代码
本质上,只要等号两边模式一致,左边变量即可获取右边对应位置的值,更多用法:
let [a, [[b], c]] = [1, [[2], 3]];console.log(a, b, c); // 1, 2, 3let [ , , c] = [1, 2, 3];console.log(c); // 3let [a, , c] = [1, 2, 3];console.log(a,c); // 1, 3let [a, ...b] = [1, 2, 3];console.log(a,b); // 1, [2,3]let [a, b, ..c.] = [1];console.log(a, b, c); // 1, undefined, []复制代码
注意点:
undefined
。let [a] = []; // a => undefinedlet [a, b] = [1]; // a => 1 , b => undefined复制代码
let [a, b] = [1, 2, 3];console.log(a, b); // 1, 2复制代码
let [a] = 1;let [a] = false;let [a] = NaN;let [a] = undefined;let [a] = null;let [a] = {};复制代码
指定解构的默认值:
基础用法:let [a = 1] = []; // a => 1let [a, b = 2] = [a]; // a => 1 , b => 2复制代码
特殊情况:
let [a = 1] = [undefined]; // a => 1let [a = 1] = [null]; // a => null复制代码
右边模式对应的值,必须严格等于undefined
,默认值才能生效,而null
不严格等于undefined
。
1.2.2 对象的解构赋值
与数组解构不同的是,对象解构不需要严格按照顺序取值,而只要按照变量名去取对应属性名的值,若取不到对应属性名的值,则为undefined
。
基础用法:
let {a, b} = {a:1, b:2}; // a => 1 , b => 2let {a, b} = {a:2, b:1}; // a => 2 , b => 1let {a} = {a:3, b:2, c:1};// a => 3let {a} = {b:2, c:1}; // a => undefined复制代码
注意点:
let {a:b} = {a:1, c:2}; // error: a is not defined// b => 1复制代码
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
上面代码中,a
是匹配的模式,b
才是变量。真正被赋值的是变量b
,而不是模式a
。 let obj = { a:[ 1, { b: 2}]};let {a, a: [c, {b}]} = obj;// a=>[1, {b: 2}], b => 2, c => 1复制代码
指定解构的默认值:
let {a=1} = {}; // a => 1let {a, b=1} = {a:2}; // a => 2, b => 1let {a:b=3} = {}; // b => 3let {a:b=3} = {a:4}; // b = >4// a是模式,b是变量 牢记let {a=1} = {a:undefined}; // a => 1let {a=1} = {a:null}; // a => null// 因为null与undefined不严格相等,所以赋值有效// 导致默认值1不会生效。复制代码
1.2.3 字符串的解构赋值
字符串的解构赋值中,字符串被转换成了一个类似数组的对象。 基础用法:
const [a, b, c, d, e] = 'hello';a // "h"b // "e"c // "l"d // "l"e // "o"let {length:len} = 'hello';// len => 5复制代码
1.2.4 数值和布尔值的解构赋值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
// 数值和布尔值的包装对象都有toString属性let {toString: s} = 123;s === Number.prototype.toString // truelet {toString: s} = true;s === Boolean.prototype.toString // truelet { prop: x } = undefined; // TypeErrorlet { prop: y } = null; // TypeError复制代码
1.2.5 函数参数的解构赋值
基础用法:
function fun ([a, b]){ return a + b;}fun ([1, 2]); // 3复制代码
指定默认值的解构:
function fun ({a=0, b=0} = {}){ return [a, b];}fun ({a:1, b:2}); // [1, 2]fun ({a:1}); // [1, 0]fun ({}); // [0, 0]fun (); // [0, 0]function fun ({a, b} = {a:0, b:0}){ return [a, b];}fun ({a:1, b:2}); // [1, 2]fun ({a:1}); // [1, undefined]fun ({}); // [undefined, undefined]fun (); // [0, 0]复制代码
1.2.6 应用
let a = 1,b = 2;[a, b] = [b, a]; // a =>2 , b => 1 复制代码
// 返回一个数组function f (){ return [1, 2, 3];}let [a, b, c] = f(); // a=>1, b=>2, c=>3// 返回一个对象function f (){ return {a:1, b:2};}let {a, b} = f(); // a=>1, b=>2复制代码
function f([a, b, c]) {...}f([1, 2, 3]);function f({a, b, c}) {...}f({b:2, c:3, a:1});复制代码
let json = { name : 'leo', age: 18}let {name, age} = json;console.log(name,age); // leo, 18复制代码
const m = new Map();m.set('a',1);m.set('b',2);for (let [k, v] of m){ console.log(k + ' : ' + v);}// 获取键名for (let [k] of m){...}// 获取键值for (let [,k] of m){...}复制代码
const {log, sin, cos} = require('math');复制代码
1.3.1 includes(),startsWith(),endsWith()
在我们判断字符串是否包含另一个字符串时,ES6之前,我们只有typeof
方法,ES6之后我们又多了三种方法:
let a = 'hello leo';a.startsWith('leo'); // falsea.endsWith('o'); // truea.includes('lo'); // true复制代码
并且这三个方法都支持第二个参数,表示起始搜索的位置。
let a = 'hello leo';a.startsWith('leo',1); // falsea.endsWith('o',5); // truea.includes('lo',6); // false复制代码
endsWith
是针对前 n
个字符,而其他两个是针对从第n
个位置直到结束。
1.3.2 repeat()
repeat
方法返回一个新字符串,表示将原字符串重复n
次。
'ab'.repeat(3); // 'ababab''ab'.repeat(0); // ''复制代码
特殊用法:
小数
,则取整'ab'.repeat(2.3); // 'abab'复制代码
负数
或Infinity
,则报错'ab'.repeat(-1); // RangeError'ab'.repeat(Infinity); // RangeError复制代码
0到-1的小数
或NaN
,则取0'ab'.repeat(-0.5); // '''ab'.repeat(NaN); // ''复制代码
字符串
,则转成数字
'ab'.repeat('ab'); // '''ab'.repeat('3'); // 'ababab'复制代码
1.3.3 padStart(),padEnd()
用于将字符串头部或尾部补全长度,padStart()
为头部补全,padEnd()
为尾部补全。
'x'.padStart(5, 'ab'); // 'ababx''x'.padEnd(5, 'ab'); // 'xabab'复制代码
特殊用法:
'xyzabc'.padStart(5, 'ab'); // 'xyzabc'复制代码
'ab'.padStart(5,'012345'); // "012ab"复制代码
空格
补全。'x'.padStart(4); // ' x''x'.padEnd(4); // 'x '复制代码
1.3.4 模版字符串
用于拼接字符串,ES6之前:
let a = 'abc' + 'def' + 'ghi';复制代码
ES6之后:
let a = ` abc def ghi`复制代码
拼接变量: 在**反引号(`)**中使用${}
包裹变量或方法。
// ES6之前let a = 'abc' + v1 + 'def';// ES6之后let a = `abc${v1}def`复制代码
1.4.1 介绍
在ES5中有两种情况。
let a = new RegExp('abc', 'i');// 等价于let a = /abx/i;复制代码
let a = new RegExp(/abc/i);//等价于let a = /abx/i;let a = new RegExp(/abc/, 'i');// Uncaught TypeError复制代码
ES6中使用:
第一个参数是正则对象,第二个是指定修饰符,如果第一个参数已经有修饰符,则会被第二个参数覆盖。new RegExp(/abc/ig, 'i');复制代码
1.4.2 字符串的正则方法
常用的四种方法:match()
、replace()
、search()
和split()
。
1.4.3 u修饰符
添加u
修饰符,是为了处理大于uFFFF
的Unicode字符,即正确处理四个字节的UTF-16编码。
/^\uD83D/u.test('\uD83D\uDC2A'); // false/^\uD83D/.test('\uD83D\uDC2A'); // true复制代码
由于ES5之前不支持四个字节UTF-16编码,会识别为两个字符,导致第二行输出true
,加入u
修饰符后ES6就会识别为一个字符,所以输出false
。
注意:
加上u
修饰符后,会改变下面正则表达式的行为: .
)在正则中表示除了换行符以外的任意单个字符。对于码点大于0xFFFF
的Unicode字符,点字符不能识别,必须加上u
修饰符。var a = "?";/^.$/.test(a); // false/^.$/u.test(a); // true复制代码
u
修饰符,才能识别大括号。/\u{61}/.test('a'); // false/\u{61}/u.test('a'); // true/\u{20BB7}/u.test('?'); // true复制代码
u
修饰符后,所有量词都会正确识别码点大于0xFFFF
的 Unicode 字符。/a{2}/.test('aa'); // true/a{2}/u.test('aa'); // true/?{2}/.test('??'); // false/?{2}/u.test('??'); // true复制代码
u
修饰符,就无法识别非规范的K
字符。/[a-z]/i.test('\u212A') // false/[a-z]/iu.test('\u212A') // true复制代码
检查是否设置u
修饰符: 使用unicode
属性。
const a = /hello/;const b = /hello/u;a.unicode // falseb.unicode // true复制代码
1.4.4 y修饰符
y
修饰符与g
修饰符类似,也是全局匹配,后一次匹配都是从上一次匹配成功的下一个位置开始。区别在于,g
修饰符只要剩余位置中存在匹配即可,而y
修饰符是必须从剩余第一个开始。
var s = 'aaa_aa_a';var r1 = /a+/g;var r2 = /a+/y;r1.exec(s) // ["aaa"]r2.exec(s) // ["aaa"]r1.exec(s) // ["aa"] 剩余 '_aa_a'r2.exec(s) // null复制代码
lastIndex
属性: 指定匹配的开始位置:
const a = /a/y;a.lastIndex = 2; // 从2号位置开始匹配a.exec('wahaha'); // nulla.lastIndex = 3; // 从3号位置开始匹配let c = a.exec('wahaha');c.index; // 3a.lastIndex; // 4复制代码
返回多个匹配:
一个y
修饰符对match
方法只能返回第一个匹配,与g
修饰符搭配能返回所有匹配。 'a1a2a3'.match(/a\d/y); // ["a1"]'a1a2a3'.match(/a\d/gy); // ["a1", "a2", "a3"]复制代码
检查是否使用y
修饰符:
sticky
属性检查。 const a = /hello\d/y;a.sticky; // true复制代码
1.4.5 flags属性
flags
属性返回所有正则表达式的修饰符。
/abc/ig.flags; // 'gi'复制代码
1.5.1 Number.isFinite(), Number.isNaN()
Number.isFinite()
用于检查一个数值是否是有限的,即不是Infinity
,若参数不是Number
类型,则一律返回false
。
Number.isFinite(10); // trueNumber.isFinite(0.5); // trueNumber.isFinite(NaN); // falseNumber.isFinite(Infinity); // falseNumber.isFinite(-Infinity); // falseNumber.isFinite('leo'); // falseNumber.isFinite('15'); // falseNumber.isFinite(true); // falseNumber.isFinite(Math.random()); // true复制代码
Number.isNaN()
用于检查是否是NaN
,若参数不是NaN
,则一律返回false
。
Number.isNaN(NaN); // trueNumber.isNaN(10); // falseNumber.isNaN('10'); // falseNumber.isNaN(true); // falseNumber.isNaN(5/NaN); // trueNumber.isNaN('true' / 0); // trueNumber.isNaN('true' / 'true'); // true复制代码
区别:
与传统全局的isFinite()
和isNaN()
方法的区别,传统的这两个方法,是先将参数转换成数值,再判断。 而ES6新增的这两个方法则只对数值有效, Number.isFinite()
对于非数值一律返回false
,Number.isNaN()
只有对于NaN
才返回true
,其他一律返回false
。 isFinite(25); // trueisFinite("25"); // trueNumber.isFinite(25); // trueNumber.isFinite("25"); // falseisNaN(NaN); // trueisNaN("NaN"); // trueNumber.isNaN(NaN); // trueNumber.isNaN("NaN"); // false复制代码
1.5.2 Number.parseInt(), Number.parseFloat()
这两个方法与全局方法parseInt()
和parseFloat()
一致,目的是逐步减少全局性的方法,让语言更模块化。
parseInt('12.34'); // 12parseFloat('123.45#'); // 123.45Number.parseInt('12.34'); // 12Number.parseFloat('123.45#'); // 123.45Number.parseInt === parseInt; // trueNumber.parseFloat === parseFloat; // true复制代码
1.5.3 Number.isInteger()
用来判断一个数值是否是整数,若参数不是数值,则返回false
。
Number.isInteger(10); // trueNumber.isInteger(10.0); // trueNumber.isInteger(10.1); // false复制代码
1.5.4 Math对象的拓展
ES6新增17个数学相关的静态方法,只能在Math对象上调用。
// 正常使用Math.trunc(1.1); // 1Math.trunc(1.9); // 1Math.trunc(-1.1); // -1Math.trunc(-1.9); // -1Math.trunc(-0.1234); // -0// 参数为非数值Math.trunc('11.22'); // 11Math.trunc(true); // 1Math.trunc(false); // 0Math.trunc(null); // 0// 参数为空和无法取整Math.trunc(NaN); // NaNMath.trunc('leo'); // NaNMath.trunc(); // NaNMath.trunc(undefined); // NaN复制代码
ES5实现:
Math.trunc = Math.trunc || function(x){ return x < 0 ? Math.ceil(x) : Math.floor(x);}复制代码
Math.sign(-1); // -1Math.sign(1); // +1Math.sign(0); // 0Math.sign(-0); // -0Math.sign(NaN); // NaNMath.sign(''); // 0Math.sign(true); // +1Math.sign(false);// 0Math.sign(null); // 0Math.sign('9'); // +1Math.sign('leo');// NaNMath.sign(); // NaNMath.sign(undefined); // NaN复制代码
ES5实现
Math.sign = Math.sign || function (x){ x = +x; if (x === 0 || isNaN(x)){ return x; } return x > 0 ? 1: -1;}复制代码
Math.cbrt(-1); // -1Math.cbrt(0); // 0Math.cbrt(1); // 1Math.cbrt(2); // 1.2599210498Math.cbrt('1'); // 1Math.cbrt('leo'); // NaN复制代码
ES5实现
Math.cbrt = Math.cbrt || function (x){ var a = Math.pow(Math.abs(x), 1/3); return x < 0 ? -y : y;}复制代码
Math.clz32(0) // 32Math.clz32(1) // 31Math.clz32(1000) // 22Math.clz32(0b01000000000000000000000000000000) // 1Math.clz32(0b00100000000000000000000000000000) // 2复制代码
Math.imul(2, 4) // 8Math.imul(-1, 8) // -8Math.imul(-2, -2) // 4复制代码
Math.fround(0) // 0Math.fround(1) // 1Math.fround(2 ** 24 - 1) // 16777215复制代码
Math.hypot(3, 4); // 5Math.hypot(3, 4, 5); // 7.0710678118654755Math.hypot(); // 0Math.hypot(NaN); // NaNMath.hypot(3, 4, 'foo'); // NaNMath.hypot(3, 4, '5'); // 7.0710678118654755Math.hypot(-3); // 3复制代码
ex - 1
,即Math.exp(x) - 1
。Math.expm1(-1) // -0.6321205588285577Math.expm1(0) // 0Math.expm1(1) // 1.718281828459045复制代码
ES5实现
Math.expm1 = Math.expm1 || function(x) { return Math.exp(x) - 1;};复制代码
1 + x
的自然对数,即Math.log(1 + x)
。如果x小于-1
,返回NaN
。Math.log1p(1) // 0.6931471805599453Math.log1p(0) // 0Math.log1p(-1) // -InfinityMath.log1p(-2) // NaN复制代码
ES5实现
Math.log1p = Math.log1p || function(x) { return Math.log(1 + x);};复制代码
10
为底的x的对数
。如果x小于 0,则返回 NaN
。Math.log10(2) // 0.3010299956639812Math.log10(1) // 0Math.log10(0) // -InfinityMath.log10(-2) // NaNMath.log10(100000) // 5复制代码
ES5实现
Math.log10 = Math.log10 || function(x) { return Math.log(x) / Math.LN10;};复制代码
2
为底的x的对数
。如果x
小于0
,则返回 NaN
。Math.log2(3) // 1.584962500721156Math.log2(2) // 1Math.log2(1) // 0Math.log2(0) // -InfinityMath.log2(-2) // NaNMath.log2(1024) // 10Math.log2(1 << 29) // 29复制代码
ES5实现
Math.log2 = Math.log2 || function(x) { return Math.log(x) / Math.LN2;};复制代码
Math.sinh(x)
返回x的双曲正弦(hyperbolic sine)Math.cosh(x)
返回x的双曲余弦(hyperbolic cosine)Math.tanh(x)
返回x的双曲正切(hyperbolic tangent)Math.asinh(x)
返回x的反双曲正弦(inverse hyperbolic sine)Math.acosh(x)
返回x的反双曲余弦(inverse hyperbolic cosine)Math.atanh(x)
返回x的反双曲正切(inverse hyperbolic tangent)1.5.5 指数运算符
新增的指数运算符(**
):
2 ** 2; // 42 ** 3; // 8 2 ** 3 ** 2; // 相当于 2 ** (3 ** 2); 返回 512复制代码
指数运算符(**
)与Math.pow
的实现不相同,对于特别大的运算结果,两者会有细微的差异。
Math.pow(99, 99)// 3.697296376497263e+19799 ** 99// 3.697296376497268e+197复制代码
1.6.1 参数默认值
// ES6 之前function f(a, b){ b = b || 'leo'; console.log(a, b);}// ES6 之后function f(a, b='leo'){ console.log(a, b);}f('hi'); // hi leof('hi', 'jack'); // hi jackf('hi', ''); // hi leo复制代码
注意:
let
和const
再次声明:function f (a = 1){ let a = 2; // error}复制代码
function f (a, a, b){ ... }; // 不报错function f (a, a, b = 1){ ... }; // 报错复制代码
与解构赋值默认值结合使用:
function f ({a, b=1}){ console.log(a,b)};f({}); // undefined 1f({a:2}); // 2 1f({a:2, b:3}); // 2 3f(); // 报错function f ({a, b = 1} = {}){ console.log(a, b)}f(); // undefined 1复制代码
尾参数定义默认值:
通常在尾参数定义默认值,便于观察参数,并且非尾参数无法省略。function f (a=1,b){ return [a, b];}f(); // [1, undefined]f(2); // [2, undefined]f(,2); // 报错f(undefined, 2); // [1, 2]function f (a, b=1, c){ return [a, b, c];}f(); // [undefined, 1, undefined]f(1); // [1,1,undefined]f(1, ,2); // 报错f(1,undefined,2); // [1,1,2]复制代码
在给参数传递默认值时,传入undefined
会触发默认值,传入null
不会触发。
function f (a = 1, b = 2){ console.log(a, b);}f(undefined, null); // 1 null复制代码
函数的length属性:
length
属性将返回,没有指定默认值的参数数量,并且rest参数不计入length
属性。 function f1 (a){...};function f2 (a=1){...};function f3 (a, b=2){...};function f4 (...a){...};function f5 (a,b,...c){...};f1.length; // 1f2.length; // 0f3.length; // 1f4.length; // 0f5.length; // 2复制代码
1.6.2 rest 参数
rest
参数形式为(...变量名
),其值为一个数组,用于获取函数多余参数。
function f (a, ...b){ console.log(a, b);}f(1,2,3,4); // 1 [2, 3, 4]复制代码
注意:
rest
参数只能放在最后一个,否则报错:function f(a, ...b, c){...}; // 报错 复制代码
length
属性不包含rest
参数。function f1 (a){...};function f2 (a,...b){...};f1(1); // 1f2(1,2); // 1复制代码
1.6.3 name 属性
用于返回该函数的函数名。
function f (){...};f.name; // fconst f = function g(){...};f.name; // g复制代码
1.6.4 箭头函数
使用“箭头”(=>
)定义函数。
// 有1个参数let f = v => v;// 等同于let f = function (v){return v};// 有多个参数let f = (v, i) => {return v + i};// 等同于let f = function (v, i){return v + i};// 没参数let f = () => 1;// 等同于let f = function (){return 1};复制代码
箭头函数与变量结构结合使用:
// 正常函数写法function f (p) { return p.a + ':' + p.b;}// 箭头函数写法let f = ({a, b}) => a + ':' + b;复制代码
简化回调函数:
// 正常函数写法[1, 2, 3].map(function (x){ return x * x;})// 箭头函数写法[1, 2, 3].map(x => x * x);复制代码
箭头函数与rest参数结合:
let f = (...n) => n;f(1, 2, 3); // [1, 2, 3]复制代码
注意点:
this
总是指向定义时所在的对象,而不是调用时。new
命令,否则报错。arguments
对象,即不能使用,可以使用rest
参数代替。yield
命令,即不能用作Generator函数。不适用场景:
this
。const obj = { a:9, b: () => { this.a --; }}复制代码
上述b
如果是普通函数,函数内部的this
指向obj
,但是如果是箭头函数,则this
会指向全局,不是预期结果。
this
时。let b = document.getElementById('myID');b.addEventListener('click', ()=>{ this.classList.toggle('on');})复制代码
上诉按钮点击会报错,因为b
监听的箭头函数中,this
是全局对象,若改成普通函数,this
就会指向被点击的按钮对象。
1.6.5 双冒号运算符
双冒号暂时是一个提案,用于解决一些不适用的场合,取代call
、apply
、bind
调用。
::
)的左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this
对象),绑定到右边函数上。 f::b;// 等同于b.bind(f);f::b(...arguments);// 等同于b.apply(f, arguments);复制代码
若双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定到该对象上。
let f = a::a.b;// 等同于let f = ::a.b;复制代码
1.7.1 拓展运算符
拓展运算符使用(...
),类似rest
参数的逆运算,将数组转为用(,
)分隔的参数序列。
console.log(...[1, 2, 3]); // 1 2 3 console.log(1, ...[2,3], 4); // 1 2 3 4复制代码
拓展运算符主要使用在函数调用。
function f (a, b){ console.log(a, b);}f(...[1, 2]); // 1 2function g (a, b, c, d, e){ console.log(a, b, c, d, e);}g(0, ...[1, 2], 3, ...[4]); // 0 1 2 3 4复制代码
若拓展运算符后面是个空数组,则不产生效果。
[...[], 1]; // 1复制代码
替代apply方法
// ES6之前function f(a, b, c){...};var a = [1, 2, 3];f.apply(null, a);// ES6之后function f(a, b, c){...};let a = [1, 2, 3];f(...a);// ES6之前Math.max.apply(null, [3,2,6]);// ES6之后Math.max(...[3,2,6]);复制代码
拓展运算符的运用
// 通常情况 浅拷贝let a1 = [1, 2];let a2 = a1; a2[0] = 3;console.log(a1,a2); // [3,2] [3,2]// 拓展运算符 深拷贝let a1 = [1, 2];let a2 = [...a1];// let [...a2] = a1; // 作用相同a2[0] = 3;console.log(a1,a2); // [1,2] [3,2]复制代码
let a1 = [1,2];let a2 = [3];let a3 = [4,5];// ES5 let a4 = a1.concat(a2, a3);// ES6let a5 = [...a1, ...a2, ...a3];a4[0] === a1[0]; // truea5[0] === a1[0]; // true复制代码
let [a, ...b] = [1, 2, 3, 4]; // a => 1 b => [2,3,4]let [a, ...b] = [];// a => undefined b => []let [a, ...b] = ["abc"];// a => "abc" b => []复制代码
1.7.2 Array.from()
将 类数组对象 和 可遍历的对象,转换成真正的数组。
// 类数组对象let a = { '0':'a', '1':'b', length:2}let arr = Array.from(a);// 可遍历的对象let a = Array.from([1,2,3]);let b = Array.from({length: 3});let c = Array.from([1,2,3]).map(x => x * x);let d = Array.from([1,2,3].map(x => x * x));复制代码
1.7.3 Array.of()
将一组数值,转换成数组,弥补Array
方法参数不同导致的差异。
Array.of(1,2,3); // [1,2,3]Array.of(1).length; // 1Array(); // []Array(2); // [,] 1个参数时,为指定数组长度Array(1,2,3); // [1,2,3] 多于2个参数,组成新数组复制代码
1.7.4 find()和findIndex()
find()
方法用于找出第一个符合条件的数组成员,参数为一个回调函数,所有成员依次执行该回调函数,返回第一个返回值为true
的成员,如果没有一个符合则返回undefined
。
[1,2,3,4,5].find( a => a < 3 ); // 1复制代码
回调函数接收三个参数,当前值、当前位置和原数组。
[1,2,3,4,5].find((value, index, arr){ // ...});复制代码
findIndex()
方法与find()
类似,返回第一个符合条件的数组成员的位置,如果都不符合则返回-1
。
[1,2,3,4].findIndex((v,i,a)=>{ return v>2;}); // 2复制代码
1.7.5 fill()
用于用指定值填充一个数组,通常用来初始化空数组,并抹去数组中已有的元素。
new Array(3).fill('a'); // ['a','a','a'][1,2,3].fill('a'); // ['a','a','a']复制代码
并且fill()
的第二个和第三个参数指定填充的起始位置和结束位置。
[1,2,3].fill('a',1,2);复制代码
1.7.6 entries(),keys(),values()
主要用于遍历数组,entries()
对键值对遍历,keys()
对键名遍历,values()
对键值遍历。
for (let i of ['a', 'b'].keys()){ console.log(i)}// 0// 1for (let e of ['a', 'b'].values()){ console.log(e)}// 'a'// 'b'for (let e of ['a', 'b'].entries()){ console.log(e)}// [0, "a"] // [1, "b"]复制代码
1.7.7 includes()
用于表示数组是否包含给定的值,与字符串的includes
方法类似。
[1,2,3].includes(2); // true[1,2,3].includes(4); // false[1,2,NaN].includes(NaN); // true复制代码
第二个参数为起始位置,默认为0
,如果负数,则表示倒数的位置,如果大于数组长度,则重置为0
开始。
[1,2,3].includes(3,3); // false[1,2,3].includes(3,4); // false[1,2,3].includes(3,-1); // true[1,2,3].includes(3,-4); // true复制代码
1.7.8 flat(),flatMap()
flat()
用于将数组一维化,返回一个新数组,不影响原数组。
Infinity
作为参数。 [1, 2, [2,3]].flat(); // [1,2,2,3][1,2,[3,[4,[5,6]]]].flat(3); // [1,2,3,4,5,6][1,2,[3,[4,[5,6]]]].flat('Infinity'); // [1,2,3,4,5,6]复制代码
flatMap()
是将原数组每个对象先执行一个函数,在对返回值组成的数组执行flat()
方法,返回一个新数组,不改变原数组。
flatMap()
只能展开一层。 [2, 3, 4].flatMap((x) => [x, x * 2]); // [2, 4, 3, 6, 4, 8] 复制代码
1.8.1 属性的简洁表示
let a = 'a1';let b = { a }; // b => { a : 'a1' }// 等同于let b = { a : a };function f(a, b){ return {a, b}; }// 等同于function f (a, b){ return {a:a ,b:b};}let a = { fun () { return 'leo'; }}// 等同于let a = { fun : function(){ return 'leo'; }}复制代码
1.8.2 属性名表达式
JavaScript
提供2种方法定义对象的属性。
// 方法1 标识符作为属性名a.f = true;// 方法2 字符串作为属性名a['f' + 'un'] = true;复制代码
延伸出来的还有:
let a = 'hi leo';let b = { [a]: true, ['a'+'bc']: 123, ['my' + 'fun'] (){ return 'hi'; }};// b.a => undefined ; b.abc => 123 ; b.myfun() => 'hi'// b[a] => 'hi leo' ; b['abc'] => 123 ; b['myfun'] => 'hi'复制代码
注意:
属性名表达式不能与简洁表示法同时使用,否则报错。// 报错let a1 = 'aa';let a2 = 'bb';let b1 = {[a1]};// 正确let a1 = 'aa';let b1 = { [a1] : 'bb'};复制代码
1.8.3 ()
Object.is()
用于比较两个值是否严格相等,在ES5时候只要使用相等运算符(==
)和严格相等运算符(===
)就可以做比较,但是它们都有缺点,前者会自动转换数据类型,后者的NaN
不等于自身,以及+0
等于-0
。
Object.is('a','a'); // trueObject.is({}, {}); // false// ES5+0 === -0 ; // trueNaN === NaN; // false// ES6Object.is(+0,-0); // falseObject.is(NaN,NaN); // true复制代码
1.8.4 Object.assign()
Object.assign()
方法用于对象的合并,将原对象的所有可枚举属性复制到目标对象。
let a = {a:1};let b = {b:2};Object.assign(a,b); // a=> {a:1,b:2}复制代码
注意:
let a = {a:1, b:2};let b = {b:3, c:4};Object.assign(a, b); // a => {a:1, b:3, c:4}复制代码
let a = {a:1};Object.assign(a) === a; // true复制代码
typeof Object.assign(2); // 'object'复制代码
undefined
或NaN
无法转成对象,所以做为参数会报错。Object.assign(undefined) // 报错Object.assign(NaN); // 报错复制代码
Object.assign()
实现的是浅拷贝。Object.assign()
拷贝得到的是这个对象的引用。这个对象的任何变化,都会反映到目标对象上面。
let a = {a: {b:1}};let b = Object.assign({},a);a.a.b = 2;console.log(b.a.b); // 2复制代码
Object.assign([1, 2, 3], [4, 5]); // [4, 5, 3]复制代码
1.9.1 介绍
ES6引入Symbol
作为一种新的原始数据类型,表示独一无二的值,主要是为了防止属性名冲突。
Symbol
、undefined
、null
、Boolean
、String
、Number
、Object
。 简单实用: let a = Symbol();typeof a; // "symbol"复制代码
注意:
Symbol
函数不能用new
,会报错。由于Symbol
是一个原始类型,不是对象,所以不能添加属性,它是类似于字符串的数据类型。Symbol
都是不相等的,即使参数相同。// 没有参数let a1 = Symbol();let a2 = Symbal();a1 === a2; // false // 有参数let a1 = Symbol('abc');let a2 = Symbal('abc');a1 === a2; // false 复制代码
Symbol
不能与其他类型的值计算,会报错。let a = Symbol('hello');a + " world!"; // 报错`${a} world!`; // 报错复制代码
Symbol可以显式转换为字符串:
let a1 = Symbol('hello');String(a1); // "Symbol(hello)"a1.toString(); // "Symbol(hello)"复制代码
Symbol可以转换为布尔值,但不能转为数值:
let a1 = Symbol();Boolean(a1);!a1; // falseNumber(a1); // TypeErrora1 + 1 ; // TypeError复制代码
1.9.2 Symbol作为属性名
好处:防止同名属性,还有防止键被改写或覆盖。
let a1 = Symbol();// 写法1let b = {};b[a1] = 'hello';// 写法2let b = { [a1] : 'hello'} // 写法3let b = {};Object.defineProperty(b, a1, {value : 'hello' });// 3种写法 结果相同b[a1]; // 'hello'复制代码
需要注意: Symbol作为对象属性名时,不能用点运算符,并且必须放在方括号内。
let a = Symbol();let b = {};// 不能用点运算b.a = 'hello';b[a] ; // undefinedb['a'] ; // 'hello'// 必须放在方括号内let c = { [a] : function (text){ console.log(text); }}c[a]('leo'); // 'leo'// 上面等价于 更简洁let c = { [a](text){ console.log(text); }}复制代码
常常还用于创建一组常量,保证所有值不相等:
let a = {};a.a1 = { AAA: Symbol('aaa'), BBB: Symbol('bbb'), CCC: Symbol('ccc')}复制代码
1.9.3 应用:消除魔术字符串
魔术字符串:指代码中多次出现,强耦合的字符串或数值,应该避免,而使用含义清晰的变量代替。
function f(a){ if(a == 'leo') { console.log('hello'); }}f('leo'); // 'leo' 为魔术字符串复制代码
常使用变量,消除魔术字符串:
let obj = { name: 'leo'};function f (a){ if(a == obj.name){ console.log('hello'); }}f(obj.name); // 'leo'复制代码
使用Symbol消除强耦合,使得不需关系具体的值:
let obj = { name: Symbol()};function f (a){ if(a == obj.name){ console.log('hello'); }}f(obj.name);复制代码
1.9.4 属性名遍历
Symbol作为属性名遍历,不出现在for...in
、for...of
循环,也不被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
let a = Symbol('aa'),b= Symbol('bb');let obj = { [a]:'11', [b]:'22'}for(let k of Object.values(obj)){console.log(k)}// 无输出let obj = {};let aa = Symbol('leo');Object.defineProperty(obj, aa, {value: 'hi'});for(let k in obj){ console.log(k); // 无输出}Object.getOwnPropertyNames(obj); // []Object.getOwnPropertySymbols(obj); // [Symbol(leo)]复制代码
Object.getOwnPropertySymbols
方法返回一个数组,包含当前对象所有用做属性名的Symbol值。
let a = {};let a1 = Symbol('a');let a2 = Symbol('b');a[a1] = 'hi';a[a2] = 'oi';let obj = Object.getOwnPropertySymbols(a);obj; // [Symbol(a), Symbol(b)]复制代码
另外可以使用Reflect.ownKeys
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
let a = { [Symbol('leo')]: 1, aa : 2, bb : 3,}Reflect.ownKeys(a); // ['aa', 'bb',Symbol('leo')]复制代码
由于Symbol值作为名称的属性不被常规方法遍历获取,因此常用于定义对象的一些非私有,且内部使用的方法。
1.9.5 Symbol.for()、Symbol.keyFor()
let a = Symbol.for('aaa');let b = Symbol.for('aaa');a === b; // true复制代码
Symbol()
和 Symbol.for()
区别:
Symbol.for('aa') === Symbol.for('aa'); // trueSymbol('aa') === Symbol('aa'); // false复制代码
let a = Symbol.for('aa');Symbol.keyFor(a); // 'aa'let b = Symbol('aa');Symbol.keyFor(b); // undefined复制代码
1.9.6 内置的Symbol值
ES6提供11个内置的Symbol值,指向语言内部使用的方法:
instanceof
运算符,判断是否为该对象的实例时,会调用这个方法。比如,foo instanceof Foo
在语言内部,实际调用的是Foo[Symbol.hasInstance](foo)
。class P { [Symbol.hasInstance](a){ return a instanceof Array; }}[1, 2, 3] instanceof new P(); // true复制代码
P是一个类,new P()会返回一个实例,该实例的Symbol.hasInstance
方法,会在进行instanceof
运算时自动调用,判断左侧的运算子是否为Array
的实例。
Array.prototype.concat()
时,是否可以展开。let a = ['aa','bb'];['cc','dd'].concat(a, 'ee'); // ['cc', 'dd', 'aa', 'bb', 'ee']a[Symbol.isConcatSpreadable]; // undefinedlet b = ['aa','bb']; b[Symbol.isConcatSpreadable] = false; ['cc','dd'].concat(b, 'ee'); // ['cc', 'dd',[ 'aa', 'bb'], 'ee']复制代码
get
取值器。class P extends Array { static get [Symbol.species](){ return this; }}复制代码
解决下面问题:
// 问题: b应该是 Array 的实例,实际上是 P 的实例class P extends Array{}let a = new P(1,2,3);let b = a.map(x => x);b instanceof Array; // trueb instanceof P; // true// 解决: 通过使用 Symbol.speciesclass P extends Array { static get [Symbol.species]() { return Array; }}let a = new P();let b = a.map(x => x);b instanceof P; // falseb instanceof Array; // true复制代码
str.match(myObject)
,传入的属性存在时会调用,并返回该方法的返回值。class P { [Symbol.match](string){ return 'hello world'.indexOf(string); }}'h'.match(new P()); // 0复制代码
String.prototype.replace
方法调用时,会返回该方法的返回值。let a = {};a[Symbol.replace] = (...s) => console.log(s);'Hello'.replace(a , 'World') // ["Hello", "World"]复制代码
String.prototype.search
方法调用时,会返回该方法的返回值。class P { constructor(val) { this.val = val; } [Symbol.search](s){ return s.indexOf(this.val); }}'hileo'.search(new P('leo')); // 2复制代码
String.prototype.split
方法调用时,会返回该方法的返回值。// 重新定义了字符串对象的split方法的行为class P { constructor(val) { this.val = val; } [Symbol.split](s) { let i = s.indexOf(this.val); if(i == -1) return s; return [ s.substr(0, i), s.substr(i + this.val.length) ] }}'helloworld'.split(new P('hello')); // ["hello", ""]'helloworld'.split(new P('world')); // ["", "world"] 'helloworld'.split(new P('leo')); // "helloworld"复制代码
for...of
循环时,会调用Symbol.iterator
方法,返回该对象的默认遍历器。class P { *[Symbol.interator]() { let i = 0; while(this[i] !== undefined ) { yield this[i]; ++i; } }}let a = new P();a[0] = 1;a[1] = 2;for (let k of a){ console.log(k);}复制代码
let obj = { [Symbol.toPrimitive](hint) { switch (hint) { case 'number': return 123; case 'string': return 'str'; case 'default': return 'default'; default: throw new Error(); } }};2 * obj // 2463 + obj // '3default'obj == 'default' // trueString(obj) // 'str'复制代码
Object.prototype.toString
方法时,如果这个属性存在,它的返回值会出现在toString
方法返回的字符串之中,表示对象的类型。也就是说,这个属性可以用来定制[object Object
]或[object Array]
中object
后面的那个字符串。// 例一({[Symbol.toStringTag]: 'Foo'}.toString())// "[object Foo]"// 例二class Collection { get [Symbol.toStringTag]() { return 'xxx'; }}let x = new Collection();Object.prototype.toString.call(x) // "[object xxx]"复制代码
// 没有 unscopables 时class MyClass { foo() { return 1; }}var foo = function () { return 2; };with (MyClass.prototype) { foo(); // 1}// 有 unscopables 时class MyClass { foo() { return 1; } get [Symbol.unscopables]() { return { foo: true }; }}var foo = function () { return 2; };with (MyClass.prototype) { foo(); // 2}复制代码
上面代码通过指定Symbol.unscopables
属性,使得with
语法块不会在当前作用域寻找foo
属性,即foo
将指向外层作用域的变量。
1.10.1 Set
介绍:
Set
数据结构类似数组,但所有成员的值唯一。Set
本身为一个构造函数,用来生成Set
数据结构,使用add
方法来添加新成员。 let a = new Set();[1,2,2,1,3,4,5,4,5].forEach(x=>a.add(x));for(let k of a){ console.log(k)};// 1 2 3 4 5复制代码
基础使用:
let a = new Set([1,2,3,3,4]);[...a]; // [1,2,3,4]a.size; // 4// 数组去重[...new Set([1,2,3,4,4,4])];// [1,2,3,4]复制代码
注意:
Set
中添加值的时候,不会类型转换,即5
和'5'
是不同的。[...new Set([5,'5'])]; // [5, "5"]复制代码
属性和方法:
属性:
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。操作方法:
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set的成员。clear()
:清除所有成员,没有返回值。let a = new Set();a.add(1).add(2); // a => Set(2) {1, 2}a.has(2); // truea.has(3); // falsea.delete(2); // true a => Set(1) {1}a.clear(); // a => Set(0) {}复制代码
数组去重:
let a = new Set([1,2,3,3,3,3]);复制代码
1.10.2 Set的应用
数组去重:
// 方法1[...new Set([1,2,3,4,4,4])]; // [1,2,3,4]// 方法2Array.from(new Set([1,2,3,4,4,4])); // [1,2,3,4]复制代码
遍历和过滤:
let a = new Set([1,2,3,4]);// map 遍历操作let b = new Set([...a].map(x =>x*2));// b => Set(4) {2,4,6,8}// filter 过滤操作let c = new Set([...a].filter(x =>(x%2) == 0)); // b => Set(2) {2,4}复制代码
获取并集、交集和差集:
let a = new Set([1,2,3]);let b = new Set([4,3,2]);// 并集let c1 = new Set([...a, ...b]); // Set {1,2,3,4}// 交集let c2 = new Set([...a].filter(x => b.has(x))); // set {2,3}// 差集let c3 = new Set([...a].filter(x => !b.has(x))); // set {1}复制代码
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回键值对的遍历器。forEach()
:使用回调函数遍历每个成员。Set
遍历顺序是插入顺序,当保存多个回调函数,只需按照顺序调用。但由于Set
结构没有键名只有键值,所以keys()
和values()
是返回结果相同。
let a = new Set(['a','b','c']);for(let i of a.keys()){console.log(i)}; // 'a' 'b' 'c'for(let i of a.values()){console.log(i)}; // 'a' 'b' 'c'for(let i of a.entries()){console.log(i)}; // ['a','a'] ['b','b'] ['c','c']复制代码
并且 还可以使用for...of
直接遍历Set
。
let a = new Set(['a','b','c']);for(let k of a){console.log(k)}; // 'a' 'b' 'c'复制代码
forEach
与数组相同,对每个成员执行操作,且无返回值。
let a = new Set(['a','b','c']);a.forEach((v,k) => console.log(k + ' : ' + v));复制代码
1.10.3 Map
由于传统的JavaScript
对象只能用字符串当做键,给开发带来很大限制,ES6增加Map
数据结构,使得各种类型的值(包括对象)都可以作为键。
Map
结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。 基础使用: let a = new Map();let b = {name: 'leo' };a.set(b,'my name'); // 添加值a.get(b); // 获取值a.size; // 获取总数a.has(b); // 查询是否存在a.delete(b); // 删除一个值a.clear(); // 清空所有成员 无返回复制代码
注意:
let a = new Map([ ['name','leo'], ['age',18]])复制代码
let a = new Map();a.set(1,'aaa').set(1,'bbb');a.get(1); // 'bbb'复制代码
undefined
。new Map().get('abcdef'); // undefined复制代码
let a = new Map();let a1 = ['aaa'];let a2 = ['aaa'];a.set(a1,111).set(a2,222);a.get(a1); // 111a.get(a2); // 222复制代码
遍历方法: Map 的遍历顺序就是插入顺序。
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历 Map 的所有成员。let a = new Map([ ['name','leo'], ['age',18]])for (let i of a.keys()){...};for (let i of a.values()){...};for (let i of a.entries()){...};a.forEach((v,k,m)=>{ console.log(`key:${k},value:${v},map:${m}`)})复制代码
将Map结构转成数组结构:
let a = new Map([ ['name','leo'], ['age',18]])let a1 = [...a.keys()]; // a1 => ["name", "age"]let a2 = [...a.values()]; // a2 => ["leo", 18]let a3 = [...a.entries()];// a3 => [['name','leo'], ['age',18]]复制代码
1.10.4 Map与其他数据结构互相转换
let a = new Map().set(true,1).set({f:2},['abc']);[...a]; // [[true:1], [ {f:2},['abc'] ]]复制代码
let a = [ ['name','leo'], [1, 'hi' ]]let b = new Map(a);复制代码
function fun(s) { let obj = Object.create(null); for (let [k,v] of s) { obj[k] = v; } return obj;}const a = new Map().set('yes', true).set('no', false);fun(a)// { yes: true, no: false }复制代码
function fun(obj) { let a = new Map(); for (let k of Object.keys(obj)) { a.set(k, obj[k]); } return a;}fun({yes: true, no: false})// Map {"yes" => true, "no" => false}复制代码
function fun (s) { let obj = Object.create(null); for (let [k,v] of s) { obj[k] = v; } return JSON.stringify(obj)}let a = new Map().set('yes', true).set('no', false);fun(a);// '{"yes":true,"no":false}'复制代码
(2)Map键名有非字符串,转为数组JSON:
function fun (map) { return JSON.stringify([...map]);}let a = new Map().set(true, 7).set({foo: 3}, ['abc']);fun(a)// '[[true,7],[{"foo":3},["abc"]]]'复制代码
function fun (s) { let strMap = new Map(); for (let k of Object.keys(s)) { strMap.set(k, s[k]); } return strMap; return JSON.parse(strMap);}fun('{"yes": true, "no": false}')// Map {'yes' => true, 'no' => false}复制代码
(2)整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组:
function fun2(s) { return new Map(JSON.parse(s));}fun2('[[true,7],[{"foo":3},["abc"]]]')// Map {true => 7, Object {foo: 3} => ['abc']}复制代码
proxy
用于修改某些操作的默认行为,可以理解为一种拦截外界对目标对象访问的一种机制,从而对外界的访问进行过滤和修改,即代理某些操作,也称“代理器”。
1.11.1 基础使用
proxy
实例化需要传入两个参数,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为。
let p = new Proxy(target, handler);let a = new Proxy({}, { get: function (target, handler){ return 'leo'; }})a.name; // leoa.age; // leoa.abcd; // leo复制代码
上述a
实例中,在第二个参数中定义了get
方法,来拦截外界访问,并且get
方法接收两个参数,分别是目标对象和所要访问的属性,所以不管外部访问对象中任何属性都会执行get
方法返回leo
。
Proxy
实例的对象才能使用这些操作。handler
没有设置拦截,则直接返回原对象。let target = {};let handler = {};let p = new Proxy(target, handler);p.a = 'leo'; target.a; // 'leo'复制代码
同个拦截器函数,设置多个拦截操作:
let p = new Proxy(function(a, b){ return a + b;},{ get:function(){ return 'get方法'; }, apply:function(){ return 'apply方法'; }})复制代码
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)。
1.11.2 取消Proxy实例
使用Proxy.revocale
方法取消Proxy
实例。
let a = {};let b = {};let {proxy, revoke} = Proxy.revocale(a, b);proxy.name = 'leo'; // 'leo'revoeke();proxy.name; // TypeError: Revoked复制代码
1.11.3 实现 Web服务的客户端
const service = createWebService('http://le.com/data');service.employees().than(json =>{ const employees = JSON.parse(json);})function createWebService(url){ return new Proxy({}, { get(target, propKey, receiver{ return () => httpGet(url+'/'+propKey); }) })}复制代码
1.12.1 概念
主要用途:解决异步编程带来的回调地狱问题。
把Promise
简单理解一个容器,存放着某个未来才会结束的事件(通常是一个异步操作)的结果。通过Promise
对象来获取异步操作消息,处理各种异步操作。 Promise
对象2特点:
Promise
对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
注意,为了行文方便,本章后面的resolve
d统一只指fulfilled
状态,不包含rejected
状态。
Promise
缺点
1.12.2 基本使用
Promise
为一个构造函数,需要用new
来实例化。
let p = new Promise(function (resolve, reject){ if(/*异步操作成功*/){ resolve(value); } else { reject(error); }})复制代码
Promise
接收一个函数作为参数,该函数两个参数resolve
和reject
,有JS引擎提供。
resolve
作用是将Promise
的状态从pending变成resolved,在异步操作成功时调用,返回异步操作的结果,作为参数传递出去。reject
作用是将Promise
的状态从pending变成rejected,在异步操作失败时报错,作为参数传递出去。Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
p.then(function(val){ // success...},function(err){ // error...})复制代码
几个例子来理解 :
Promise
状态便成为resolved
触发then
方法绑定的回调函数。function timeout (s){ return new Promise((resolve, reject){ setTimeout(result,ms, 'done'); })}timeout(100).then(val => { console.log(val);})复制代码
Promise
新建后立刻执行。let p = new Promise(function(resolve, reject){ console.log(1); resolve();})p.then(()=>{ console.log(2);})console.log(3);// 1// 3// 2 复制代码
异步加载图片:
function f(url){ return new Promise(function(resolve, reject){ const img = new Image (); img.onload = function(){ resolve(img); } img.onerror = function(){ reject(new Error( 'Could not load image at ' + url )); } img.src = url; })}复制代码
resolve
函数和reject
函数的参数为resolve
函数或reject
函数:
p1
的状态决定了p2
的状态,所以p2
要等待p1
的结果再执行回调函数。 const p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000)})const p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000)})p2 .then(result => console.log(result)) .catch(error => console.log(error))// Error: fail复制代码
调用resolve
或reject
不会结束Promise
参数函数的执行,除了return
:
new Promise((resolve, reject){ resolve(1); console.log(2);}).then(r => { console.log(3);})// 2// 1new Promise((resolve, reject){ return resolve(1); console.log(2);})// 1复制代码
1.12.3 Promise.prototype.then()
作用是为Promise
添加状态改变时的回调函数,then
方法的第一个参数是resolved
状态的回调函数,第二个参数(可选)是rejected
状态的回调函数。
then
方法返回一个新Promise
实例,与原来Promise
实例不同,因此可以使用链式写法,上一个then
的结果作为下一个then
的参数。 getJSON("/posts.json").then(function(json) { return json.post;}).then(function(post) { // ...});复制代码
1.12.4 Promise.prototype.catch()
Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) { // ...}).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error);});复制代码
如果 Promise
状态已经变成resolved
,再抛出错误是无效的。
const p = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test');});p .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) });// ok复制代码
当promise
抛出一个错误,就被catch
方法指定的回调函数捕获,下面三种写法相同。
// 写法一const p = new Promise(function(resolve, reject) { throw new Error('test');});p.catch(function(error) { console.log(error);});// Error: test// 写法二const p = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); }});p.catch(function(error) { console.log(error);});// 写法三const p = new Promise(function(resolve, reject) { reject(new Error('test'));});p.catch(function(error) { console.log(error);});复制代码
一般来说,不要在then
方法里面定义Reject
状态的回调函数(即then
的第二个参数),总是使用catch
方法。
// badpromise .then(function(data) { // success }, function(err) { // error });// goodpromise .then(function(data) { //cb // success }) .catch(function(err) { // error });复制代码
1.12.5 Promise.prototype.finally()
finally
方法用于指定不管 Promise
对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
promise.then(result => {···}).catch(error => {···}).finally(() => {···});复制代码
finally
不接收任何参数,与状态无关,本质上是then
方法的特例。
promise.finally(() => { // 语句});// 等同于promise.then( result => { // 语句 return result; }, error => { // 语句 throw error; });复制代码
上面代码中,如果不使用finally
方法,同样的语句需要为成功和失败两种情况各写一次。有了finally
方法,则只需要写一次。
finally
方法总是会返回原来的值。 // resolve 的值是 undefinedPromise.resolve(2).then(() => {}, () => {})// resolve 的值是 2Promise.resolve(2).finally(() => {})// reject 的值是 undefinedPromise.reject(3).then(() => {}, () => {})// reject 的值是 3Promise.reject(3).finally(() => {})复制代码
1.12.6 Promise.all()
用于将多个 Promise
实例,包装成一个新的 Promise
实例,参数可以不是数组,但必须是Iterator接口,且返回的每个成员都是Promise
实例。
const p = Promise.all([p1, p2, p3]);复制代码
p
的状态由p1
、p2
、p3
决定,分成两种情况。
// 生成一个Promise对象的数组const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json");});Promise.all(promises).then(function (posts) { // ...}).catch(function(reason){ // ...});复制代码
上面代码中,promises
是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
注意:如果Promise
的参数中定义了catch
方法,则rejected
后不会触发Promise.all()
的catch
方法,因为参数中的catch
方法执行完后也会变成resolved
,当Promise.all()
方法参数的实例都是resolved
时就会调用Promise.all()
的then
方法。
const p1 = new Promise((resolve, reject) => { resolve('hello');}).then(result => result).catch(e => e);const p2 = new Promise((resolve, reject) => { throw new Error('报错了');}).then(result => result).catch(e => e);Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));// ["hello", Error: 报错了]复制代码
如果参数里面都没有catch方法,就会调用Promise.all()的catch方法。
const p1 = new Promise((resolve, reject) => { resolve('hello');}).then(result => result);const p2 = new Promise((resolve, reject) => { throw new Error('报错了');}).then(result => result);Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));// Error: 报错了复制代码
1.12.7 Promise.race()
与Promise.all
方法类似,也是将多个Promise
实例包装成一个新的Promise
实例。
const p = Promise.race([p1, p2, p3]);复制代码
与Promise.all
方法区别在于,Promise.race
方法是p1
, p2
, p3
中只要一个参数先改变状态,就会把这个参数的返回值传给p
的回调函数。
1.12.8 Promise.resolve()
将现有对象转换成 Promise
对象。
const p = Promise.resolve($.ajax('/whatever.json'));复制代码
1.12.9 Promise.reject()
返回一个rejected
状态的Promise
实例。
const p = Promise.reject('出错了');// 等同于const p = new Promise((resolve, reject) => reject('出错了'))p.then(null, function (s) { console.log(s)});// 出错了复制代码
注意,Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。
const thenable = { then(resolve, reject) { reject('出错了'); }};Promise.reject(thenable).catch(e => { console.log(e === thenable)})// true复制代码
1.13.1 Iterator遍历器概念
Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
Iterator三个作用:
for...of
消费;1.13.2 Iterator遍历过程
next
方法,可以将指针指向数据结构的第一个成员。next
方法,指针就指向数据结构的第二个成员。next
方法,直到它指向数据结构的结束位置。每一次调用next
方法,都会返回数据结构的当前成员的信息。具体来说,就是返回一个包含value
和done
两个属性的对象。
value
属性是当前成员的值;done
属性是一个布尔值,表示遍历是否结束;模拟next
方法返回值:
let f = function (arr){ var nextIndex = 0; return { next:function(){ return nextIndex < arr.length ? {value: arr[nextIndex++], done: false}: {value: undefined, done: true} } }}let a = f(['a', 'b']);a.next(); // { value: "a", done: false }a.next(); // { value: "b", done: false }a.next(); // { value: undefined, done: true }复制代码
1.13.3 默认Iterator接口
若数据可遍历,即一种数据部署了Iterator接口。
ES6中默认的Iterator接口部署在数据结构的Symbol.iterator
属性,即如果一个数据结构具有Symbol.iterator
属性,就可以认为是可遍历。Symbol.iterator
属性本身是函数,是当前数据结构默认的遍历器生成函数。执行这个函数,就会返回一个遍历器。至于属性名Symbol.iterator
,它是一个表达式,返回Symbol
对象的iterator
属性,这是一个预定义好的、类型为 Symbol 的特殊值,所以要放在方括号内(参见《Symbol》一章)。 原生具有Iterator接口的数据结构有:
1.13.4 Iterator使用场景
Set
结构进行解构赋值时,会默认调用Symbol.iterator
方法。let a = new Set().add('a').add('b').add('c');let [x, y] = a; // x = 'a' y = 'b'let [a1, ...a2] = a; // a1 = 'a' a2 = ['b','c']复制代码
...
)也会调用默认的 Iterator 接口。let a = 'hello';[...a]; // ['h','e','l','l','o']let a = ['b', 'c'];['a', ...a, 'd']; // ['a', 'b', 'c', 'd']复制代码
yield*
后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。let a = function*(){ yield 1; yield* [2,3,4]; yield 5;}let b = a();b.next() // { value: 1, done: false }b.next() // { value: 2, done: false }b.next() // { value: 3, done: false }b.next() // { value: 4, done: false }b.next() // { value: 5, done: false }b.next() // { value: undefined, done: true }复制代码
(4)其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]])
)
Promise.all()
Promise.race()
1.13.5 for...of循环
只要数据结构部署了Symbol.iterator
属性,即具有 iterator 接口,可以用for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的Symbol.iterato
方法。
for...of
可以使用在数组,Set
和Map
结构,类数组对象,Genetator对象和字符串。 for...of
循环可以代替数组实例的forEach
方法。let a = ['a', 'b', 'c'];for (let k of a){console.log(k)}; // a b ca.forEach((ele, index)=>{ console.log(ele); // a b c console.log(index); // 0 1 2 })复制代码
与for...in
对比,for...in
只能获取对象键名,不能直接获取键值,而for...of
允许直接获取键值。
let a = ['a', 'b', 'c'];for (let k of a){console.log(k)}; // a b cfor (let k in a){console.log(k)}; // 0 1 2复制代码
for (let [k,v] of b){...}
。let a = new Set(['a', 'b', 'c']);for (let k of a){console.log(k)}; // a b clet b = new Map();b.set('name','leo');b.set('age', 18);b.set('aaa','bbb');for (let [k,v] of b){console.log(k + ":" + v)};// name:leo// age:18// aaa:bbb复制代码
// 字符串let a = 'hello';for (let k of a ){console.log(k)}; // h e l l o// DOM NodeList对象let b = document.querySelectorAll('p');for (let k of b ){ k.classList.add('test');}// arguments对象function f(){ for (let k of arguments){ console.log(k); }}f('a','b'); // a b复制代码
for...of
会报错,要部署Iterator才能使用。let a = {a:'aa',b:'bb',c:'cc'};for (let k in a){console.log(k)}; // a b cfor (let k of a){console>log(k)}; // TypeError复制代码
1.13.6 跳出for...of
使用break
来实现。
for (let k of a){ if(k>100) break; console.log(k);}复制代码
1.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}复制代码
1.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}复制代码
1.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返回的是对象 和数字计算会NaNlet 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复制代码
1.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 复制代码
1.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复制代码
1.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}复制代码
1.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;复制代码
1.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复制代码
1.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();// 使用Generatorfunction * 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对象,所以用thenresult.value.then(function(data){ return data.json();}).then(function(data){ g.next(data);})复制代码
1.15.1 介绍
ES6中的class
可以看作只是一个语法糖,绝大部分功能都可以用ES5实现,并且,类和模块的内部,默认就是严格模式,所以不需要使用use strict指定运行模式。
// ES5function P (x,y){ this.x = x; this.y = y;}P.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')';};var a = new P(1, 2);// ES6class 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 (); // ReferenceErrorclass P{...};复制代码
Class的name属性:
name
属性总是返回紧跟在class
后的类名。 class P {}P.name; // 'P'复制代码
1.15.2 constructor()方法
constructor()
是类的默认方法,通过new
实例化时自动调用执行,一个类必须有constructor()
方法,否则一个空的constructor()
会默认添加。
constructor()
方法默认返回实例对象(即this
)。 class P { ... }// 等同于class P { constructor(){ ... }}复制代码
1.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') // truepoint.hasOwnProperty('y') // truepoint.hasOwnProperty('toString') // false point.__proto__.hasOwnProperty('toString') // true// toString是原型对象的属性(因为定义在Point类上)复制代码
1.15.4 Class表达式
与函数一样,类也可以使用表达式来定义,使用表达式来作为类的名字,而class
后跟的名字,用来指代当前类,只能再Class内部使用。
let a = class P{ get(){ return P.name; }}let b = new a();b.get(); // PP.name; // ReferenceError: P is not defined复制代码
如果类的内部没用到的话,可以省略P
,也就是可以写成下面的形式。
let a = class { ... }复制代码
1.15.5 私有方法和私有属性
由于ES6不提供,只能变通来实现:
_
,但是不保险,外面也可以调用到。class P { // 公有方法 f1 (x) { this._x(x); } // 私有方法 _x (x){ return this.y = x; }}复制代码
call
方法。class P { f1 (x){ f2.call(this, x); }}function f2 (x){ return this.y = x;}复制代码
Symbol
为私有方法命名。const a1 = Symbol('a1');const a2 = Symbol('a2');export default class P{ // 公有方法 f1 (x){ this[a1](x); } // 私有方法 [a1](x){ return this[a2] = x; }}复制代码
1.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类的实例,所以报错复制代码
解决方法:
this
class P { constructor(){ this.name = this.name.bind(this); }}复制代码
class P{ constructor(){ this.name = (name = 'leo' )=>{ this.print(`my name is ${name}`) } }}复制代码
1.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 : 100a.f; // getter复制代码
1.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'复制代码
1.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'复制代码
1.15.10 Class的静态属性和实例属性
ES6中明确规定,Class内部只有静态方法没有静态属性,所以只能通过下面实现。
// 正确写法class P {}P.a1 = 1;P.a1; // 1// 无效写法class P { a1: 2, // 无效 static a1 : 2, // 无效}P.a1; // undefined复制代码
新提案来规定实例属性和静态属性的新写法
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 } } }复制代码
static
关键字就可以。class P { static prop = 100; constructor(){console.log(this.prop)}; // 100}复制代码
新写法方便静态属性的表达。
// old class P { .... }P.a = 1;// new class P { static a = 1;}复制代码
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 调用superclass Q extends P { constructor(){ ... }}let a = new Q(); // ReferenceError 因为Q没有调用super// 关键点2 调用superclass R extends P { constructor (x, y. z){ this.z = z; // ReferenceError 没调用super不能使用 super(x, y); this.z = z; // 正确 }}// 关键点3 子类继承父类静态方法R.hello(); // 'hello leo'复制代码
super关键字:
既可以当函数使用,还可以当对象使用。class P {... };class R extends P { constructor(){ super(); }}复制代码
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// 情况3class J extends P { constructor(){ super(); this.a = 3; } g(){ super.print(); }}let c = new J();c.g(); // 3 由于执行了super()后 this指向当前实例复制代码
1.16.1 介绍
ES6之前用于JavaScript的模块加载方案,是一些社区提供的,主要有CommonJS
和AMD
两种,前者用于服务器,后者用于浏览器。
export
命令对外暴露接口,使用import
命令输入其他模块暴露的接口。 // CommonJS模块let { stat, exists, readFire } = require('fs');// ES6模块import { stat, exists, readFire } = from 'fs';复制代码
1.16.2 严格模式
ES6模块自动采用严格模式,无论模块头部是否有"use strict"
。
with
语句delete prop
,会报错,只能删除属性delete * global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化arguments.callee
arguments.caller
this
指向全局对象fn.caller
和fn.arguments
获取函数调用的堆栈protected
、static
和interface
)特别是,ES6中顶层this
指向undefined
,即不应该在顶层代码使用this
。
1.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};复制代码
1.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
只会执行一次。
1.16.5 模块的整体加载
当一个模块暴露多个方法和变量,引用时可以用*
整体加载。
// a.jsexport function f(){...}export function g(){...}// b.jsimport * 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(){...}; 复制代码
1.16.6 export default 命令
使用export default
命令,为模块指定默认输出,引用的时候直接指定任意名称即可。
// a.jsexport default function(){console.log('leo')};// b.jsimport leo from './a.js';leo(); // 'leo'复制代码
export defualt
暴露有函数名的函数时,在调用时相当于匿名函数。
// a.jsexport default function f(){console.log('leo')};// 或者function f(){console.log('leo')};export default f;// b.jsimport leo from './a.js';复制代码
export defualt
其实是输出一个名字叫default
的变量,所以后面不能跟变量赋值语句。
// 正确export let a= 1;let a = 1;export defualt a;// 错误export default let a = 1;复制代码
export default
命令的本质是将后面的值,赋给default
变量,所以可以直接将一个值写在export default
之后。
// 正确export default 1;// 错误export 1;复制代码
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';复制代码
常常用在模块继承。
1.16.8 浏览器中的加载规则
ES6中,可以在浏览器使用<script>
标签,需要加入type="module"
属性,并且这些都是异步加载,避免浏览器阻塞,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>
标签的defer
属性。
复制代码
另外,ES6模块也可以内嵌到网页,语法与外部加载脚本一致:
复制代码
注意点:
use strict
。import
命令加载其他模块(.js
后缀不可省略,需要提供绝对 UR
L 或相对 UR
L),也可以使用export
命令输出对外接口。this
关键字返回undefined
,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无意义的。includes()
用于查找一个值是否在数组中,如果在返回true
,否则返回false
。
['a', 'b', 'c'].includes('a'); // true['a', 'b', 'c'].includes('d'); // false复制代码
includes()
方法接收两个参数,搜索的内容和开始搜索的索引,默认值为0,若搜索值在数组中则返回true
否则返回false
。
['a', 'b', 'c', 'd'].includes('b'); // true['a', 'b', 'c', 'd'].includes('b', 1); // true['a', 'b', 'c', 'd'].includes('b', 2); // false复制代码
与indexOf
方法对比,下面方法效果相同:
['a', 'b', 'c', 'd'].indexOf('b') > -1; // true['a', 'b', 'c', 'd'].includes('b'); // true 复制代码
includes()与indexOf对比:
includes
相比indexOf
更具语义化,includes
返回的是是否存在的具体结果,值为布尔值,而indexOf
返回的是搜索值的下标。includes
相比indexOf
更准确,includes
认为两个NaN
相等,而indexOf
不会。let a = [1, NaN, 3];a.indexOf(NaN); // -1a.includes(NaN); // true复制代码
另外在判断+0
与-0
时,includes
和indexOf
的返回相同。
[1, +0, 3, 4].includes(-0); // true[1, +0, 3, 4].indexOf(-0); // 1复制代码
基本用法:
let a = 3 ** 2 ; // 9// 等效于Math.pow(3, 2); // 9复制代码
**
是一个运算符,也可以满足类似假发的操作,如下:
let a = 3;a **= 2; // 9复制代码
3.1.1 介绍
ES8引入async
函数,是为了使异步操作更加方便,其实它就是Generator函数的语法糖。
async
函数使用起来,只要把Generator函数的**(*)**号换成async
,yield
换成await
即可。对比如下: // Generator写法const fs = require('fs');const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); });};const gen = function* () { const f1 = yield readFile('/etc/fstab'); const f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString());};// async await写法const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab'); const f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString());};复制代码
对比Genenrator有四个优点:
async
函数自带执行器,即async
函数与普通函数一模一样:async f();复制代码
async
和await
,比起星号
和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then
方法指定下一步的操作。进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
3.1.2 基本用法
async
函数返回一个Promise对象,可以使用then
方法添加回调函数,函数执行时,遇到await
就会先返回,等到异步操作完成,在继续执行。
async function f(item){ let a = await g(item); let b = await h(item); return b;}f('hello').then(res => { console.log(res);})复制代码
async
表明该函数内部有异步操作,调用该函数时,会立即返回一个Promise对象。
function f (ms){ return new Promise(res => { setTimeout(res, ms); });}async function g(val, ms){ await f(ms); console.log(val);}g('leo', 50);复制代码
async
函数还有很多使用形式:
// 函数声明async function f (){...}// 函数表达式let f = async function (){...}// 对象的方法let a = { async f(){...}}a.f().then(...)// Class的方法class c { constructor(){...} async f(){...}}// 箭头函数let f = async () => {...}复制代码
3.1.3 返回Promise对象
async
内部return
返回的值会作为then
方法的参数,另外只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
async function f(){ return 'leo';}f().then(res => { console.log (res) }); // 'leo'复制代码
async
内部抛出的错误,会被catch
接收。
async function f(){ throw new Error('err');}f().then ( v => console.log(v), e => console.log(e))// Error: err复制代码
3.1.4 await命令
通常await
后面是一个Promise对象,如果不是就返回对应的值。
async function f(){ return await 10;}f().then(v => console.log(v)); // 10复制代码
我们常常将async await
和try..catch
一起使用,并且可以放多个await
命令,也是防止异步操作失败因为中断后续异步操作的情况。
async function f(){ try{ await Promise.reject('err'); }catch(err){ ... } return await Promise.resolve('leo');}f().then(v => console.log(v)); // 'leo'复制代码
3.1.5 使用注意
await
命令放在try...catch
代码块中,防止Promise返回rejected
。await
后面的异步操作不存在继发关系,最好让他们同时执行。// 效率低let a = await f();let b = await g();// 效率高let [a, b] = await Promise.all([f(), g()]);// 或者let a = f();let b = g();let a1 = await a();let b1 = await b();复制代码
await
命令只能用在async
函数之中,如果用在普通函数,就会报错。// 错误async function f(){ let a = [{}, {}, {}]; a.forEach(v =>{ // 报错,forEach是普通函数 await post(v); });}// 正确async function f(){ let a = [{}, {}, {}]; for(let k of a){ await post(k); }}复制代码
finally()
是ES8中Promise添加的一个新标准,用于指定不管Promise对象最后状态(是fulfilled
还是rejected
)如何,都会执行此操作,并且finally
方法必须写在最后面,即在then
和catch
方法后面。
// 写法如下: promise .then(res => {...}) .catch(err => {...}) .finally(() => {...})复制代码
finally
方法常用在处理Promise请求后关闭服务器连接:
server.listen(port) .then(() => {..}) .finally(server.stop);复制代码
本质上,finally方法是then方法的特例:
promise.finally(() => {...});// 等同于promise.then( result => { // ... return result }, error => { // ... throw error })复制代码
ES7中新增加的 Object.values()
和Object.entries()
与之前的Object.keys()
类似,返回数组类型。
Object.keys()
: var a = { f1: 'hi', f2: 'leo'};Object.keys(a); // ['f1', 'f2']复制代码
3.3.1 Object.values()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值。
let a = { f1: 'hi', f2: 'leo'};Object.values(a); // ['hi', 'leo']复制代码
如果参数不是对象,则返回空数组:
Object.values(10); // []Object.values(true); // []复制代码
3.3.2 Object.entries()
返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组。
let a = { f1: 'hi', f2: 'leo'};Object.entries(a); // [['f1','hi'], ['f2', 'leo']]复制代码
let a = { f1: 'hi', f2: 'leo'};for (let [k, v] of Object.entries(a)){ console.log( `${JSON.stringfy(k)}:${JSON.stringfy(v)}` )}// 'f1':'hi'// 'f2':'leo'复制代码
let a = { f1: 'hi', f2: 'leo'};let map = new Map(Object.entries(a));复制代码
手动实现Object.entries()
方法:
// Generator函数实现: function* entries(obj){ for (let k of Object.keys(obj)){ yield [k ,obj[k]]; }}// 非Generator函数实现:function entries (obj){ let arr = []; for(let k of Object.keys(obj)){ arr.push([k, obj[k]]); } return arr;}复制代码
之前有Object.getOwnPropertyDescriptor
方法会返回某个对象属性的描述对象,新增的Object.getOwnPropertyDescriptors()
方法,返回指定对象所有自身属性(非继承属性)的描述对象,所有原对象的属性名都是该对象的属性名,对应的属性值就是该属性的描述对象
let a = { a1:1, get f1(){ return 100}}Object.getOwnPropetyDescriptors(a);/*{ a:{ configurable:true, enumerable:true, value:1, writeable:true} f1:{ configurable:true, enumerable:true, get:f, set:undefined}}*/复制代码
实现原理:
function getOwnPropertyDescriptors(obj) { const result = {}; for (let key of Reflect.ownKeys(obj)) { result[key] = Object.getOwnPropertyDescriptor(obj, key); } return result;}复制代码
引入这个方法,主要是为了解决Object.assign()
无法正确拷贝get
属性和set
属性的问题。
let a = { set f(v){ console.log(v) }}let b = {};Object.assign(b, a);Object.a(b, 'f');/*f = { configurable: true, enumable: true, value: undefined, writeable: true}*/复制代码
value
为undefined
是因为Object.assign
方法不会拷贝其中的get
和set
方法,使用getOwnPropertyDescriptors
配合Object.defineProperties
方法来实现正确的拷贝:
let a = { set f(v){ console.log(v) }}let b = {};Object.defineProperties(b, Object.getOwnPropertyDescriptors(a));Object.getOwnPropertyDescriptor(b, 'f')/* configurable: true, enumable: true, get: undefined, set: function(){...}*/复制代码
Object.getOwnPropertyDescriptors
方法的配合Object.create
方法,将对象属性克隆到一个新对象,实现浅拷贝。
const clone = Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));// 或者const shallowClone = (obj) => Object.create( Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));复制代码
用来为字符串填充特定字符串,并且都有两个参数:字符串目标长度和填充字段,第二个参数可选,默认空格。
'es8'.padStart(2); // 'es8''es8'.padStart(5); // ' es8''es8'.padStart(6, 'woof'); // 'wooes8''es8'.padStart(14, 'wow'); // 'wowwowwowwoes8''es8'.padStart(7, '0'); // '0000es8''es8'.padEnd(2); // 'es8''es8'.padEnd(5); // 'es8 ''es8'.padEnd(6, 'woof'); // 'es8woo''es8'.padEnd(14, 'wow'); // 'es8wowwowwowwo''es8'.padEnd(7, '6'); // 'es86666'复制代码
从上面结果来看,填充函数只有在字符长度小于目标长度时才有效,若字符长度已经等于或小于目标长度时,填充字符不会起作用,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出。
该特性允许我们在定义或者调用函数时添加尾部逗号而不报错:
function es8(var1, var2, var3,) { // ...}es8(10, 20, 30,);复制代码
当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。这部分主要介绍了 ES8 中新的构造函数 SharedArrayBuffer
以及拥有许多静态方法的命名空间对象 Atomic
。
Atomic
对象类似于 Math
对象,拥有许多静态方法,所以我们不能把它当做构造函数。 Atomic
对象有如下常用的静态方法: 4.1.1 介绍
对象的拓展运算符,即对象的Rest/Spread属性,可将对象解构赋值用于从一个对象取值,搜键值对分配到指定对象上,与数组的拓展运算符类似:
let {x, y, ...z} = {x:1, y:2, a:3, b:4};x; // 1y; // 2z; // {a:3, b:4} 复制代码
对象的解构赋值要求等号右边必须是个对象,所以如果等号右边是undefined
或null
就会报错无法转成对象。
let {a, ...b} = null; // 运行时报错let {a, ...b} = undefined; // 运行时报错复制代码
解构赋值必须是最后一个参数,否则报错。
let {...a, b, c} = obj; // 语法错误let {a, ...b, c} = obj; // 语法错误复制代码
注意:
let a = {a1: {a2: 1}};let {...b} = a;a.a1.a2 = 'leo';b.a1.a2 = 'leo';复制代码
let o1 = { a: 1 };let o2 = { b: 2 };o2.__proto__ = o1;let { ...o3 } = o2;o3; // { b: 2 }o3.a; // undefined复制代码
4.1.2 使用场景
let a = { a1:1, a2:2 };let b = { ...a };b; // { a1:1, a2:2 }// 类似Object.assign方法复制代码
let a = { a1:1, a2:2 };let b = { b1:11, b2:22 };let ab = { ...a, ...b }; // {a1: 1, a2: 2, b1: 11, b2: 22}// 等同于let ab = Object.assign({}, a, b);复制代码
let a = { a1:1, a2:2, a3:3 };let r = { ...a, a3:666 }; // r {a1: 1, a2: 2, a3: 666}// 等同于let r = { ...a, ...{ a3:666 }};// r {a1: 1, a2: 2, a3: 666}// 等同于let r = Object.assign({}, a, { a3:666 });// r {a1: 1, a2: 2, a3: 666}复制代码
let a = { a1:1, a2:2 };let r = { a3:666, ...a };// r {a3: 666, a1: 1, a2: 2}// 等同于let r = Object.assign({}, {a3:666}, a);// r {a3: 666, a1: 1, a2: 2}// 等同于let r = Object.assign({a3:666}, a);// r {a3: 666, a1: 1, a2: 2}复制代码
let a = { ...(x>1? {a:!:{}), b:2}复制代码
{...{}, a:1}; // {a:1}复制代码
null
或undefined
则忽略且不报错。let a = { ...null, ...undefined }; // 不报错复制代码
get
则会执行。// 不会打印 因为f属性只是定义 而不没执行let a = { ...a1, get f(){console.log(1)}}// 会打印 因为f执行了let a = { ...a1, ...{ get f(){console.log(1)} }}复制代码
在正则表达式中,点(.
)可以表示任意单个字符,除了两个:用u
修饰符解决四个字节的UTF-16字符,另一个是行终止符。
/foo.bar/.test('foo\nbar')// false复制代码
上面代码中,因为.
不匹配\n
,所以正则表达式返回false
。
/foo[^]bar/.test('foo\nbar')// true复制代码
ES9引入s
修饰符,使得.
可以匹配任意单个字符:
/foo.bar/s.test('foo\nbar') // true复制代码
这被称为dotAll
模式,即点(dot
)代表一切字符。所以,正则表达式还引入了一个dotAll
属性,返回一个布尔值,表示该正则表达式是否处在dotAll
模式。
const re = /foo.bar/s;// 另一种写法// const re = new RegExp('foo.bar', 's');re.test('foo\nbar') // truere.dotAll // truere.flags // 's'复制代码
/s
修饰符和多行修饰符/m
不冲突,两者一起使用的情况下,.
匹配所有字符,而^
和$
匹配每一行的行首和行尾。
在前面ES6章节中,介绍了Iterator接口,而ES6引入了“异步遍历器”,是为异步操作提供原生的遍历器接口,即value
和done
这两个属性都是异步产生的。
4.3.1 异步遍历的接口
通过调用遍历器的next
方法,返回一个Promise对象。
a.next().then( ({value, done}) => { //... })复制代码
上述a
为异步遍历器,调用next
后返回一个Promise对象,再调用then
方法就可以指定Promise对象状态变为resolve
后执行的回调函数,参数为value
和done
两个属性的对象,与同步遍历器一致。
Symbol.asyncIterator
属性上,只要有这个属性,就都可以异步遍历。 let a = createAsyncIterable(['a', 'b']);//createAsyncIterable方法用于构建一个iterator接口let b = a[Symbol.asyncInterator]();b.next().then( result1 => { console.log(result1); // {value: 'a', done:false} return b.next();}).then( result2 => { console.log(result2); // {value: 'b', done:false} return b.next();}).then( result3 => { console.log(result3); // {value: undefined, done:true}})复制代码
另外next
方法返回的是一个Promise对象,所以可以放在await
命令后。
async function f(){ let a = createAsyncIterable(['a', 'b']); let b = a[Symbol.asyncInterator](); console.log(await b.next());// {value: 'a', done:false} console.log(await b.next());// {value: 'b', done:false} console.log(await b.next());// {value: undefined, done:true}}复制代码
还有一种情况,使用Promise.all
方法,将所有的next
按顺序连续调用:
let a = createAsyncIterable(['a', 'b']);let b = a[Symbol.asyncInterator]();let { {value:v1}, {value:v2}} = await Promise.all([ b.next(), b.next()])复制代码
也可以一次调用所有next
方法,再用await
最后一步操作。
async function f(){ let write = openFile('aaa.txt'); write.next('hi'); write.next('leo'); await write.return();}f();复制代码
4.3.2 for await...of
for...of
用于遍历同步的Iterator接口,而ES8引入for await...of
遍历异步的Iterator接口。
async function f(){ for await(let a of createAsyncIterable(['a', 'b'])) { console.log(x); }}// a// b复制代码
上面代码,createAsyncIterable()
返回一个拥有异步遍历器接口的对象,for...of
自动调用这个对象的next
方法,得到一个Promise对象,await
用来处理这个Promise,一但resolve
就把得到的值x
传到for...of
里面。
let a = '';async function f(){ for await (let b of req) { a += b; } let c = JSON.parse(a); console.log('leo', c);}复制代码
当next
返回的Promise对象被reject
,for await...of
就会保错,用try...catch
捕获。
async function f(){ try{ for await (let a of iterableObj()){ console.log(a); } }catch(e){ console.error(e); }}复制代码
注意,for await...of
循环也可以用于同步遍历器。
(async function () { for await (let a of ['a', 'b']) { console.log(a); }})();// a// b复制代码
4.3.3 异步Generator函数
就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
在语法上,异步 Generator 函数就是async
函数与 Generator 函数的结合。 async function* f() { yield 'hi';}const a = f();a.next().then(x => console.log(x));// { value: 'hello', done: false }复制代码
设计异步遍历器的目的之一,就是为了让Generator函数能用同一套接口处理同步和异步操作。
// 同步Generator函数function * f(iterable, fun){ let a = iterabl[Symbol.iterator](); while(true){ let {val, done} = a.next(); if(done) break; yield fun(val); }}// 异步Generator函数async function * f(iterable, fun){ let a = iterabl[Symbol.iterator](); while(true){ let {val, done} = await a.next(); if(done) break; yield fun(val); }}复制代码
同步和异步Generator函数相同点:在yield
时用next
方法停下,将后面表达式的值作为next()
返回对象的value
。
await
和yield
,简单样理解,await
命令用于将外部操作产生的值输入函数内部,yield
命令用于将函数内部的值输出。 (async function () { for await (const line of readLines(filePath)) { console.log(line); }})()复制代码
异步 Generator 函数可以与for await...of
循环结合起来使用。
async function* f(asyncIterable) { for await (const line of asyncIterable) { yield '> ' + line; }}复制代码
4.3.4 yield* 语句
yield*
语句跟一个异步遍历器。
async function * f(){ yield 'a'; yield 'b'; return 'leo';}async function * g(){ const a = yield* f(); // a => 'leo'}复制代码
与同步 Generator 函数一样,for await...of
循环会展开yield*
。
(async function () { for await (const x of gen2()) { console.log(x); }})();// a// b复制代码
通常指一个函数内部,或者一个代码块内部。
比如:function fun1 () { // 块级作用域 if (true) { // 块级作用域 }}复制代码
缺点: 1.导致内层变量覆盖外层变量
var a1 = new Date();function f1 (){ console.log(a1); // undefined if (false) { var a1 = 'hello' }}复制代码
输出 undefined
是因为 if
内的 a1
变量声明的变量提升,导致内部的 a1
覆盖外部的 a1
,所以输出为 undefined
。
2.变量的全局污染
var a = 'hello';for (var i = 0; i< a.length; i++) { //...}console.log(i); // 5复制代码
循环结束后,变量 i
的值依然存在,造成变量的全局污染。
数组的空位不是undefined
,而是没有任何值,数组的undefined
也是有值。
0 in [undefined,undefined,undefined] // true0 in [,,,] // false复制代码
ES5对空位的处理:
forEach()
, filter()
, reduce()
, every()
和some()
都会跳过空位。map()
会跳过空位,但会保留这个值。join()
和toString()
会将空位视为undefined
,而undefined
和null
会被处理成空字符串。[,'a'].forEach((x,i) => console.log(i)); // 1['a',,'b'].filter(x => true); // ['a','b'][,'a'].every(x => x==='a'); // true[1,,2].reduce((x,y) => x+y); // 3[,'a'].some(x => x !== 'a'); // false[,'a'].map(x => 1); // [,1][,'a',undefined,null].join('#'); // "#a##"[,'a',undefined,null].toString(); // ",a,,"复制代码
ES6对空位的处理:
将空位视为正常值,转成undefined
。 Array.from(['a',,'b']);// [ "a", undefined, "b" ][...['a',,'b']]; // [ "a", undefined, "b" ]//copyWithin() 会连空位一起拷贝。 [,'a','b',,].copyWithin(2,0) // [,"a",,"a"]//fill()会将空位视为正常的数组位置。new Array(3).fill('a') // ["a","a","a"]//for...of循环也会遍历空位。let arr = [, ,];for (let i of arr) { console.log(1);} // 1 1复制代码
entries()
、keys()
、values()
、find()
和findIndex()
会将空位处理成undefined
。
[...[,'a'].entries()] // [[0,undefined], [1,"a"]][...[,'a'].keys()] // [0,1][...[,'a'].values()] // [undefined,"a"][,'a'].find(x => true) // undefined[,'a'].findIndex(x => true) // 0复制代码
由于空位的处理规则非常不统一,所以建议避免出现空位。