ES6
一、 变量赋值与解构
- 默认值赋值注意点:
- 只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined]; x // 1 let [x = 1] = [null]; x // null
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1 let [x = 1, y = x] = [2]; // x=2; y=2 let [x = 1, y = x] = [1, 2]; // x=1; y=2 let [x = y, y = 1] = []; // ReferenceError: y is not defined
-
变量的解构注意点:
- 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
- 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
- 为了区分模式,还是表达式 --> 只要有可能导致解构的歧义,就不得使用圆括号。
//变量申明 let [(a)] = [1]; let {x: (c)} = {}; // 函数参数 function f([(z)]) { return z; } function f([z,(x)]) { return x; } //赋值语句的模式部分 ({ p: a }) = { p: 42 }; ([a]) = [5]; //可以使用(赋值语句的非模式部分) [(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
> 与`for...of`配合 获取键名和键值就非常方便。
二、- 字符串拓展
三、- 正则拓展
- 详见阮一峰老师网站-RegExp
-
u修饰符,含义为“Unicode 模式”
-
y 修饰符“粘连”(sticky)修饰符:确保匹配必须从剩余的第一个位置开始
-
‘后行断言’
-
先行断言:
x
只有在y
前面才匹配,必须写成/x(?=y)/
比如:只匹配百分号之前的数字,要写成/\d+(?=%)/
-
先行否定断言:
x
只有不在y
前面才匹配,必须写成/x(?!y)/
-
后行断言:
x
只有在y
后面才匹配,必须写成/(?<=y)x/
如:只匹配美元符号之后的数字,要写成/(?<=\$)\d+/
-
后行否定断言:
x
只有不在y
后面才匹配,必须写成/(?<!y)x/
后行断言与正常情况顺序是相反的,为“先右后左”的执行顺序
-
-
具名组匹配:允许为每一个组匹配指定一个名字;
引用某个“具名组匹配”\k<组名>
或者\1
具名组匹配在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?<year>
),然后就可以在exec方法返回结果的groups属性上引用该组名。const REG = /^(?<word>[a-z]+)!\k<word>!\1$/; REG.test('abc!abc!abc') // true REG.test('abc!abc!ab') // false
-
四、- 数值的拓展
- 二进制和八进制表示法(前缀
0b | 0o
) - Number.isFinite(), Number.isNaN()
- Number.parseInt(), Number.parseFloat()
- Number.isInteger()
- Number.EPSILON(最小精度)
2 ** -52
- 安全整数和 Number.isSafeInteger(): 判断是否在安全值范围
- Math 对象的扩展
- 指数运算符
**
五、- 函数的扩展
-
函数参数的默认值
-
rest 参数 …props
-
严格模式
-
name 属性
-
箭头函数 =>this引用的外层函数
注意点:- (1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- (2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- (3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- (4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
-
双冒号运算符
foo::bar; // 等同于 bar.bind(foo); var method = obj::obj.foo; // 等同于 var method = ::obj.foo;
-
尾调用:(指某个函数的最后一步是调用另一个函数)优化
- 只保留内层调用
注意点:只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”。
-
尾递归:尾调用自身
//计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n) 。 function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); } factorial(5) // 120 //尾递归优化,只保留一个调用记录,复杂度 O(1) 。 function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); } factorial(5) // 120
tips:把所有用到的内部变量改写成函数的参数
六、- 数组的扩展
-
扩展运算符(
...[]
)的应用-
转成参数序列
-
复制数组
const a1 = [1, 2]; const a2 = a1.concat(); //es5 const a3 = [...a1]; //es6 const [...a4] = a1;
-
合并数组
[...arr1, ...arr2, ...arr3]
(浅拷贝) -
解构赋值
const [first, ...rest] = [1, 2, 3, 4, 5]
-
正确返回字符长度
[...str].length
'x\uD83D\uDE80y'.length // 4 [...'x\uD83D\uDE80y'].length // 3
-
实现
Iterator
接口的对象.
任何Iterator
接口的对象,都可以用扩展运算符转为真正的数组。
对于那些没有部署Iterator
接口的类似数组的对象,使用Array.from(arrayLike)
,实际上任何有length属性的对象,都可以通过Array.from方法转为数组。
-
-
Array.from()
:- 用于将两类对象转为真正的数组:类似数组的对象和可遍历的对象(包括 ES6 新增的数据结构 Set 和 Map)。
Array.from
还可以接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
Array.from(arrayLike, x => x * x); // 等同于 Array.from(arrayLike).map(x => x * x); Array.from([1, 2, 3], (x) => x * x) // [1, 4, 9]
-
Array.of()
:
- 用于将一组值,转换为数组。
- 为了弥补构造函数`Array()`的不足
```
//参数个数不一样行为不同
Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]
```
`Array.of`总是返回参数值组成的数组。如果没有参数,就返回一个空数组。
-
数组实例的
copyWithin()
- 将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。。
Array.prototype.copyWithin(target, start = 0, end = this.length)
-
数组实例的
fill()
-
可用于填充数组,数组中已有的元素,会被全部抹去。
-
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c', 'd'].fill(7, 1, 3) // ['a', 7, 7, 'd'] //> 注意,如果填充的类型为对象,那么被赋值的是同一个内存地址的对象,而不是深拷贝对象。 let arr = new Array(3).fill([]); arr[0].push(5); // arr = [[5], [5], [5]]
-
-
数组实例的
entries()
,keys()
和values()
-
用
for...of
循环进行遍历,keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。for (let index of ['a', 'b'].keys()) { console.log(index); } // 0 // 1 for (let elem of ['a', 'b'].values()) { console.log(elem); } // 'a' // 'b' for (let [index, elem] of ['a', 'b'].entries()) { console.log(index, elem); }1 // 0 "a" // 1 "b"
-
-
数组实例的
includes()
- 表示某个数组是否包含给定的值(第二个参数表示搜索的起始位置),与字符串的includes方法类似
[1, 2, 3].includes(3, 3) // false [1, 2, 3].includes(3, -1) // true [1, 2, [3]].includes([3]) // false [NaN].includes(NaN) // true
-
数组实例的
flat()
,flatMap()
—Chrome无此方法(2018/8/21)-
flat()
用于将嵌套的数组“拉平”,变成一维的数组。参数:想要拉平的层数,默认为1。[1, 2, [3, [4, 5]]].flat() // [1, 2, 3, [4, 5]] //如果原数组有空位,flat()方法会跳过空位。 [1, 2, , 4, 5].flat() // [1, 2, 4, 5] [1, [2, [3]]].flat(Infinity) //Infinity:不管有多少层嵌套,都要转成一维数组 // [1, 2, 3]
-
flatMap()
方法对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组。传入函数(类似map
),只能展开一层
-
-
数组的空位的处理
-
ES5:
- forEach(), filter(), reduce(), every() 和some()都会跳过空位。
- map()会跳过空位,但会保留这个值
- join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
-
ES6:
-
明确将空位转为undefined
[...['a',,'b']] // [ "a", undefined, "b" ]
-
-
七、- 对象的扩展
-
简写
{foo} === {foo: foo}
-
Object.is()
:+0 === -0 //true NaN === NaN // false Object.is(+0, -0) // false Object.is(NaN, NaN) // true
八、Symbol类型:表示独一无二的值
- Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
- Symbol 值不能与其他类型的值进行运算,会报错。
- Symbol 值可以显式转为字符串|布尔值但不能转为数值。
- Symbol 作为属性名,该属性不会出现在
for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols
方法,可以获取指定对象的所有 Symbol 属性名。
九、 Set 和 Map 数据结构
- Set: 它类似于数组,但是成员的值都是唯一的,没有重复的值。
const set = new Set([1, 2, 3, 4, 4, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 去除数组的重复成员
[...new Set(array)]
//在 Set 内部,两个NaN是相等。
//另外,两个对象总是不相等的。
- add(value):添加某个值,返回 Set 结构本身。
- delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
- has(value):返回一个布尔值,表示该值是否为Set的成员。
- clear():清除所有成员,没有返回值。
-
WeakSet :与 Set 类似,但
WeakSet
的成员只能是对象,而不能是其他类型的值。
没有size属性,没有办法遍历它的成员。 -
Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
const map = new Map(); const k1 = ['a']; const k2 = ['a']; map .set(k1, 111) .set(k2, 222); map.get(k1) // 111 map.get(k2) // 222
-
WeakMap :与Map结构类似,也是用于生成键值对的集合。
- 区别:
-
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
-
WeakMap的键名所指向的对象,不计入垃圾回收机制。
const wm = new WeakMap(); const element = document.getElementById('example'); wm.set(element, 'some information'); wm.get(element) // "some information" /* 先新建一个 Weakmap 实例。然后,将一个 DOM 节点作为键名存入该实例, 并将一些附加信息作为键值,一起存放在 WeakMap 里面。这时,WeakMap 里面对element 的引用就是弱引用,不会被计入垃圾回收机制即DOM节点对象的引用计数是1,而不是2。 可以有效防止内存泄漏。 (WeakMap 的另一个用处是部署私有属性。如果删除实例,它们也就随之消失) */
-
- 区别:
十、 Proxy
-
含义:在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。(代理器)
-
new Proxy(target, handler)
-
Proxy支持的拦截操作
- 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)
。
- get(target, propKey, receiver):拦截对象属性的读取,比如
十一、 Reflect
-
从Reflect对象上可以拿到语言内部的方法。
-
13个静态方法
-
Reflect.apply(target, thisArg, args)
-
Reflect.construct(target, args)
-
Reflect.get(target, name, receiver)
const myObject = { foo: 1, bar: 2, get baz() { return this.foo + this.bar; }, } console.log(Reflect.get(myObject, 'foo')) // 1 console.log(Reflect.get(myObject, 'bar')) // 2 console.log(Reflect.get(myObject, 'baz')) // 3
-
Reflect.set(target, name, value, receiver)
myObject.foo // 1 Reflect.set(myObject, 'foo', 2); myObject.foo // 2
-
Reflect.defineProperty(target, name, desc)
// 新写法 Reflect.defineProperty(MyDate, 'now', { value: new Date() }) console.log(MyDate.now)
-
Reflect.deleteProperty(target, name)
-
Reflect.has(target, name)
-
Reflect.ownKeys(target)
-
Reflect.isExtensible(target)
-
Reflect.preventExtensions(target)
-
Reflect.getOwnPropertyDescriptor(target, name)
const myObject = {}; Reflect.defineProperty(myObject, 'hidden', { value: true, writable:true, }); const theDescriptor = Reflect.getOwnPropertyDescriptor(myObject,'hidden'); console.log(theDescriptor) //{value: true, writable: true, enumerable: false, configurable: false} myObject.hidden = 1 console.log(myObject) //{hidden: 1}
-
Reflect.getPrototypeOf(target)
-
Reflect.setPrototypeOf(target, prototype)
-
-
实例:使用 Proxy 实现观察者模式
const queuedObservers = new Set(); const observe = fn => queuedObservers.add(fn); const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); queuedObservers.forEach(observer => observer()); return result; } const person = observable({ name: '张三', }); function print() { console.log(`${person.name}`) } observe(print); person.name = '李四'; //李四 person.name = '33'; //33
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者—这跟vue很相似
十二、 Promise 对象
-
含义:Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。
-
Promise.prototype.then()
:
then方法返回的是一个新的Promise实例。因此可以采用链式写法 -
Promise.prototype.catch()
:
.then(null, rejection)
的别名,一般建议总是使用catch
方法。catch
方法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。
如果没有使用catch方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码
-
Promise.prototype.finally()
:
不管 Promise 对象最后状态如何,都会执行的操作。promise .finally(() => { // 语句 }); // 等同于 promise .then( result => { // ... return result; }, error => { //... throw error; } )
-
Promise.all()
:
const p = Promise.all([p1, p2, p3]);
注意:
- (
Promise.all
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。) - 只有所有的状态都变成
fulfilled
,返回值组成一个数组,传递给回调函数,
或者有一个变为rejected
,被reject
的实例的返回值才会继续传递给回调函数。
- (
-
Promise.race()
:
const p = Promise.race([p1, p2, p3]);
只要有一个状态改变就。。。//当接口五秒内未返回就报超时错误 const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p .then(console.log) .catch(console.error);
-
Promise.resolve()
:Promise.resolve('foo') // 等价于(可以将现有对象转成Promise对象) new Promise(resolve => resolve('foo'))
- 参数是个Promise 实例:不做修改直接返回这个实例
- 参数是个具有then方法的对象:先转Promise然后立即执行then方法
- 参数不是具有then方法的对象,或根本就不是对象:返回状态为resolved的Promise对象
不带有任何参数:直接返回一个resolved状态的 Promise 对象。
-
Promise.reject()
:
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。 -
Promise.try()
:
不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它时。解决方案1 :
const f = () => console.log('now'); (async () => f())() console.log('next') // now // next
解决方案2 :
const f = () => console.log('now'); ( () => new Promise( resolve => resolve(f()) ) )(); console.log('next'); // now // next
十三、Iterator 和 for…of 循环
①. Iterator
-
凡是部署了
Symbol.iterator
属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
原生具备Iterator
接口的数据结构 (可以用for…of遍历它们):- Array
- Map
- Set
- String
- TypedArray
- 函数的 arguments 对象
- NodeList 对象
-
默认调用
Symbol.iterator
方法的情况- 对数组和 Set 结构进行解构赋值时
- 使用扩展运算符(…)
- yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
const generator = function* () { yield* [1,2]; }; var iterator = generator(); iterator.next() // { value: 1, done: false } iterator.next() // { value: 2, done: false } iterator.next() // { value: undefined, done: true }
- 数组的遍历会调用遍历器接口
- for…of
- Array.from()
- Map(), Set(), WeakMap(), WeakSet()(比如new Map([[‘a’,1],[‘b’,2]]))
- Promise.all()
- Promise.race()
…
-
字符串是一个类似数组的对象,也原生具有 Iterator 接口。
-
遍历器对象的 return()
可以触发return的情况
// 情况一:break for (let line of readLinesSync(fileName)) { console.log(line); break; } // 情况二:Error for (let line of readLinesSync(fileName)) { console.log(line); throw new Error(); }
- 情况一输出文件的第一行以后,就会执行return方法,关闭这个文件;
- 情况二会在执行return方法关闭文件之后,再抛出错误。
#### ②. for...of 循环 1. `for...of`循环内部调用的是数据结构的`Symbol.iterator`方法。 2. `for...in`循环读取键名,`for...of`循环读取键值。 `for...of`循环调用遍历器接口,数组的遍历器接口只返回具有数字索引的属性。 ``` let arr = [3, 5, 7]; arr.foo = 'hello';
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo"
}
for (let i of arr) {
console.log(i); // "3", "5", "7"
}
```
Set和Map
- Set 结构遍历时,返回的是一个值,而 Map 结构遍历时,返回的是一个数组(
[k,v]
)
其他
- 对于没有Iterator接口的类数组对象:使用
Array.from
方法将其转为数组。
十四、Generator 函数的语法
- Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。
const myIterable = {}
myIterable[Symbol.iterator] = function* () {
yield 1
yield 2
yield 3
};
console.log([...myIterable]) // [1, 2, 3]
2. 注意: - `yield`表达式只能用在 Generator 函数里面,用在其他地方都会报错。 - `yield`表达式如果用在另一个表达式之中,必须放在圆括号里面。 - `yield`表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
-
next
方法的参数next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
也就是说:可以在Generator
函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。- 注意: 由于
next
方法的参数表示上一个yield
表达式的返回值,所以在第一次使用next
方法时,传递参数是无效的
- 注意: 由于
-
Generator.prototype.throw()
:- 只要 Generator 函数内部部署了try…catch代码块,那么遍历器的throw方法抛出的错误,不影响下一次遍历。
- 一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。
-
Generator.prototype.return()
:- 可以返回给定的值,并且终结遍历 Generator 函数。
- 可以返回给定的值,并且终结遍历 Generator 函数。
-
yield*
表达式:为了说明yield
表达式后面跟的是一个遍历器对象时加*
-
Generator
函数的this
:- Generator 函数总是返回遍历器对象而不是
this
function* gen() { this.a = 1; yield this.b = 2; yield this.c = 3; } function F() { return gen.call(gen.prototype);//<-- } var f = new F(); f.next(); // Object {value: 2, done: false} f.next(); // Object {value: 3, done: false} f.next(); // Object {value: undefined, done: true} f.a // 1 f.b // 2 f.c // 3
- Generator 函数总是返回遍历器对象而不是
-
Generator
- 与状态机:完美管理状态
- 协程:既可以用单线程实现,也可以用多线程实现。前者是一种特殊的子例程,后者是一种特殊的线程。
- 传统的“子例程”(subroutine)采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。
- 与普通线程差异:
- 同一时间可以有多个线程处于运行状态,但是运行的协程只能有一个,其他协程都处于暂停状态。
- 普通的线程是抢先式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。
十五、Generator 函数的异步应用
-
传统方法
-
回调函数 ->(callback hell)强耦合
-
事件监听
-
发布/订阅
-
Promise
对象 ->代码冗余,原来的任务被Promise
包装了一下,一堆then,原来的语义变得很不清楚。
-
-
Thunk
函数:是“传名调用”的一种实现策略,用来替换某个表达式。
/JavaScript 语言是传值调用,以下是个自执行器(thunkify、co模块、)//实现自执行
function thunkify(fn) {
return function() {
var args = new Array(arguments.length);
var ctx = this;
for (var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
return function (done) {
var called;
args.push(function () {
if (called) return;
called = true;
done.apply(null, arguments);
});
try {
fn.apply(ctx, args);
} catch (err) {
done(err);
}
}
}
}
const ft = (n,cb) =>cb(n)
const CB = thunkify(ft)
//实现自执行
function run(fn) {
var gen = fn()
function next(err, data) {
var result = gen.next(data)
console.log(result)
if (result.done) return;
result.value(next)
}
next()
}
const g = function* (){
var f1 = yield CB('aaaaaa')
var f2 = yield CB('bbbbbb')
// ...
var fn = yield CB('nnnnnn')
}
run(g)
```
<br/>
-
CO模块
- 原理:Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。以下两种方法可以做到这一点:
- 回调函数。将异步操作包装成 Thunk 函数,在回调函数里面交回执行权。
- Promise 对象。将异步操作包装成 Promise 对象,用then方法交回执行权。
- 基于 Promise 对象的自动执行
function run(gen){ var g = gen(); function next(data){ var result = g.next(data) console.log(result) if (result.done) return result.value result.value.then(function(data){ next(data); }); } next(); } //需要返回promise 然后链式自执行 const CO = (data) => new Promise((resolve)=> resolve(data)) const g = function* (){ var f1 = yield CO('aaaaaa') var f2 = yield CO('bbbbbb') // ... var fn = yield CO('nnnnnn') } run(g)
- 处理并发的异步操作
- co 支持并发的异步操作,即允许某些操作同时进行,等到它们全部完成,才进行下一步。这时,要把并发的操作都放在数组或对象里面,跟在yield语句后面。
- 实例:处理 Stream
- 原理:Generator 就是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。以下两种方法可以做到这一点:
十六、async 函数
-
Generator 函数的语法糖
- 改进
- (1)async函数自带执行器。
- (2)更广的适用性
co模块
约定,yield
命令后面只能是Thunk
函数或Promise
对象,而async
函数的await
命令后面,可以是Promise
对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作) - (3)返回值是
Promise
。
Generator
函数的返回值是Iterator
对象
-
注意点
- async函数内部
return
语句返回的值,会成为then
方法回调函数的参数。 - async函数内部抛出错误,会导致返回的
Promise
对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。 - 只要一个
await
语句后面的Promise
变为reject
,那么整个async
函数都会中断执行。 => 想继续执行 可以吧代码放在try...catch
中。 - await命令只能用在async函数之中,如果用在普通函数,就会报错。
- forEach/Map中使用async也会出问题 :
// scope B array.forEach(async function(item) { // scope A await wait(1000); }); /* `Scope B` 部分的代码并不会等待 `Scope A` 中的 `async/await` 执行完后继续执行后面的代码,相反,他会立刻执行后面的代码 */
- 正确的写法是采用for循环、
- async函数内部
-
并发执行
// 写法一 let [foo, bar] = await Promise.all([getFoo(), getBar()]); // 写法二 let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise;
-
for await...of
:遍历异步的 Iterator 接口。for await (const chunk of readStream) { console.log('>>> '+chunk); }
-
异步 Generator 函数
- 在头部加async 标志,此时即可返回一个Promise
十七、Class 的基本语法
constructor
方法- 通过new命令生成对象实例时,自动调用该方法。一个类必须有
constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。 constructor
方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
- 通过new命令生成对象实例时,自动调用该方法。一个类必须有
class Foo {}
不存在声明提升
- 私有方法和私有属性
- 函数名加下划线(仅为命名方法,外部仍可调用)
- 将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; }
- 利用
Symbol
值的唯一性,将私有方法的名字命名为一个Symbol值。
const bar = Symbol('bar') const snaf = Symbol('snaf') export default class myClass{ foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... };
4. this 的指向 - 当类的方法中有this使用且方法被提取出来单独使用时this会指向方法运行环境导致意料之外的错误 1. 可以在构造函数中绑定`this.printName = this.printName.bind(this)` 2. 箭头函数: ``` constructor() { this.printName = (name = 'there') => { this.print(`Hello ${name}`) } }
```
3. 使用Proxy,获取方法的时候,自动绑定this
```
function selfish (target) {
const cache = new WeakMap();
const handler = {
get (target, key) {
const value = Reflect.get(target, key);
if (typeof value !== 'function') {
return value;
}
if (!cache.has(value)) {
cache.set(value, value.bind(target));
}
return cache.get(value);
}
};
const proxy = new Proxy(target, handler);
return proxy;
}
const logger = selfish(new Logger());
```
<br/>
- Class 的取值函数(getter)和存值函数(setter)
```
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'
```
</br>
- Class 的静态方法
在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用
十八、Class 的继承
-
子类必须在
constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。 =>constructor
不写默认 有super(...args)
-
Object.getPrototypeOf
:从子类上获取父类//可以使用这个方法判断,一个类是否继承了另一个类。 Object.getPrototypeOf(ColorPoint) === Point // true
-
super
:- super在B的构造函数中,指向
A.prototype
,所以super
可以拿到父类原型对象上的实例方法 - 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。
- super在B的构造函数中,指向
-
原生构造函数的继承
- Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
原生构造函数会忽略apply方法传入的this,也就是说,原生构造函数的this无法绑定,导致拿不到内部属性。
extends
关键字不仅可以用来继承类,还可以用来继承原生的构造函数! - Boolean()
-
Mixin 模式的实现(多个对象合成一个新的对象)
function mix(...mixins) { class Mix {} for (let mixin of mixins) { copyProperties(Mix.prototype, mixin); // 拷贝实例属性 copyProperties(Mix.prototype, Reflect.getPrototypeOf(mixin)); // 拷贝原型属性 } return Mix; } function copyProperties(target, source) { for (let key of Reflect.ownKeys(source)) { if ( key !== "constructor" && key !== "prototype" && key !== "name" ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } } }
十九、修饰器(提案)
- 修饰器只能用于类和类的方法,不能用于函数,因为存在函数提升
- 详见>>ES6-修饰器
二十、Module 的语法
- 注意点
- import在静态解析阶段执行,所以它是一个模块之中最早执行的。
- import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。
// 报错 import { 'f' + 'oo' } from 'my_module'; // 报错 let module = 'my_module'; import { foo } from module; // 报错 if (x === 1) { import { foo } from 'module1'; } else { import { foo } from 'module2'; } //在静态分析阶段,这些语法都是没法得到值的。
export default
命令其实只是输出一个叫做default的变量- export 与 import 的复合写法
//foo和bar实际上并没有被导入当前模块,只是相当于对外转发了这两个接口 export { foo, bar } from 'my_module' //>可以用来合并一些模块
- 提案:import()函数 => 用来动态加载模块 返回Promise
二十、Module 的加载实现
-
浏览器加载
- 传统方法
- defer :“渲染完再执行” , 多个defer脚本,会按照它们在页面出现的顺序加载。
- async :“下载完就执行” , 多个async脚本,不能保证加载顺序。
- 加载 ES6 模块
<script type="module" src="./...js"></script>
:此时行为与defer相同
-
ES6 模块与 CommonJS 模块的差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
-
Node 加载
- Node 有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的
- Node 要求 ES6 模块采用.mjs后缀文件名。(新版)
$ node --experimental-modules my-app.mjs
- 内部变量差异
- ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块。
- ES6不存在这些顶层变量:
- arguments
- require
- module
- exports
- __filename
- __dirname
- ES6 模块加载 CommonJS 模块
- Node 的import命令加载 CommonJS 模块,Node 会自动将
module.exports
属性,当作模块的默认输出,即等同于{ default: module.exports }
- import命令加载 CommonJS 模块时,不允许采用解构的写法,改为整体输入
- Node 的import命令加载 CommonJS 模块,Node 会自动将
- CommonJS 模块加载 ES6 模块
- 不能使用require命令,而要使用import()函数。ES6 模块的所有输出接口,会成为输入对象的属性。
- Node 有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的
-
循环加载
- CommonJS 模块加载原理
- require命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。
- CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
- 由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。
- ES6 模块的循环加载
- 与CommonJS不同:变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。
- a 引用 b,b中引用a,执行到b中对a引用时会认为a已经存在,继续往下执行