Promise
promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise标准解读
- 一个promise的当前状态只能是pending、fulfilled和rejected三种之一。状态改变只能是pending到fulfilled或者pending到rejected。状态改变不可逆。
- promise的then方法接收两个可选参数,表示该promise状态改变时的回调(promise.then(onFulfilled, onRejected))。then方法返回一个promise。then 方法可以被同一个 promise 调用多次。
实现一个promise
- 构造函数接收一个executor立即执行函数,内部定义一个状态变量为pending,定一个data存放接收的数据,然后定义两个变量resolveCallback和rejectCallback用存放回调函数。
- executor立即执行函数接收一个resolve,一个reject函数,这个是在promise内部定义的,resolve方法会把状态变为resolved,然后把接收到的参数赋值给data,之后传入data到resolveCallback并执行,reject方法也是类似,改变状态,赋值变量,然后传入data,执行rejectCallback。
- 然后通过prototype定义then方法,then方法主要是传入回调方法,赋值给promise内部的resolveCallback和rejectCallback,因为是链式调用,所以返回的还是个promise
- 外部调用了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对象实例,状态为rejectedPromise.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 接口,就可以完成遍历操作。
默认有遍历器的原生的结构有Array、Map、Set、String、NodeList等等。
如果自己定义的对象想添加遍历器的话,需要在symbol.iterator上自己定义,定义的方法必须是遍历器生成函数,返回一个遍历器,遍历器内部定义next()方法,用来移动内部指针。由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,来定义该对象的 Iterator 接口。
有一些方法会默认调用遍历器,比如说解构赋值,扩展运算符,generator函数中的yield,Array.from()等等
for..of..
- 没有for...in那些缺点,不会遍历原型上的键,而且是有顺序的。
- 它可以与break、continue和return配合使用。
- 提供了遍历所有数据结构的统一操作接口。
es6模块与commonjs模块区别
它们有两个重大差异:
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 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高级,可以返回中文识别,还在提案,可以使用polyfillincludes,startWith,endWith查询字符位置repeat重复字符padStartpadEnd字符串补全功能- 最有用的是
模板字符串,告别拼接,能插入变量,插入函数,还能嵌套。
标签模板
标签模板的功能,就是把模板字符串紧跟在函数后面使用,这个函数将被调用来处理这个模板字符串,主要可以用来过滤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- 将全局方法
parseInt和parseFloat移植到了Number对象上 Number.isInterge()判断是否是一个整数Number.isSafeInterge()是否是在-2的53次方到2的53次方之间
Math方法的增加
Math.trunc()去除小数Math.sign()判断是正数负数还是0 ,返回值有5个 +1 -1 0 -0 NaNMath.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不等于-0Object.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.getOwnPropertyNamesObject.getOwnpropertySymbolsObject.keys,返回属性集合getOwnPropertyDescriptor(target,propKey)拦截Object.getOwnPropertyzdescriptor,返回属性的描述对象defineProperty(target,propKey,propDesc)拦截Object.definePropertyObject.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。
- 为了将Object对象的一些明显属于语言内部的方法,比如Object.defineProperty,放到reflect对象上。未来的新方法只会部署到Reflect上面。
- 修改一些Object方法的返回结果,让其变得更合理。比如Object.defineProperty在无法定义属性时会抛出一个错误,而Reflect.defineProperty会返回false。
- 让Object操作都变成函数行为,比如 name in Object和delete Obj[name],而Reflect可以转换成has()和delete()方法
- 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对象