1 对象的拓展运算符

1.1 介绍

对象的拓展运算符,即对象的Rest/Spread属性,可将对象解构赋值用于从一个对象取值,搜键值对分配到指定对象上,与数组的拓展运算符类似:

  1. let {x, y, ...z} = {x:1, y:2, a:3, b:4};
  2. x; // 1
  3. y; // 2
  4. z; // {a:3, b:4}

对象的解构赋值要求等号右边必须是个对象,所以如果等号右边是undefinednull就会报错无法转成对象。

  1. let {a, ...b} = null; // 运行时报错
  2. let {a, ...b} = undefined; // 运行时报错

解构赋值必须是最后一个参数,否则报错。

  1. let {...a, b, c} = obj; // 语法错误
  2. let {a, ...b, c} = obj; // 语法错误

注意

  • 1.解构赋值是浅拷贝。
  1. let a = {a1: {a2: 'leo'}};
  2. let {...b} = a;
  3. a.a1.a2 = 'leo';
  4. b.a1.a2 = 'leo';
  • 2.拓展运算符的解构赋值,不能复制继承自原型对象的属性。
  1. let o1 = { a: 1 };
  2. let o2 = { b: 2 };
  3. o2.__proto__ = o1;
  4. let { ...o3 } = o2;
  5. o3; // { b: 2 }
  6. o3.a; // undefined

