【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 下

简介: 【复习资料】ES6/ES7/ES8/ES9资料整理(个人整理) 下

1.16 Module语法和加载实现

1.16.1 介绍

ES6之前用于JavaScript的模块加载方案,是一些社区提供的,主要有CommonJSAMD两种,前者用于服务器,后者用于浏览器

ES6提供了模块的实现,使用export命令对外暴露接口,使用import命令输入其他模块暴露的接口。

// CommonJS模块
let { stat, exists, readFire } = require('fs');
// ES6模块
import { stat, exists, readFire } = from 'fs';

1.16.2 严格模式

ES6模块自动采用严格模式,无论模块头部是否有"use strict"

严格模式有以下限制

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete * global[prop]
  • eval不会在它的外层作用域引入变量
  • evalarguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.callerfn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protectedstaticinterface

特别是,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.js
export function f(){...}
export function g(){...}
// b.js
import * 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.js
export default function(){console.log('leo')};
// b.js
import leo from './a.js';
leo(); // 'leo'

export defualt暴露有函数名的函数时,在调用时相当于匿名函数。

// a.js
export default function f(){console.log('leo')};
// 或者
function f(){console.log('leo')};
export default f;
// b.js
import 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属性。

<script type="module" src="./a.js"></script>

另外,ES6模块也可以内嵌到网页,语法与外部加载脚本一致:

<script type="module">
    import a from './a.js';
</script>

注意点

  • 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明use strict
  • 模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
  • 模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
  • 同一个模块如果加载多次,将只执行一次。

2. ES7

2.1 Array.prototype.includes()方法

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);     // -1
a.includes(NaN);    // true

另外在判断+0-0时,includesindexOf的返回相同。

[1, +0, 3, 4].includes(-0);   // true
[1, +0, 3, 4].indexOf(-0);    // 1

2.2 指数操作符(**)

基本用法:

let a = 3 ** 2 ; // 9
// 等效于
Math.pow(3, 2);  // 9

**是一个运算符,也可以满足类似假发的操作,如下:

let a = 3;
a **= 2;    // 9

3. ES8

3.1 async函数

3.1.1 介绍

ES8引入async函数,是为了使异步操作更加方便,其实它就是Generator函数的语法糖。

async函数使用起来,只要把Generator函数的(*)号换成asyncyield换成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有四个优点:

  • (1)内置执行器 Generator函数执行需要有执行器,而async函数自带执行器,即async函数与普通函数一模一样:
async f();
  • (2)更好的语义 asyncawait,比起星号yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
  • (3)更广的适用性 yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
  • (4)返回值是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 awaittry..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 使用注意

  • (1)await命令放在try...catch代码块中,防止Promise返回rejected
  • (2)若多个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();
  • (3)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);
    }
}

3.2 Promise.prototype.finally()

finally()是ES8中Promise添加的一个新标准,用于指定不管Promise对象最后状态(是fulfilled还是rejected)如何,都会执行此操作,并且finally方法必须写在最后面,即在thencatch方法后面。

// 写法如下:  
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
    }
)

3.3 Object.values(),Object.entries()

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']]
  • 用途1
    遍历对象属性。
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'
  • 用途2: 将对象转为真正的Map结构。
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;
}

3.4 Object.getOwnPropertyDescriptors()

之前有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
}
*/

valueundefined是因为Object.assign方法不会拷贝其中的getset方法,使用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)
);

3.5 字符串填充 padStart和padEnd

用来为字符串填充特定字符串,并且都有两个参数:字符串目标长度填充字段,第二个参数可选,默认空格。

'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'

从上面结果来看,填充函数只有在字符长度小于目标长度时才有效,若字符长度已经等于或小于目标长度时,填充字符不会起作用,而且目标长度如果小于字符串本身长度时,字符串也不会做截断处理,只会原样输出。

3.6 函数参数列表与调用中的尾部逗号

该特性允许我们在定义或者调用函数时添加尾部逗号而不报错:

function es8(var1, var2, var3,) {
  // ...
}
es8(10, 20, 30,);

3.7 共享内存与原子操作

