笔记整理,看前方-ES6

194 阅读17分钟

Promise

promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise标准解读

  1. 一个promise的当前状态只能是pending、fulfilled和rejected三种之一。状态改变只能是pending到fulfilled或者pending到rejected。状态改变不可逆。
  2. promise的then方法接收两个可选参数,表示该promise状态改变时的回调(promise.then(onFulfilled, onRejected))。then方法返回一个promise。then 方法可以被同一个 promise 调用多次。

实现一个promise

  1. 构造函数接收一个executor立即执行函数,内部定义一个状态变量为pending,定一个data存放接收的数据,然后定义两个变量resolveCallback和rejectCallback用存放回调函数。
  2. executor立即执行函数接收一个resolve,一个reject函数,这个是在promise内部定义的,resolve方法会把状态变为resolved,然后把接收到的参数赋值给data,之后传入data到resolveCallback并执行,reject方法也是类似,改变状态,赋值变量,然后传入data,执行rejectCallback。
  3. 然后通过prototype定义then方法,then方法主要是传入回调方法,赋值给promise内部的resolveCallback和rejectCallback,因为是链式调用,所以返回的还是个promise
  4. 外部调用了new Promise,使用了立即执行函数接收的resolve或者reject方法,然后就会触发状态更改,然后执行回调函数

function Promise(executor) {
    var self = this;
    self.status = 'pending'; //promise当前的状态
    self.data = undefined; //promise的值
    self.onResolvedCallback = [];
    //promise状态变为resolve时的回调函数集,可能有多个
    self.onRejectedCallback = [];
    //promise状态变为reject时的回调函数集,可能有多个
   function resolve(value) {
       if(self.status === 'pending') {
           self.status = 'resolved';
           self.data = value;
           for(var i = 0; i < self.onResolvedCallback.length; i++) {
               self.onResolvedCallback[i](value);
           }
       }
   }


   function reject(reason) {
        if(self.status === 'pending') {
            self.status = 'rejected';
            self.data = reason;
            for(var i = 0; i < self.onRejectedCallback.length; i++) {
                self.onRejectedCallback[i](reason);
            }
        }
   }


   try {
       executor(resolve, reject);
   } catch (e){
       reject(e);
   }
};
Promise.prototype.then = function (onResolve, onReject) {
    this.onResolvedCallback.push(onResolve);
    this.onRejectedCallback.push(onReject);
};


// 调用
const promise = new Promise((resolve) => {
    setTimeout(()=> {
        resolve(1);
    }, 2000);
});
promise.then(a=> alert(a));
promise.then(a => alert(a+1));

promise串行,可以使用reduce,上一个执行的then里再调用下一个promise, 并行的话,执行promise.all

Promise.all / Promise.race 手写

// 封装 Promise.all方法
Promise.all = function (values) {
    return new Promise((resolve, reject) => {
        let result = []; // 存放返回值
        let counter = 0; // 计数器,用于判断异步完成
        function processData(key, value) {
            result[key] = value;
            // 每成功一次计数器就会加1,直到所有都成功的时候会与values长度一致,则认定为都成功了,所以能避免异步问题
            if (++counter === values.length) {
                resolve(result);
            }
        }
        // 遍历 数组中的每一项,判断传入的是否是promise
        for (let i = 0; i < values.length; i++) {
            let current = values[i];
            // 如果是promise则调用获取data值,然后再处理data
            if (isPromise(current)) {
                current.then(data => {
                    processData(i, data);
                }, reject);
            } else {
                // 如果不是promise,传入的是普通值,则直接返回
                processData(i, current);
            }
        }
    });
}

Promise.race = function(arr) {
    return new Promise((resolve,reject)=>{
        if(arr.length === 0){
            return 
        }else{
            for(let item of arr){
                if(isPromise(item)){
                    item.then((data)=>{
                        resolve(data);
                    },reject);
                }else{
                    resolve(item);
                }
            }
        }
    })
}