1.2 使用场景

  • 1.取出参数对象所有可遍历属性,拷贝到当前对象中。
  1. let a = { a1:1, a2:2 };
  2. let b = { ...a };
  3. b; // { a1:1, a2:2 }
  4. // 类似Object.assign方法
  • 2.合并两个对象。
  1. let a = { a1:1, a2:2 };
  2. let b = { b1:11, b2:22 };
  3. let ab = { ...a, ...b }; // {a1: 1, a2: 2, b1: 11, b2: 22}
  4. // 等同于
  5. let ab = Object.assign({}, a, b);
  • 3.将自定义属性放在拓展运算符后面,覆盖对象原有的同名属性。
  1. let a = { a1:1, a2:2, a3:3 };
  2. let r = { ...a, a3:666 };
  3. // r {a1: 1, a2: 2, a3: 666}
  4. // 等同于
  5. let r = { ...a, ...{ a3:666 }};
  6. // r {a1: 1, a2: 2, a3: 666}
  7. // 等同于
  8. let r = Object.assign({}, a, { a3:666 });
  9. // r {a1: 1, a2: 2, a3: 666}
  • 4.将自定义属性放在拓展运算符前面,就会成为设置新对象的默认值。
  1. let a = { a1:1, a2:2 };
  2. let r = { a3:666, ...a };
  3. // r {a3: 666, a1: 1, a2: 2}
  4. // 等同于
  5. let r = Object.assign({}, {a3:666}, a);
  6. // r {a3: 666, a1: 1, a2: 2}
  7. // 等同于
  8. let r = Object.assign({a3:666}, a);
  9. // r {a3: 666, a1: 1, a2: 2}
  • 5.拓展运算符后面可以使用表达式。
  1. let a = {
  2. ...(x>1? {a:!:{}),
  3. b:2
  4. }
  • 6.拓展运算符后面如果是个空对象,则没有任何效果。
  1. {...{}, a:1}; // {a:1}
  • 7.若参数是nullundefined则忽略且不报错。
  1. let a = { ...null, ...undefined }; // 不报错
  • 8.若有取值函数get则会执行。
  1. // 不会打印 因为f属性只是定义 而不没执行
  2. let a = {
  3. ...a1,
  4. get f(){console.log(1)}
  5. }
  6. // 会打印 因为f执行了
  7. let a = {
  8. ...a1,
  9. ...{
  10. get f(){console.log(1)}
  11. }
  12. }

⬆ 返回目录

2 正则表达式 s 修饰符

在正则表达式中,点(.)可以表示任意单个字符,除了两个:用u修饰符解决四个字节的UTF-16字符,另一个是行终止符。
终止符即表示一行的结束,如下四个字符属于“行终止符”:

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
  1. /foo.bar/.test('foo\nbar')
  2. // false

上面代码中,因为.不匹配\n,所以正则表达式返回false
换个醒,可以匹配任意单个字符:

  1. /foo[^]bar/.test('foo\nbar')
  2. // true

ES9引入s修饰符,使得.可以匹配任意单个字符:

  1. /foo.bar/s.test('foo\nbar') // true

这被称为dotAll模式,即点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。

  1. const re = /foo.bar/s;
  2. // 另一种写法
  3. // const re = new RegExp('foo.bar', 's');
  4. re.test('foo\nbar') // true
  5. re.dotAll // true
  6. re.flags // 's'

/s修饰符和多行修饰符/m不冲突,两者一起使用的情况下,.匹配所有字符,而^$匹配每一行的行首和行尾。

⬆ 返回目录

3 异步遍历器

在前面ES6章节中,介绍了Iterator接口,而ES6引入了“异步遍历器”,是为异步操作提供原生的遍历器接口,即valuedone这两个属性都是异步产生的。

3.1 异步遍历的接口

通过调用遍历器的next方法,返回一个Promise对象。

  1. a.next().then(
  2. ({value, done}) => {
  3. //...
  4. }
  5. )

上述a为异步遍历器,调用next后返回一个Promise对象,再调用then方法就可以指定Promise对象状态变为resolve后执行的回调函数,参数为valuedone两个属性的对象,与同步遍历器一致。
与同步遍历器一样,异步遍历器接口也是部署在Symbol.asyncIterator属性上,只要有这个属性,就都可以异步遍历。

  1. let a = createAsyncIterable(['a', 'b']);
  2. //createAsyncIterable方法用于构建一个iterator接口
  3. let b = a[Symbol.asyncInterator]();
  4. b.next().then( result1 => {
  5. console.log(result1); // {value: 'a', done:false}
  6. return b.next();
  7. }).then( result2 => {
  8. console.log(result2); // {value: 'b', done:false}
  9. return b.next();
  10. }).then( result3 => {
  11. console.log(result3); // {value: undefined, done:true}
  12. })

另外next方法返回的是一个Promise对象,所以可以放在await命令后。

  1. async function f(){
  2. let a = createAsyncIterable(['a', 'b']);
  3. let b = a[Symbol.asyncInterator]();
  4. console.log(await b.next());// {value: 'a', done:false}
  5. console.log(await b.next());// {value: 'b', done:false}
  6. console.log(await b.next());// {value: undefined, done:true}
  7. }

还有一种情况,使用Promise.all方法,将所有的next按顺序连续调用:

  1. let a = createAsyncIterable(['a', 'b']);
  2. let b = a[Symbol.asyncInterator]();
  3. let {{value:v1}, {value:v2}} = await Promise.all([
  4. b.next(), b.next()
  5. ])

也可以一次调用所有next方法,再用await最后一步操作。

  1. async function f(){
  2. let write = openFile('aaa.txt');
  3. write.next('hi');
  4. write.next('leo');
  5. await write.return();
  6. }
  7. f();

3.2 for await…of

for...of用于遍历同步的Iterator接口,而ES8引入for await...of遍历异步的Iterator接口。

  1. async function f(){
  2. for await(let a of createAsyncIterable(['a', 'b'])) {
  3. console.log(x);
  4. }
  5. }
  6. // a
  7. // b

上面代码,createAsyncIterable()返回一个拥有异步遍历器接口的对象,for...of自动调用这个对象的next方法,得到一个Promise对象,await用来处理这个Promise,一但resolve就把得到的值x传到for...of里面。
用途
直接把部署了asyncIteable操作的异步接口放入这个循环。

  1. let a = '';
  2. async function f(){
  3. for await (let b of req) {
  4. a += b;
  5. }
  6. let c = JSON.parse(a);
  7. console.log('leo', c);
  8. }

next返回的Promise对象被rejectfor await...of就会保错,用try...catch捕获。

  1. async function f(){
  2. try{
  3. for await (let a of iterableObj()){
  4. console.log(a);
  5. }
  6. }catch(e){
  7. console.error(e);
  8. }
  9. }

注意,for await...of循环也可以用于同步遍历器。

  1. (async function () {
  2. for await (let a of ['a', 'b']) {
  3. console.log(a);
  4. }
  5. })();
  6. // a
  7. // b

3.3 异步Generator函数

就像 Generator 函数返回一个同步遍历器对象一样,异步 Generator 函数的作用,是返回一个异步遍历器对象。
在语法上,异步 Generator 函数就是async函数与 Generator 函数的结合。

  1. async function* f() {
  2. yield 'hi';
  3. }
  4. const a = f();
  5. a.next().then(x => console.log(x));
  6. // { value: 'hello', done: false }

设计异步遍历器的目的之一,就是为了让Generator函数能用同一套接口处理同步和异步操作。

  1. // 同步Generator函数
  2. function * f(iterable, fun){
  3. let a = iterabl[Symbol.iterator]();
  4. while(true){
  5. let {val, done} = a.next();
  6. if(done) break;
  7. yield fun(val);
  8. }
  9. }
  10. // 异步Generator函数
  11. async function * f(iterable, fun){
  12. let a = iterabl[Symbol.iterator]();
  13. while(true){
  14. let {val, done} = await a.next();
  15. if(done) break;
  16. yield fun(val);
  17. }
  18. }

同步和异步Generator函数相同点:在yield时用next方法停下,将后面表达式的值作为next()返回对象的value
在异步Generator函数中,同时使用awaityield,简单样理解,await命令用于将外部操作产生的值输入函数内部,yield命令用于将函数内部的值输出。

  1. (async function () {
  2. for await (const line of readLines(filePath)) {
  3. console.log(line);
  4. }
  5. })()

异步 Generator 函数可以与for await...of循环结合起来使用。

  1. async function* f(asyncIterable) {
  2. for await (const line of asyncIterable) {
  3. yield '> ' + line;
  4. }
  5. }

3.4 yield* 语句

yield*语句跟一个异步遍历器。

  1. async function * f(){
  2. yield 'a';
  3. yield 'b';
  4. return 'leo';
  5. }
  6. async function * g(){
  7. const a = yield* f(); // a => 'leo'
  8. }

与同步 Generator 函数一样,for await...of循环会展开yield*

  1. (async function () {
  2. for await (const x of gen2()) {
  3. console.log(x);
  4. }
  5. })();
  6. // a
  7. // b

⬆ 返回目录