当内存被共享时,多个线程可以并发读、写内存中相同的数据。原子操作可以确保那些被读、写的值都是可预期的,即新的事务是在旧的事务结束之后启动的,旧的事务在结束之前并不会被中断。这部分主要介绍了 ES8 中新的构造函数 SharedArrayBuffer 以及拥有许多静态方法的命名空间对象 Atomic

Atomic 对象类似于 Math 对象,拥有许多静态方法,所以我们不能把它当做构造函数。 Atomic 对象有如下常用的静态方法:

  • add /sub :为某个指定的value值在某个特定的位置增加或者减去某个值
  • and / or /xor :进行位操作
  • load :获取特定位置的值

4. ES9

4.1 对象的拓展运算符

4.1.1 介绍

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

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

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

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

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

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

注意

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

4.1.2 使用场景

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

4.2 正则表达式 s 修饰符

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

终止符即表示一行的结束,如下四个字符属于“行终止符”:

  • U+000A 换行符(\n)
  • U+000D 回车符(\r)
  • U+2028 行分隔符(line separator)
  • U+2029 段分隔符(paragraph separator)
/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') // true
re.dotAll // true
re.flags // 's'

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

4.3 异步遍历器

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

4.3.1 异步遍历的接口

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

a.next().then( 
    ({value, done}) => {
        //...
    }
)

上述a为异步遍历器,调用next后返回一个Promise对象,再调用then方法就可以指定Promise对象状态变为resolve后执行的回调函数,参数为valuedone两个属性的对象,与同步遍历器一致。

与同步遍历器一样,异步遍历器接口也是部署在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里面。

用途

直接把部署了asyncIteable操作的异步接口放入这个循环。

let a = '';
async function f(){
    for await (let b of req) {
        a += b;
    }
    let c = JSON.parse(a);
    console.log('leo', c);
}

next返回的Promise对象被rejectfor 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

在异步Generator函数中,同时使用awaityield,简单样理解,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

5. 知识补充

5.1 块级作用域

通常指一个函数内部,或者一个代码块内部

比如:

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 的值依然存在,造成变量的全局污染。

5.2 ES5/6对数组空位的处理

数组的空位不是undefined,而是没有任何值,数组的undefined也是有值。

0 in [undefined,undefined,undefined] // true
0 in [,,,] // false

ES5对空位的处理

  • forEach(), filter(), reduce(), every()some()都会跳过空位。
  • map()会跳过空位,但会保留这个值。
  • join()toString()会将空位视为undefined,而undefinednull会被处理成空字符串。
[,'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

由于空位的处理规则非常不统一,所以建议避免出现空位。

三、结语

参考文章

推荐文章

目录
相关文章
|
2天前
|
JavaScript 小程序 Java
ES6使用实践总结
ES6使用实践总结
16 0
|
2天前
|
JavaScript 前端开发
|
10月前
|
自然语言处理 前端开发 Java
【ES系列八】——问题整理与总结
前端时间在团队中进行了ES的分享活动,只是将大概的内容进行了宏观的分享,以及自己在做的过程中的一些思路,但是在最后的答疑环节,伙伴们提出了一些问题,今天就针对于这些问题进行了一个简单的总结,希望能够带给读者朋友一些帮助。
|
11月前
|
缓存 JavaScript 前端开发
「万字进阶」深入浅出 Commonjs 和 Es Module
全方位介绍模块化 Commonjs 和 Es Module
「万字进阶」深入浅出 Commonjs 和 Es Module
|
11月前
|
Docker 容器
es应用笔记1-es部署
es应用笔记1-es部署
83 0
|
11月前
|
前端开发 JavaScript
每天3分钟,重学ES6-ES12系列文章汇总
每天3分钟,重学ES6-ES12系列文章汇总
40 0
|
存储 监控 前端开发
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
120 0
ES6 从入门到精通 # 23:ES6 的模块化实现(完结)
|
JavaScript 前端开发 数据库
再不学ES6你就out了 —— 一文搞懂ES6
ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版 虽然15年就有正式版本了,但是国内普遍商用是在2018年之后去了,甚至到现在有很多前端仍然搞不懂ES6
142 0
再不学ES6你就out了 —— 一文搞懂ES6
|
JSON JavaScript 数据格式
ES6学习记录
ES6学习记录
89 0