Promise相关API

  • Promise.all() 只有全部返回fulfilled的才会返回数据,其中一个rejected,就会返回这个rejected的值
  • Promise.race() 有一个返回fulfilled就会返回值传给外部的回调函数
  • Promise.allSettled() 只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束.Promise.allSettled()的返回值状态只可能变成fulfilled。返回一个数组,数组内每个返回对象都有status属性,该属性的值只可能是字符串fulfilled或字符串rejected。fulfilled时,对象有value属性,rejected时有reason属性,对应两种状态的返回值。
  • Promise.any() 只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态
  • Promise.resolve() 会将现有对象转化成Promise对象,状态为resolved,如果原本即使Promise对象,原装返回
  • Promise.reject() 也会返回一个新的Promise对象实例,状态为rejected
  • Promise.try() 不管是同步还是异步方法,都可以使用then方法管理流,而且不会把同步方法改为异步

generate && Iterator

Generator

Generator 函数是 ES6 提供的一种异步编程解决方案。 执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

Generator 函数内部的状态是通过next()实现改变的,当内部执行遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。下一次调用next方法时,这个状态才会发生改变,再继续往下执行,直到遇到下一个yield表达式,最后执行到return或者没有yield结束。

因为generator的这个特性,我们可以把异步的调用放在yield表达式里,然后把异步操作的回调放在yield表达式下面,这样等到调用next方法时再执行肯定有值。这样异步改同步,解决回调地狱。

generator还可以用作控制流的管理,定义一个generator函数,定义好内部的每一步流程,外部做一个递归调用执行这些task,到最后return就可以结束掉了。也非常直观。

引申async

async函数是generator函数的语法糖,它内部自带执行器,直接像普通函数一样调用即可。而且返回的值是Promise,比generator返回迭代器要方便多了。

async函数内部return语句返回的值,会成为then方法回调函数的参数。 async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。其实就是相当于在拆分成一个一个yield任务,然后自动递归执行,执行完毕吐出数据。

Ieterator

iterator它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

默认有遍历器的原生的结构有ArrayMapSetStringNodeList等等。

如果自己定义的对象想添加遍历器的话,需要在symbol.iterator上自己定义,定义的方法必须是遍历器生成函数,返回一个遍历器,遍历器内部定义next()方法,用来移动内部指针。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,来定义该对象的 Iterator 接口。

有一些方法会默认调用遍历器,比如说解构赋值,扩展运算符,generator函数中的yield,Array.from()等等

for..of..

  • 没有for...in那些缺点,不会遍历原型上的键,而且是有顺序的。
  • 它可以与break、continue和return配合使用。
  • 提供了遍历所有数据结构的统一操作接口。

es6模块与commonjs模块区别

参考文章

它们有两个重大差异:

  1. CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  2. CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。

而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

下面重点解释第一个差异,我们还是举上面那个CommonJS模块的加载机制例子:

// lib.js
export let counter = 3;
export function incCounter() {
  counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

ES6 模块的运行机制与 CommonJS 不一样。ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

let const

块级作用域的优点

内部变量不会影响外部,用来计数的循环变量也不会泄露到全局,不允许重复声明,并且会产生暂时性死区可以严格规范代码。

有提案do表达式,可以直接返回块级作用域的值

const用来声明一个常量,但是其实是保存的变量的内存地址指针,所以如果声明对象的话,内部的值还是可以改变的,如果想彻底冻结,可以使用Object.freeze深度遍历冻结,因为Object.freeze冻结的对象内部还有对象属性的话,冻结的还是指针。

扩展:Object.seal不可增加删除,但是可以修改,Object.freeze不可增删改

解构赋值的用途

交换变量值

let x=1 
let y=2
[x,y] = [y,x]

函数参数的定义更方便,可以有序可以无序

function([x,y,z]) function({x,y,z})

函数参数上的默认值

提取json数据

输入模块的指定方法

const {a,b} = require('c')

字符的扩展

  • codePointAt 返回字符的码点
  • for of 遍历器接口
  • at()方法,比chartAt高级,可以返回中文识别,还在提案,可以使用polyfill
  • includes,startWith,endWith 查询字符位置
  • repeat 重复字符
  • padStart padEnd 字符串补全功能
  • 最有用的是模板字符串,告别拼接,能插入变量,插入函数,还能嵌套。

标签模板

标签模板的功能,就是把模板字符串紧跟在函数后面使用,这个函数将被调用来处理这个模板字符串,主要可以用来过滤HTML字符串,防止用户恶意输入,缺点是无法嵌入其他语言

正则的扩展

  • .字符,可以匹配除换行外的任意单个字符,不包括换行符、回车符
  • y修饰符,“粘连”(sticky)修饰符,和全局修饰符g不同,黏连修饰符确保匹配必须从剩余的第一个位置开始。

[注意]y修饰符只有在调用 exec()和test()这两个正则匹配方法时,才会进行全局匹配;如果调用match()、replace()等字符串的方法时,不会进行全局匹配

  • flag属性,可以查看修饰符

  • s修饰符,是dotAll模式,匹配所有单个字符

  • 具名组匹配

const RE_DATE = /(\d{4})-(\d{2})-(\d{2})/
const matchObj = RE_DATE.exec(‘1991-01-20’)
const year = matchObj[0]
const month = matchObj[1]
const day = matchObj[2]

改写为

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/
const year =  matchObj.groups.year 
const month =  matchObj.groups.month
const day =  matchObj.groups.day

数值的扩展

新增方法

  • Number.isFinite()是否是有限数组
  • Number.isNaN() 用来检查检查一个值是否为NaN
  • 将全局方法parseIntparseFloat移植到了Number对象上
  • Number.isInterge()判断是否是一个整数
  • Number.isSafeInterge()是否是在-2的53次方到2的53次方之间

Math方法的增加

  • Math.trunc() 去除小数
  • Math.sign() 判断是正数负数还是0 ,返回值有5个 +1 -1 0 -0 NaN
  • Math.cbrt() 立方根

** 指数运算符

函数的扩展

箭头函数,不绑定this,不能作为构造函数,没有原型属性prototype,没有arguments对象,要用rest参数解决,不可以使用yeild命令,不能用作Generator函数。

新提案,双冒号 :: 绑定 会把冒号左边的对象绑定到右边的函数上

尾调用的优化

在末尾把需要执行的函数return出去,这样就不用保留上一个函数的调用帧了,每次执行调用帧时只执行一项,可以大大的节省内存,这就是尾调用优化。

尾调用优化只有在严格模式开启,在正常模式下的func.arguments和func.caller可以跟踪函数的调用栈,但是尾调用优化时,函数的调用栈会被改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用模式仅在严格模式下生效。

尾递归

尾调用自身就是尾递归,优化方案是循环替换递归,循环是每次返回一个函数,然后直接执行,再返回一个函数再执行,不用是函数里面调用函数,这样就避免掉了递归执行,消除调用栈过大的问题。

数组的扩展

  • 扩展运算符,内部调用的是遍历器接口,用途:合并数组,与结构赋值一起使用,用于生成数组[first,…rest] = [1,2,3,4],字符串转数组,类似数组转成真正的数组

  • Array.from() 类数组对象和可遍历对象转为真正的数组,而且第二个参数可以对每个元素进行处理,类似与map方法

  • Array.of() 用来将一组值转换为数组 Array.of(1,2,3) // [1,2,3]

  • CopyWithin() 将数组内的指定成员复制到其他位置,并覆盖原有原成员,然后返回数组。

对象的扩展

  • Object.is() 功能基本相当于 === ,但是可以判断isNaN相等,+0不等于-0
  • Object.assign() 浅拷贝,用途:为对象添加属性,为对象添加方法,克隆对象,合并对象

[注意]这个克隆对象不能克隆继承的值,所以要特殊处理一下

function clone(origin) {
    let originProto = Object.getPrototypeOf(origin)
    return Object.assign(Object.create(originProto), origin)
}
  • Object.setPrototypeOf() Object.getPrototypeOf() Object.create() 代替原始的__proto__的读取创建操作

  • Object.keys() 返回一个包含属性名的数组

  • Object.values() 返回一个包含自身可遍历属性值的数据,会过滤Symbol属性

  • Object.entries() 返回一个数组,成员依然是数组,包含key和value

  • Object.getOwnPropertyDescriptor() 返回对象某个属性的描述属性集合

  • Object.getOwnPropertyDescriptors() 返回对象所有自身属性的描述属性的集合

空位合并操作符?.

a?.b?.c?.d

Symbol

新的原始数据类型,表示独一无二的值。本质上是一种类似于字符串的数据类型。这是第7种数据类型,其余6种是Undefined,Null,Boolean,String,Number,Object

用法不需要new let a = Symbol()

Symbol接收一个字符串作为参数,Symbol(‘abc’)

括号里的值表示对Symbol实例的描述,主要是为了好区分

Symbol不可以运算,但是可以显式地转为字符串,也可以转换为布尔值。

[注意] Symbol作为属性值的时候,不能直接.取值,会被认为是一个字符串

Object.getOwnPropertySymbols() Reflect.ownKeys() 可以用于获取Symbol属性名的获取

Symbol.for() 可以重复使用同一个Symbol值

Symbol.keyFor() 返回一个已等级Symbol类型值的key

Symbol.isConcatSpreadable() 返回一个布尔值,表示该对象使用Array.prototype.concat()时是否可以展开,默认为undefined,和true的效果一样,都是可以展开,设置为false的话,就不可以展开。

Set

新的数据结构,类似于数组,但是成员是唯一的。本身是一个构造函数。

[…new Set(Array)]可以做快速的数组去重

内部是调用“same-value equality”,等同于Object.is的算法

Array.from可以把Set数据结构转为数组

方法有:add/delete/has/clear

遍历操作: keys()/values()/entries()/forEach()

由于Set没有键值,key和value返回一致

WeakSet

结构和Set类似,成员唯一,但成员只能是对象,并且是弱引用,就会WeakSet引用了某个对象是不会被计数的,垃圾回收不考虑WeakSet对对象的引用。

垃圾回收运行机制前后,WeakSet的成员个数很可能会改变,所以WeakSet不可遍历。

方法: add/delete/has

用途: 存储DOM节点

Map

也是键值对集合,但是key不限制于只能是字符串,各种类型的值都能当做key。

Map的key实际上是和内存地址绑定的,所以不同的实例会被视作不同的键。

方法: size/set/get/has/delete/clear/

遍历方法: keys()/values()/entries()/foreach()

Map和数组、对象、JSON都可以互相转换

WeakMap

与Map结构类似,用于生成键值对集合,但是只接受对象作为键名(null除外),键名所指向的对象不计入垃圾回收机制,与WeakSet一样。没有遍历操作,没有size属性。

Proxy

用于修改某些操作的默认行为。可以理解成在对象前架设了一个“拦截”层,外界对该对象的访问都必须通过这个拦截层,因此提供了一种机制可以对外界的访问进行过滤和改写。

var proxy = new Proxy(target,handler)

Proxy本身是一个构造函数,接收两个参数,target是代理的目标,handler是一个对象用来定制拦截行为。

可以拦截的操作:

  • get(target,propKey,receiver) 拦截对象属性读取
  • set(target,propKey,receiver) 拦截对象属性赋值,返回布尔值
  • has(target,propKey) 拦截propKey in proxy 的操作,返回布尔值
  • deleteProperty(target,propKey) 拦截delete Proxy[property],返回一个布尔值
  • ownKeys(target) 拦截Object.getOwnPropertyNames Object.getOwnpropertySymbols Object.keys,返回属性集合
  • getOwnPropertyDescriptor(target,propKey) 拦截Object.getOwnPropertyzdescriptor,返回属性的描述对象
  • defineProperty(target,propKey,propDesc) 拦截Object.defineProperty Object.defineProperties 返回一个布尔值
  • preventExtensions(target)
  • getPropertyOf(target)
  • isExtensible(target)
  • setPropertyOf(target,proto)
  • apply(target,object,args)
  • construct(target,args) 拦截Proxy实例作为构造函数调用的操作,比如 new proxy(…args)

在proxy代理下,this会指向Proxy代理。

Reflect

为操作对象而提供的新的API。

  1. 为了将Object对象的一些明显属于语言内部的方法,比如Object.defineProperty,放到reflect对象上。未来的新方法只会部署到Reflect上面。
  2. 修改一些Object方法的返回结果,让其变得更合理。比如Object.defineProperty在无法定义属性时会抛出一个错误,而Reflect.defineProperty会返回false。
  3. 让Object操作都变成函数行为,比如 name in Object和delete Obj[name],而Reflect可以转换成has()和delete()方法
  4. Reflect方法和Proxy方法是一一对应的,这样就可以保证原生行为可以正常进行。

ES新特性

ES7

  • Array.prototype.includes()方法
  • 求幂运算符**,具有与Math.pow()等效的计算结果

ES8

  • Async/Await
  • Object.values(),Object.entries()
  • String padStart/padEnd
  • Object.getOwnPropertyDescriptors() 返回指定对象所有自身属性(非继承属性)的描述对象

ES9

  • for await of ES9新增的for await of可以用来遍历具有Symbol.asyncIterator方法的数据结构,也就是异步迭代器,且会等待前一个成员的状态改变后才会遍历到下一个成员,相当于async函数内部的await。
  • Object Rest Spread
  • Promise.prototype.finally()
  • 新的正则表达式特性 s (dotAll) 标志 / 命名捕获组 / Lookbehind 后行断言

ES10

  • Array.prototype.flat()
  • Array.prototype.flatMap()
  • Object.fromEntries() 这个新的API实现了与 Object.entries 相反的操作。这使得根据对象的 entries 很容易得到 object。
  • String.trimStart 和 String.trimEnd,去空格
  • String.prototype.matchAll
  • try…catch 在ES10中,try-catch语句中的参数变为了一个可选项。以前我们写catch语句时,必须传递一个异常参数。这就意味着,即便我们在catch里面根本不需要用到这个异常参数也必须将其传递进去,现在不需要了
  • BigInt 现在ES10引入了一种新的数据类型 BigInt(大整数),BigInt 只用来表示整数,没有位数的限制,任何位数的整数都可以精确表示。
  • Symbol.prototype.description
  • Function.prototype.toString() ES2019中,Function.toString()发生了变化。之前执行这个方法时,得到的字符串是去空白符号的。而现在,得到的字符串呈现出原本源码的样子
  • 可选链操作符(Optional Chaining),
let nestedProp = obj?.first?.second
  • 空位合并操作符(Nullish coalescing Operator) ??
let c = a ?? b;
// 等价于let c = a !== undefined && a !== null ? a : b;
  • Promise.allSettled()
  • Dynamic import,import()方法,按需加载
  • globalThis 目的就是提供一种标准化方式访问全局对象,有了 globalThis 后,你可以在任意上下文,任意时刻都能获取到全局对象。

其他新特性

  • 类的私有变量,我们将使用 # 符号表示类的私有变量。这样就不需要使用闭包来隐藏不想暴露给外界的私有变量。
  • static 字段,它允许类拥有静态字段,类似于大多数OOP语言。静态字段可以用来代替枚举,也可以用于私有字段
  • Top-level await ,ES2017(ES8)中的 async/await 特性仅仅允许在 async 函数内使用 await 关键字,新的提案旨在允许 await 关键字在顶层内容中的使用,例如可以简化动态模块加载的过程
  • WeakRef,它提供了真正的弱引用,Weakref 实例具有一个方法 deref,该方法返回被引用的原始对象,如果原始对象已被收集,则返回undefined对象