6、ES6

文章目录

  • 一.关于ES6
  • 二.关于变量声明
    • let声明变量
    • const 声明常量
  • 三.变量的解构赋值
  • 四.字符串的扩展
  • 五.函数的扩展
    • 函数默认参数
    • rest参数
    • 箭头函数(函数的新写法)
  • 六.数组的扩展
  • 七.对象的扩展
    • 语法上的简化
    • 对象的解构赋值
  • 八.Symbol:新的数据类型(类似于字符串)独一无二
  • 九.Set:新的数据结构(类似于数组)其中数据独一无二,数组去重
    • Set的属性和方法:都是定义在原型上的
    • Set的遍历操作
    • 关于for of循环
    • Set 与数组的转换
  • 十.Map:新的数据结构(类似于对象),属性名可是任何类型的
    • Map的属性和方法
    • Map的遍历方法
    • Map与数组的转换
  • 十一.promise对象:解决异步问题(面试常问)
    • 基本概念
    • 使用
    • 关于catch
    • Promise.all 全部
    • Promise.race 比赛
  • 十二.async await (ES7 2017Promise优化版)
  • 十三. module 模块化
    • 基本用法:
    • export 改名
    • import改名
    • export default 默认导出
    • 整个文件的引入
  • 十四.class 类
    • 基本概念
    • constructor方法
    • class的继承
  • 十五.高级
    • 事件循环eventloop解决异步问题(面试常问)
    • 手撕promise源码(面试大厂可能会问)
    • Promise完整源码--黑马
      • 构造函数
      • 状态及原因
      • then方法
        • then方法--成功和失败回调
        • then方法-异步和多次调用
      • 异步任务
      • 链式编程
        • 链式编程-fulfilled状态
        • 链式编程-rejected状态
        • 链式编程-pending状态
      • 实例方法-Promise.catch()
      • 实例方法-Promise.finally()
      • 静态方法-Promise.resolve()
      • 静态方法-Promise.reject()
      • 静态方法-Promise.race()
      • 静态方法-Promise.all()
      • 静态方法-Promise.allSettled()
      • 静态方法-Promise.any()
  • 十六.find()
  • 十七.面试题

阮一峰 ES6文档

由于JS语言出现的过于仓促,其中出现了很多并不合理的地方,es6就是对这些不合理的地方加以完善修改。

ES6只是用在VUE和REACT项目中用的比较多,而在jQuery中是不用ES6的。

一.关于ES6

JS:EcmaScript+DOM+BOM

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。

因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。

二.关于变量声明

在原来js中的变量声明用var,使用var可以变量提升;可以重复声明;未声明直接赋值自动声明成全局变量;

let声明变量

  • 没有变量提升
console.log(a)
let a=1; //ReferenceError: Cannot access 'a' before initialization
  • 不允许重复声明
let a=1;
let a=2; //SyntaxError: Identifier 'a' has already been declared
  • 只在块级作用域内有效

块级作用域:{},像 if(){}、for(){}、函数、while{}循环

<h1></h1>

应用:如在js中this给4个div绑定点击事件,点谁显示谁的内容。之前解决这种问题时使用this,现在可以使用let变量声明变量i,这样在每次循环时都会声明一个变量i,总共声明4个i,每次使用的对应的i。

<div>1</div><div>2</div><div>3</div><div>4</div><script>// var divs = document.getElementsByTagName("div")// for (var i = 0; i < divs.length; i++) {//     divs[i].onclick = function () {//         console.log(divs[i].innerText)//     }// }// 出现错误,因为i是全局变量,在循环完成之后i=4,而点击之后没有divs[4],无法读取到其内容// 第一种解决方法:使用this指向当前调用函数的元素// var divs = document.getElementsByTagName("div")// for (var i = 0; i < divs.length; i++) {//     divs[i].onclick = function () {//         console.log(this.innerText)//     }// }// 第二种解决方法:使用let声明i变量,由于let声明变量只在块级作用域内有效,发生4次循环,产生4个i变量,每次使用的都是当前的ivar divs = document.getElementsByTagName("div")for (let i = 0; i < divs.length; i++) {divs[i].onclick = function () {console.log(divs[i].innerText)}}</script>
{let a = 10;var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
var a = [];
for (var i = 0; i < 10; i++) {a[i] = function () {console.log(i);};
}
a[6](); // 输出结果:10 无论是a[几]()结果都是10,因为函数只有在调用的时候才会执行,在调用之前循环已经结束了,使用var声明的全局变量i=10,,在调用时输出值为10.
//如果将i用let声明,则调用a[几]()输出的就是几
  • **暂时性死区:**在一个块级作用域内,如果存在有let声明的变量,那么该变量自动绑定当前作用域,在该作用域内,使用这个名字的变量只能用let声明的。总之,在代码块内,使用let命令声明变量之前,该变量都不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。”暂时性死区“也意味着typeof不再是一个百分百安全的操作。
var tmp = 123;
if (true) {tmp = 'abc'; // ReferenceError,必须使用let声明的tmp,不能使用其他类型的声明let tmp;
}

const 声明常量

const声明一个只读的常量。一旦声明,常量的值就不能改变。但是如果使用常量存储的数组或对象,其中他的内容是可以更改的,因为const声明的变量中存储的是指针是地址,其值并没有发生更改,改的是数组和对象里面的内容。

应用:获取元素对象、正则表达式都不会更改。

     const obj = {name: "小明",age: 18}obj.name = "李雷"console.log(obj)//输出结果:{name: '李雷', age: 18},因为const常量中存储的是对象的地址,并没有发生改变
const a=5;
a=6;//TypeError: Assignment to constant variable.

const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。

const的作用域与let命令相同:只在声明所在的块级作用域内有效。

const命令声明的常量也是不提升。

const声明的变量同样存在暂时性死区,只能在声明的位置后面使用。

const声明的常量,也与let一样不可重复声明。

const a //SyntaxError: Missing initializer in const declaration

三.变量的解构赋值

本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。假如两边的变量和值的数量不一致,则依次对应即可,多余的变量就是undefined,多余的值就不用。

let [变量名1,变量名2,变量名3]=[值1,值2,值3];

案例:

let [foo, [[bar], baz]] = [1, [[2], 3]];//1 2 3
let [foo] = [];//undefined
let [bar, foo] = [1];//1 undefined
let [x, y] = [1, 2, 3];//1 2

四.字符串的扩展

之前在js中的字符串拼接很复杂麻烦,如DOM操作中的innerHTML赋值,在ES6中使用模板字符串就不用拼接了。

模板字符串:

  • 支持换行
  • 可以写变量 ${变量名}
<body><div id="app"></div><script>let app = document.getElementById("app")let msg = "我是模板字符串"let id = "box"app.innerHTML = `<div id=${id}><div>${msg}</div><h1>${3 > 2 ? "haha" : "hehe"}</h1></div>`</script></body>

五.函数的扩展

函数默认参数

用es5实现函数默认参数:如果调用函数时未传入参数值,使用默认值

function fn(a){a=a||5  //利用或||的短路性console.log(a)
}
fn(0/flase/null/"")
//弊端,如果传入0 false null "" 等数据会认为没有传参数,从而使用默认值

es6的写法:将默认的值写在函数名后面的括号中直接对参数赋值。如果在调用函数时传了参数,则默认参数没有用,使用的是传的参数;如果在调用函数时没有传参数,则使用的是默认参数的值。

function fn(a=5){console.log(a)
}
fn() // 5
fn(0) // 0

rest参数

ES6 引入 rest 参数(形式为…变量名),用于获取函数的剩余参数,这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

function fn(a,...b){console.log(a)//1console.log(b)//[2, 3, 4]
}
fn(1,2,3,4)

arguments对象不是数组,而是一个类似数组的对象。所以为了使用数组的方法,必须使用Array.from先将其转为数组。rest 参数就不存在这个问题,它就是一个真正的数组,数组特有的方法都可以使用

注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

// 报错
function f(a, ...b, c) {// ...
}

箭头函数(函数的新写法)

与普通函数相比,箭头函数在写法上的优化:取消了function关键字;在参数和函数体之间用箭头=>代替;

ES5的写法:

var fn=function(m,n){console.log(m,n)}

ES6的写法

let fn=(m,n)=>{console.log(m,n)}

ES6箭头函数的特殊情况:

  • 如果只有一个参数的情况下,默认参数的括号可以省略;
  • 如果函数只是想返回一个值,那么可以省略return及{};
var fn=function(m){return m;}//普通函数let fn=m=>m//箭头函数

箭头函数和普通函数的区别:

  1. 写法不同:箭头函数取消了function关键字,在参数和函数体之间用箭头=>连接。并且在特殊情况只有一个参数时可以省略小括号;函数只是做一个返回值时可以省略return和{}.
  2. 关于this指向:箭头函数中的this指向的是定义时所在的对象,也就是他的父级(内部的this就是定义时上层作用域中的this ).也就是说箭头函数没有自己的this,箭头函数内部的this指向是固定的.相比之下,普通函数的this指向是可变的,指向的是调用他时的元素对象。
  3. 箭头函数不能当作构造函数,不可以对箭头函数使用new命令。因为构造函数的原理中有一步是要更改this指向,而箭头函数的this指向是无法更改的。
  4. 箭头函数不可以使用arguments对象,该对象在函数体内不存在,如果要用,可以用rest参数代替。
function Timer() {this.s1 = 0;this.s2 = 0;// 箭头函数 this指向定义它时的对象timer,其自身的s1++setInterval(() => this.s1++, 1000);// 普通函数 定时器的this指向window,自身的s2并没有发生变化setInterval(function () {this.s2++;}, 1000);
}var timer = new Timer();setTimeout(() => console.log('s1: ', timer.s1), 3100);// 3 输出的是timer的s1和s2
setTimeout(() => console.log('s2: ', timer.s2), 3100);// 0

在之后的框架vue和react中基本上都用的是箭头函数了,使用普通函数很少了。

箭头函数不适用的场合

  1. 定义对象的方法,且该方法内部包括this
const cat = {lives: 9,jumps: () => {this.lives--;}
}
// 上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。
  1. 需要动态this的时候,也不应使用箭头函数.
var button = document.getElementById('press');
button.addEventListener('click', () => {this.classList.toggle('on');
});
// 上面代码运行时,点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。
  1. 另外,如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。

六.数组的扩展

扩展运算符...

… 扩展运算符 可以把数组、类数组、对象、字符串(之前还得用用空字符拼接)、Set、Map结构拆分成以逗号分隔的参数序列。可以用于合并。

应用:

  • Math.min(参数序列)/max(参数序列),如Math.min(1,4,5,3,2):只接受参数序列当参数,不接受数组,而如果想要使用数组,需要ES6的数组扩展。
var arr=[1,5,8]
console.log(Math.min(...arr))
  • 数组/对象合并
    • 法一:concat方法
var arr1 = [1, 2, 3]
var arr2 = [4, 5, 6]
var arr3 = arr1.concat(arr2)
console.log(arr3)//[1,2,3,4,5,6]
- 法二:将数组拆成参数序列,再放进数组中。对象也是同样的方法。
var arr1 = [1, 2, 3]
var arr2 = [4, 5, 6]
console.log([...arr1,...arr2])

七.对象的扩展

语法上的简化

如果对象的属性值是变量,并且变量的名字跟属性名同名,键值对可以省略为一个 。

如果对象的属性值是函数,可以省略 :function

let birth = '2000/01/01';const Person = {name: '张三',//等同于birth: birthbirth,// 等同于hello: function ()...hello() { console.log('我的名字是', this.name); }//箭头函数的写法:hello:()=>{console.log('我的名字是', this.name)}
};

对象的解构赋值

我们的目的是要拿出对象的属性值,不需要再像以前一样声明一个变量存储var name=obj.name;只需要让{}={}..嵌套的解构赋值解构的是最里层的,外层的拿不到

对象和数组的解构模式有两种:绑定和赋值。在使用赋值模式时,当使用对象文字解构赋值而不带声明时,在赋值语句周围必须添加括号 ( ... ).如果你的编码风格不包括尾随分号,则 ( ... ) 表达式前面需要有一个分号,否则它可能用于执行前一行的函数。

最简单案例:

 let obj={name:"黎明",age:18,salary:3500,hobby:{type:"打篮球"}} ;let {name,age,salary,hobby,hobby:{type}}=objconcole.log(name,age,salary,hobby)// 小明 18 3500        {type:"打篮球"}//嵌套结构解构最里层时   变量名:{里层变量名}console.log(type)//打篮球

使用对象解构赋值的函数定义方式:

let obj = { name: "李雷", age: 18 };
function fn({ name, age }) {console.log(name, age)
}//函数中的参数传的是对象,再进行解构赋值
fn(obj)//李雷 18

配合扩展运算符:扩展运算符的解构赋值,不能复制继承自原型对象的属性

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

配合函数默认参数:

// 写法一
function m1({x = 0, y = 0} = {}) {return [x, y];
}// 写法二
function m2({x, y} = { x: 0, y: 0 }) {return [x, y];
}// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]// x 和 y 都有值的情况
m1({x: 3, y: 8}) // [3, 8]
m2({x: 3, y: 8}) // [3, 8]// x 有值,y 无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]// x 和 y 都无值的情况
m1({}) // [0, 0];
m2({}) // [undefined, undefined]m1({z: 3}) // [0, 0]
m2({z: 3}) // [undefined, undefined]

嵌套解构:

let {name,age,hobby:{sport}}={name:"小明",age:18,hobby:{sport:"basketball"}}

八.Symbol:新的数据类型(类似于字符串)独一无二

ES6 引入了一种新的原始数据类型Symbol,类似于字符串,表示独一无二的值。

Symbol数据要通过Symbol函数来生成,但生成的Symbol数据都是不一样的 let a=Symbol("name");,里面传的参数是没有任何实际意义的,只是为了更有描述性方便看而已。

应用:对象的属性名必须是唯一的,独一无二的。如果自定义的属性或方法与原来对象本身就存在的属性或方法重名的话,就会将原来的属性或方法给改了,后面的把原来的给覆盖了,影响到原来的属性或方法的使用。

js中的原始数据类型有5+1种:string number boolean null undefined symbol.

typeof的返回值有string、number、boolean、undefined、function、object、symbol。

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();s1 === s2 // false,Symbol数据是独一无二的,即使是看起来一样,实际上都是不一样的// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');s1 === s2 // false  里面的参数没有任何实际意义,只是为了方便查看而已

symbol做为对象的属性名:在对象的内部,使用 Symbol 值定义属性时,Symbol 值必须放在方括号之中。

let mySymbol = Symbol();// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';// 第二种写法
let a = {[mySymbol]: 'Hello!'
};

九.Set:新的数据结构(类似于数组)其中数据独一无二,数组去重

Set在工作中的使用场景较少,基本只有数组去重,其他时候我们还是使用的是数组。

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值(相当于自动去重),并且相对于数组没有角标

使用new Set()创建数据时里面传的参数是数组;而在数组中使用new Array(1,2,3)创建数组时传的参数是参数序列。

const set = new Set([1, 2, 3, 4, 4]);//{1,2,3,4} 自动去重let arr = new Array(1, 2, 3, 4, 3, 2, 1)
console.log(arr)//数组  [1, 2, 3, 4, 3, 2, 1]
let set = new Set(arr)
console.log(set)// {1, 2, 3, 4}

实际场景的应用:利用set数组去重:

[...new Set(array)]

Set的属性和方法:都是定义在原型上的

Set的这些属性和方法定义在原型中。

  • size :返回Set实例的成员总数
  • add(value) :添加某个值,返回 Set 结构本身,支持链式调用
  • delete(value) :删除某个值,返回一个布尔值,表示删除是否成功。不支持链式调用
  • has(value):返回一个布尔值,表示该值是否为Set的成员。
  • clear():清除所有成员,没有返回值。

案例:

s.add(1).add(2).add(2);
// 注意2被加入了两次,但最终加入的只有一个s.size // 2s.has(1) // true
s.has(2) // true
s.has(3) // falses.delete(2);
s.has(2) // false

Set的遍历操作

  • keys():返回键名的遍历器
  • values():返回键值的遍历器 由于Set结构没有键名,只有键值(或者说键名和键值是同一个值),所以keys方法和values方法的行为完全一致。
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员,对每个成员做同样的事情。

关于for of循环

循环对象可以用for in,不能用for of;循环数组既可以用for in,有可以用for of.

数组可以用for循环有角标,而set数据没有角标,不能用for循环。

 //循环对象const obj = {a: 1,b: 2,c: 3}for (let i in obj) {console.log(i)// a// b// c}for (let i of obj) {console.log(i)// Uncaught TypeError: obj is not iterable 不可迭代的 报错了}

循环数组用for in 和for of都行

//循环数组
const arr = ['a', 'b', 'c']
// for in
for (let i in arr) {console.log(i)// 0// 1// 2
}
// for of
for (let i of arr) {console.log(i)// a// b// c
}

循环Set数据用for of

//循环set
let set=new Set([1,2,3,4,3,2,1])
//遍历keys键 在set中keys和values都是一样的,但是在Map中是不一样的。
for(let item of set.keys()){console.log(item)//1 2 3 4
}
//遍历values
for(let item of set.values()){console.log(item)//1 2 3 4
}
//遍历整体entries,输出的是整体的键值对
for(let item of set.entries()){console.log(item)//[1,1]//[2,2]//[3,3]//[4,4]
}
//foreach()
set.foreach((item)=>{console.log("haha"+item)
})

结论:

关于for in:专注于角标key

for … in 循环返回的值都是数据结构的 键值名。遍历对象返回的对象的属性名key值,遍历数组返回的数组的下标(key),遍历字符串返回的是角标。

关于for of: 专注于值value

for of 循环用来直接获取一对键值对中的值,而 for in 获取的是 键名。

一个数据结构只要部署了 Symbol.iterator 属性, 就被视为具有 iterator接口, 就可以使用 for of循环。 要想知道是否能用for of ,即是否具有iterator接口可以通过查看其原型看是否有Symbol(Symbol.iterator) console.log(Array.prototype),Object没有,而Set和Array有。

哪些对象可以使用for of循环?

  • 数组 Array
  • Map
  • Set
  • String
  • arguments对象 类数组
  • Nodelist对象, 就是获取的dom列表集合 dom获取的元素对象

Set 与数组的转换

使用扩展运算符...

 //Set转换成数组 把数组当作Set的参数  
let set = new Set([1, 2, 3, 4, 3, 2, 1])
console.log([...set])//[1,2,3,4]//数组转换成Set
let arr = [1, 2, 3, 4, 3, 2, 1]
let set2 = new Set(arr)
console.log(set2)

十.Map:新的数据结构(类似于对象),属性名可是任何类型的

Map在实际工作中使用较少,常作为数据中转,拿到其中的键、值。

ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围即属性名类型不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应。

Map接收一个数组当参数,数组中又有若干个小数组,在小数组中有两项,一项代表key(不限制数据类型),一项代表value.

const map = new Map([['name', '张三'],['title', 'Author']
]);const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')//使用set()给map添加项

Map的属性和方法

Map的这些属性和方法定义在原型中。

  • size :返回 Map 实例的成员总数
  • set(key, value) :添加项,set方法设置键名key对应的键值为value
  • get(key) get方法读取key对应的键值,如果找不到key,返回undefined。
let map=new Map()
let arr=[1,2,3,4]
map.set(arr,180)
console.log(map.get(arr))//180
console.log(map.get([1, 2, 3, 4]))//undefined,因为两个的地址不一样,不是同一个
  • has(key):has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中
  • delete(key):delete方法删除某个键,返回true。如果删除失败,返回false。
  • clear():clear方法清除所有成员,没有返回值。

Map的遍历方法

  • keys():返回键名的遍历器
  • values():返回键值的遍历器
  • entries():返回键值对的遍历器
  • forEach():使用回调函数遍历每个成员

Map与数组的转换

Map转换成数组:使用扩展运算符…

数组转换成Map:使用Map()

//数组转换成Map 把数组当作Map的参数即可
const map = new Map([[1, 'one'],[2, 'two'],[3, 'three'],
]);//Map转换成数组
[...map.keys()]
// [1, 2, 3][...map.values()]
// ['one', 'two', 'three'][...map.entries()]
// [[1,'one'], [2, 'two'], [3, 'three']][...map]
// [[1,'one'], [2, 'two'], [3, 'three']]

十一.promise对象:解决异步问题(面试常问)

 /* 1.发送请求,根据手机号查询个人身份证号2.发生请求,根据身份证号查询银行卡余额3.发送请求,根据银行卡余额查询贷款信息*/$.ajax({//1})$.ajax({//2})$.ajax({//3})/*这种方法是错误的,无法实现异步,无法确定是哪一个先执行结束,能否在后一个执行前拿到前一个的结果作为后一个的参数。如果加入定时器的话,设定时间过长会影响用户体验,设定时间过短又无法保证按照顺序执行,在万不得已的情况下不要使用定时器来解决异步的问题1s而promise就是用来解决这种异步问题的,保证后一个的请求在前一个请求结束之后进行。*/

基本概念

异步:先写的代码不一定先执行,重新开一个线程执行 。目前学到的异步有定时器、ajax。

我们想要使用上一个请求执行结束之后得到的结果来做下一个请求的参数,使用ajax请求执行的顺序由于网速等各种原因无法得到控制,可能会导致在发送请求时拿不到想要的结果。为了解决这种问题,如果使用定时器来解决这个问题,由于定时器的时间难以把控,在万不得已的情况下不要使用。

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。

1.什么是异步编程? 传统方案怎么解决异步编程?

简单来说,异步编程就是在执行一个指令之后不是马上得到结果,而是继续执行后面的指令,等到特定的事件触发后,才得到结果。

之前我们一般通过回调函数的方式解决异步编程,如下

 $.ajax({success:function(){$.ajax({success: function(){$.ajax({success:function(){...   }})}})}})
       setTimeout(function () {console.log(1)setTimeout(function () {console.log(2)setTimeout(function () {console.log(3)setTimeout(function() {console.log(4)}, 500);}, 1000)}, 2000)}, 3000)

这样的代码看层级少了当然还是可以凑合看的,但是在某些场景下,我们就需要一次又一次的回调…回调到最后,会发现我们的代码就会变成金字塔形状?这种情况被亲切地称为回调地狱。

回调地狱不仅看起来很不舒服,可读性比较差,难以维护,除此之外还有比较重要的一点就是对异常的捕获无法支持。

使用回调函数解决异步问题的缺点:采用传统的回调函数来解决异步问题,会出现回调地狱的问题,代码呈现横向发展的趋势,可读性差,难以维护,当业务逻辑较多时对异常的捕获难以支持。

使用

Promise是一个对象,能保证执行顺序,即使是后来的再简单也能保证先来的先执行。主要是靠then()管控,promise本身是同步的,promise中的then是异步的。

Promise有三种状态

  1. pending: 初始状态,既不是成功,也不是失败状态。
  2. fulfilled: 意味着操作成功完成。
  3. rejected: 意味着操作失败。

基本语法:

/*1.构造函数创建Promise对象 接收函数为参数,函数中的默认参数resolve和reject是两个函数名resolve():在异步操作成功的时候调用,把DOM操作过后的值传出去,传到then()中的参数中,将要传出去的值放在resolve中的参数。reject():在异步操作失败的时候调用,把值传出去。p.then()保证Promise先走,走完之后再走then()里面的事情,要把需要控制执行顺序的所有的步骤都写在Promise中,交给Promise监管。then()以函数为参数,将resolve中传出来的值作为函数的参数,做下面的操作。在promise执行结束之后需要调用resolve函数将状态由pending改为fulfilled,才能执行下面的then。在链式的then()中要return new Promise(),用Promise来限制住then()中的执行顺序,并使用resolve()或rejected()来转换状态,否则then()中就是同步的了,无法按照我们想要的顺序来执行。
*/
let promise = new Promise(function(resolve, reject) {// … 异步操作的代码if (/* 异步操作成功 */){resolve(value);} else {reject(error);}
});

Resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 fulfilled),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;

Reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定fulfilled状态和rejected状态的回调函数。成功的函数作为then()的第一个参数,失败的函数作为then()的第二个参数。resolve()和reject()本质上是一样的,执行哪一个都可以,只是习惯上在成功时使用resolve()在失败时使用reject(),resolve()和reject()函数与ajax中的success()和error()是不一样的,resolve()和reject()是自己人为规定的,而ajax中的success()和error()是请求成功和失败只能这么走。

promise.then(function(value) {// success
}, function(error) {// failure
});

then方法是Promise实例状态改变时的回调函数
then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为fulfilled调用,第二个回调函数是Promise对象的状态变为rejected时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式调用写法,即then方法后面再调用另一个then方法。then()中需要return new Promise(),因为需要使用Promise来限制执行顺序,在then()中不使用promise的话是同步执行的。

 //使用Promise实现解决定时器异步的案例,这种写法很乱,一般不使用,我们之后使用的是把要做的Promise封装起来配合axios使用let promise = new Promise((resolve, reject) => {//异步操作代码setTimeout(() => {console.log(1)resolve()//将Promise对象的状态由pending改为fulfilled,才能继续执行then()的内容}, 3000);/*错误1 :将resolve()写在这里,结果是先执行2再执行1,因为Promise解决异步是靠then()的,定时器是异步的,resolve()是同步的,先执行的resolve(),一进来就告诉状态改为了成功可以执行then()了。在Promise内部写的代码的执行顺序与在外面写的执行顺序是一样的,解决异步是靠then()*/})promise.then(() => {//在then()中需要return new Promise()使用Promise来监管执行顺序return new Promise((resolve, reject) => {setTimeout(() => {console.log(2)reject("我是失败的数据")//失败的函数 将Promise对象的状态由pending改为rejected}, 2000);})}).then(() => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(3)resolve()}, 1000);})}, (error) => {console.log(30, error)//这里没有使用resolve()和reject()也继续往下面走了,这是因为没有使用Promise限制执行顺序,是同步}).then(() => {setTimeout(() => {console.log(4)}, 500);//最后一个不需要再用return new Promise})

封装函数的Promise案例:

//将要异步做的事情封装成函数,直接调用,简化过程。在后面的实际工作中常与axios一起使用,是使用的第三方插件自动返回的就是promise,基本不用自己做封装函数,直接写后面的调用和then()function p() {return new Promise((resolve, reject) => {setTimeout(() => {console.log(1)resolve()}, 3000);})}function p1() {return new Promise((resolve, reject) => {setTimeout(() => {console.log(2)resolve()}, 2000);})}function p2() {return new Promise((resolve, reject) => {setTimeout(() => {console.log(3)resolve()}, 1000);})}p().then((res) => {return p1()//在函数中都要加上return,上面p1函数中的return是p1函数中的return,不是then()的return,如果不加return 的话then()就会返回一个默认的空的Promise}).then(() => {return p2()}).then(() => {console.log("完事了")})

关于catch

Promise.prototype.catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。与then()并列写。

Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。是错误具有”冒泡“性质,不是catch()能冒泡,只要reject()了,即使是使用的then()的第二个参数也能实现;如果把处理错误的函数放在了其他then()的第二个参数中了,其他的then()也能使用,不用每一个then()都写一个处理错误的函数了,同理catch()也是。也就是说,错误总是会被下一个catch语句捕获。

catch和then中第二个函数的区别:

主要区别就是,如果在then的第一个函数里抛出了异常,后面的catch能捕获到,而then的第二个函数捕获不到,只能从后面的捕获。如下

new Promise((resolve, reject) => {resolve(1)
}).then(() => {return new Promise((resolve, reject) => {// 在第一个函数中抛出错误throw new Error("失败了嘤嘤嘤")})
}, (err) => {console.log(111,err); // 在then的第二个函数中处捕获不到异常,只能在后续的then中捕获到异常
}).then(()=>{},(err)=>{console.log(222,err) // 在这里才被捕获到了异常
})
new Promise((resolve, reject) => {resolve(1)
}).then(()=>{return new Promise((resolve, reject) => {// 在第一个函数中抛出错误throw new Error("失败了嘤嘤嘤")})
}).catch(err=>{console.log("catch捕获到了",err) //在catch这里异常被捕获到了
})

Promise.all 全部

p1,p2,p3是三个promise实例

const p = Promise.all([p1, p2, p3]);

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

用法:

let p1 = new Promise((resolve, reject) => {resolve('成功了')
})let p2 = new Promise((resolve, reject) => {resolve('success')
})Promise.all([p1, p2]).then((result) => {console.log(result)               //['成功了', 'success']
}).catch((error) => {console.log(error)
})let p3 = Promise.reject('失败')Promise.all([p1,p3,p2]).then((result) => {console.log(result)
}).catch((error) => {console.log(error)      // 失败了,打出 '失败'
})

特殊的:

const p1 = new Promise((resolve, reject) => {resolve('hello');
})
.then(result => result)
.catch(e => e);const p2 = new Promise((resolve, reject) => {reject("报错了")
})
.then(result => result)
.catch(e => e);Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));//['hello' '报错了']

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()的catch方法。

Promise.race 比赛

const p = Promise.race([p1, p2, p3]);

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

谁快谁管用,如果快的是resolve()则后面执行then(),如果快的是reject()则后面执行catch().

let p1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('success')},1000)
})let p2 = new Promise((resolve, reject) => {setTimeout(() => {reject('failed')}, 500)
})Promise.race([p1, p2]).then((result) => {console.log("成功的",result)
}).catch((error) => {console.log("失败的",error)  // 打印的是 '失败的  failed'  因为p2更快,而p2是reject(),执行catch
})

同样,特殊的如果p2有自己的catch,可以自己处理,则处理了错误之后变成了resolved,输出结果变成了’ 成功的 failed’.

十二.async await (ES7 2017Promise优化版)

async:异步函数,函数内部有异步操作。

async 函数的返回值是promise(可以调用then()和catch()),return的值默认就是async函数的resolve的值。

await如果等到的是promise,整个表达式的值即await promise就是Promise中resolve的值,并不是reject的值,想要处理reject的值就需要fn().catch()(可以catch是因为异步函数的返回值是Promise),失败就catch()。

async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

async函数会返回一个promise对象如果在函数中return一个值,那么该值会通过Promise.resolve()传递出去。

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。
如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。

async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。(语法糖:本质上没有什么区别,只是样子上变了,更好理解了 )

//使用函数来存储Promise,手动控制Promise的执行  
function p1(){return  new Promise((resolve,reject)=>{setTimeout(()=>{console.log("aaa");resolve("结束了aaa")},2000)})}function p2(){return new Promise((resolve,reject)=>{setTimeout(()=>{console.log("bbb");reject("结束了bbb")},1000)})}async function fn(){//异步操作let a=await p1() //先阻塞了let b=await p2() //后阻塞了console.log(a,b);}
//rejectfn().catch((err)=>{console.log(err)})
//aaa
//bbb
//结束了aaa 结束了bbb

因为async的返回值是promise,所以也可以使用then来处理和catch来处理reject

比如:

async function f() {// 等同于// return 123;return await 123;//async函数中return的值默认是async函数的resolve的值 resolve(123)
}f().then(v => console.log(v))//123

Promise 和 async await 的对比:

function p1(){return new Promise((resolve,reject)=>{setTimeout(() => {console.log(1)resolve("1aaa")}, 3000);})
}
function p2(){return new Promise((resolve,reject)=>{setTimeout(() => {console.log(2);reject("2bbb")}, 2000);})
}
function p3(){return new Promise((resolve,reject)=>{setTimeout(() => {console.log(3)resolve("3ccc")}, 1000);})
}
// 方法一:Promise
p1().then((res)=>{console.log("p1 resolve的"+res)// 注意:这里执行p2函数之后一定要returnreturn p2()
}).then(()=>{},(err)=>{console.log("p2 reject的"+err)return p3()
}).then((res)=>{console.log("p3 resolve的"+res)
})// 方法二:async awiat
async function fn(){let a=await p1()let b=await p2()}
// async await reject的在catch中处理
fn().catch((err)=>{console.log(err)p3()
})

十三. module 模块化

基本用法:

1.export :导出的一定是个完整的变量声明语句,同时导出多个可以 export{ 变量名1,变量名2}

export let a =1; export function fn(){};

2.import:引入的时候注意,路径名如果是当前目录需要加 ./,不然引入的就是核心模块node_modules.

import {a,b} from “./a.js”;

export 改名

通常情况下,export输出的变量就是本来的名字,但是可以使用as关键字重命名。

导出的同时改名字:

function v1() { ... }
function v2() { ... }export {v1 as streamV1,v2 as streamV2,
};

这是时候其他文件引入的时候

import {streamV1 } from 路径名

import改名

import { lastName as surname } from './profile.js';

注意:

import命令输入的变量都是只读的,因为它的本质是输入接口。

也就是说,不允许在加载模块的脚本里面,改写接口。

如果a是一个对象,改写a的属性是允许的.(因为改的是属性,地址指针是没变化的)

import {a} from './xxx.js'a = {}; // Syntax Error : 'a' is read-only;//如果a是一个对象,改写a的属性是允许的。a.foo = 'hello'; // 合法操作

export default 默认导出

  1. 默认导出,导出的是一个值,不是一个声明语句。因为default代表的就是变量,后面只需要跟值就可以
  2. 引入的时候变量名可以是任意的,且没有{}.因为核心原理as重新命名了,而去掉{}是因为默认导出只能有一个,不可能有多个。
  3. 一个文件中只能有一个默认导出。
export default function() {console.log('foo');
}//这里默认导出的应该是值,不能是声明语句。import 任意名字 from "路径"//这个名字无论叫什么都是对应的fn()

核心原理:

export {a as default}

import {default as c任意}

既想要引入默认导出又想要引入普通导出:普通导出放在{}中,非普通导出在外面,用,分隔。

import hello,{b} from "./a,js"

整个文件的引入

整个文件引入时不需要导出

import "./index.css"
import "../index.js"  //此情况被引入的文件无需导出

十四.class 类

class类在vue中基本不用,在react中的类组件中占比逐年降低,但是class类在晋升往架构师源码方向会非常有用。

基本概念

类中定义的所有属性和方法本质上都是定义在原型上的。

ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到。

本质上 class (类)是构造函数的语法糖。

新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。

例如:

// ES5的构造函数的写法
function Point(x, y) {this.x = x;this.y = y;
}Point.prototype.toString = function () {return '(' + this.x + ', ' + this.y + ')';
};var p = new Point(1, 2);

上面的代码用 ES6 的class改写,就是下面这样。

每一个类都有一个默认的构造方法(等同于原来的构造函数),在实例化对象的时候会自动调用类默认的构造方法。

**在类中定义的所有方法默认都是定义在原型上的。**类中的方法不需要function关键字,方法之间也不需要逗号,

每个构造函数都有原型属性,(原型是个属性)这个原型指向原型对象,原型对象中拥有一个属性constructor,它指回构造函数。

// ES6的class类的写法
class Point {constructor(x, y) { //这就是构造方法this.x = x; //代表实例对象this.y = y;}
//注意,定义toString()方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法与方法之间不需要逗号分隔,加了会报错。toString() {return '(' + this.x + ', ' + this.y + ')';}
}
let p=new Point(1,2)//本质上是调用Point对象的方法

ES6 的类,完全可以看作构造函数的另一种写法。

class Point {// ...
}typeof Point // "function"
Point === Point.prototype.constructor // true

使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

class Bar {doStuff() {console.log('stuff');}
}const b = new Bar();
b.doStuff() // "stuff"

构造函数的prototype属性,在 ES6 的“类”上面继续存在。

事实上,类的所有方法都定义在类的prototype属性上面。

class Point {constructor() {// ...}toString() {// ...}toValue() {// ...}
}
// 等同于
Point.prototype = {constructor() {},toString() {},toValue() {},
};

prototype对象的constructor()属性,直接指向“类”的本身,这与 ES5 的行为是一致的。

Point.prototype.constructor === Point // true

constructor方法

constructor()方法是类的默认方法,通过new命令生成对象实例时,会自动调用该方法。

一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。

class Point {
}// 等同于
class Point {constructor() {}
}

class的继承

class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。

extends 的写法比 ES5 的原型链继承,要清晰和方便很多。

在继承时必须先调用父类的构造方法,如果子类没有写自己的constructor(),系统默认调用父类的constructor();如果子类写了自己的constructor(),则在里面必须使用super(“共同参数1”,“共同参数2”,…)主动调用父类的constructor()。

ES5的继承:先创建一个子类实例对象,再往这个对象上面添加属性,实例在前 继承在后。(new关键字的实现原理就是先创建一个空对象)

ES6的继承:直接给一个匿名对象加上父类的属性,再把这个对象赋予子类作为子类的实例,继承在前 实例在后。(子类要主动使用super调用父类的构造函数)

使用ES6的class类的方法:把子类中的共同的属性和方法抽出来定义成父类,如果子类没有定义自己的constructor(),默认调用父类的constructor();如果子类定义了自己的constructor(),则必须先使用super()主动调用父类的构造方法,再加入属于自己的参数。另外,对于需要传参的构造方法,子类的constructor()要传入所有的参数,子类constructor()中的super(实参)也要传入父类构造函数中所需要的实参,这个实参是通过子类的constructor()的形参传进来的。

基本语法:

  class Point {fn(){console.log(1)}}class ColorPoint extends Point {      }  let a=new ColorPoint();a.fn()

Point是父类,ColorPoint是子类,它通过extends关键字,继承了Point类的所有属性和方法。

Object.getPrototypeOf():用来从子类上获取父类。

class Point { /*...*/ }class ColorPoint extends Point { /*...*/ }Object.getPrototypeOf(ColorPoint) === Point
// true

因此,可以使用这个个方法判断,一个类是否继承了另一个类。

super的用法:super代表父类,super()代表父类的构造方法。

super这个关键字,既可以当作函数使用,也可以当作对象使用。 在这两种情况下,它的用法完全不同。

第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super()函数。

class A {}class B extends A {constructor() {super();}
}

调用super()的作用是形成子类的this对象,把父类的实例属性和方法放到这个this对象上面。子类在调用super()之前,是没有this对象的,任何对this的操作都要放在super()的后面。

第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。

class Point {fn(){console.log(1)}aa(){console.log("父类")}
}class ColorPoint extends Point {aa(){super.aa()//super当作父类的对象来使用}
}  
let a=new ColorPoint();
a.aa()

ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用super()方法,子类就得不到自己的this对象。

class Point { /* ... */ }class ColorPoint extends Point {constructor() {}
}let cp = new ColorPoint(); // ReferenceError

为什么子类的构造函数,一定要调用super()?原因就在于 ES6 的继承机制,与 ES5 完全不同。ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。

注意,这意味着新建子类实例时,父类的构造函数必定会先运行一次

class Foo {constructor() {console.log(1);}
}class Bar extends Foo {constructor() {super();//super当作父类的构造函数来使用console.log(2);}
}const bar = new Bar();
//1
//2

上面示例中,子类 Bar 新建实例时,会输出1和2。原因就是子类构造函数调用super()时,会执行一次父类构造函数。

另一个需要注意的地方是,在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。

class Point {constructor(x, y) {this.x = x;this.y = y;}
}class ColorPoint extends Point {constructor(x, y, color) {//this.color = color; // ReferenceErrorsuper(x, y);//主动调用父类的构造方法,要带有实参,参数从子类的构造方法中传进来this.color = color; // 正确}
}
let a = new ColorPoint(5, 9, "red")
console.log(a)//{x: 5, y: 9, color: 'red'}

使用es5和es6对比的例子:

        //es5没有继承的写法/* function Benz(color, price, name, luxury) {this.color = colorthis.price = pricethis.name = namethis.luxury = luxury}function Bmw(color, price, name, sport) {this.color = colorthis.price = pricethis.name = namethis.sport = sport}function Audi(color, price, name, business) {this.color = colorthis.p rice = pricethis.name = namethis.business = business}let bz = new Benz("白色", 100000, "bz", "便捷")let bm = new Bmw("黑色", 20000, "bm", "运动")let ad = new Audi("粉色", 500000, "ad", "商务")console.log(bz, bm, ad)*///es6的class类继承写法class Car {constructor(color, price, name) {this.color = colorthis.price = pricethis.name = name}fn() {console.log("我是父类的方法")}}class Benz extends Car {constructor(color, price, name, luxury) {super(color, price, name)this.luxury = luxury}}class Bmw extends Car {constructor(color, price, name, sport) {super(color, price, name)this.sport = sport}}class Audi extends Car {constructor(color, price, name, business) {super(color, price, name)this.business = business}}let bz1 = new Benz("白色", 100000, "bz", "便捷")let bz2 = new Benz("黑色", 200000, "bz", "贼便捷")let bm = new Bmw("黑色", 20000, "bm", "运动")let ad = new Audi("粉色", 500000, "ad", "商务")console.log(bz1, bz2, bm, ad)bz1.fn()

例子的执行结果:

十五.高级

事件循环eventloop解决异步问题(面试常问)

事件循环:所有的同步任务都在主线程上执行,遇到异步任务将其存放入任务队列中,当主线程执行结束之后再从任务队列中按照“先进先出”的原则检查代码拿到主线程中去执行,执行结束之后再去任务队列中再取出下一个异步任务拿到主线程去执行,这样依次反复实现事件循环。

什么是事件循环?同步任务优先执行,异步任务放在事件队列中,遇到异步任务分为宏任务和微任务,每次宏任务执行完,需要清除本轮所有的微任务,再从事件队列中取出下一个任务,以此往复。

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 遇到异步任务, 进入Event Table并注册回调函数; 等到指定的事件完成(如ajax请求响应返回, setTimeout延迟到指定时间)时,Event Table会将这个回调函数移入Event Queue。
  3. 当栈中的代码执行完毕,执行栈(call stack)中的任务为空时,主线程会先检查micro-task(微任务)队列中是否有任务,如果有,就将micro-task(微任务)队列中的所有任务依次执行,直到micro-task(微任务)队列为空; 之后再检查macro-task(宏任务)队列中是否有任务,如果有,则取出第一个macro-task(宏任务)加入到执行栈中,之后再清空执行栈,检查micro-task(微任务),以此循环,直到全部的任务都执行完成

遇到同步任务直接执行,遇到异步任务分类为宏任务(macro-task)和微任务(micro-task)。

宏任务:整体的js代码、 setTimeout、 setInterval

微任务:Promise中的then、 process.nextTick

Promise是同步

执行原则:

有微则微,无微则宏

如果微任务列表里面有任务 会执行完毕后在执行宏任务。

执行原则:从整体到局部,先从整体判微宏,先执行整体的微,如果整体微中有宏,将其放到整体宏的后面,直至所有的整体微执行结束;再执行整体宏,如果整体宏中还有宏,再继续往宏队列后面放。

案例1:

 //这是一个同步任务直接被执行	console.log('1')//这是一个宏任务,放进宏任务列表setTimeout(function () {console.log('2')});new Promise(function (resolve) {console.log('3');//这是同步任务直接被执行 异步任务在then中resolve();}).then(function () {//then是个微任务 ,放进微任务列表console.log('4')setTimeout(function () {console.log('5')});//又开了一个宏任务,需要将之前的宏任务执行结束之后,再执行新的宏任务})
//最终结果为: 1、3、4、2、5

案例:

console.log('script start');//1setTimeout(function() {console.log('timeout1');
}, 10);//5new Promise(resolve => {console.log('promise1');//2resolve();setTimeout(() => console.log('timeout2'), 10);//6
}).then(function() {console.log('then1')//4
})console.log('script end');//3

案例:

 Promise.resolve().then(()=>{console.log('Promise1') //2setTimeout(()=>{console.log('setTimeout2')//5},0)
});
setTimeout(()=>{console.log('setTimeout1')//3Promise.resolve().then(()=>{console.log('Promise2') //4   })
},0);
console.log('start');//1

案例:注意:此处,这里先执行4再执行hello,是因为把resolve放到定时器里面了,只有resolve出去看才能执行then,否则根本不能走then.

   console.log(1)new Promise((resolve,reject)=>{console.log(2);setTimeout(()=>{resolve("hello");console.log(4)})}).then((res)=>{console.log(res)})console.log(3)//1 2 3 4 hello
// 注意:这里先执行4再执行hello,是因为把resolve放到定时器里面了,只有resolve出去看才能执行then,否则根本不能走then

案例:

console.log(1)//同步 第1个执行
setTimeout(() => {console.log(2)
});//异步 宏任务 放一放
//Promise的异步是在then中的
new Promise((resolve) => {console.log(3)//同步  第2个执行resolve()setTimeout(() => {console.log(5)});
}).then(() => {//异步 微任务console.log(4)
})
//1 3 4 2 5Promise.resolve().then(() => {console.log(1)setTimeout(() => {console.log(2)});
})
setTimeout(() => {console.log(3)Promise.resolve().then(() => {console.log(4)})
});
console.log(5)
//5 1 3 4 2

手撕promise源码(面试大厂可能会问)

1.基本结构:

//Promise的三种状态pengding fulfilled rejected 默认是在pending,使用常量存储三种状态
/*使用常量将字符串状态存储起来,并且用大写字母表示。使用常量将状态存储起来,而不是直接使用字符串:这是因为使用常量容易找到错误,使用字符串的话拼写错误不容易发现。使用大写:这是因为使用常量存储一些字符串不打算修改的话都是用大写,以便于跟普通变量做区分。
*/
/*Promsie的初始值:初始状态是pending
*/
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise{constructor(fn){this.status = PENDING;//给当前对象加属性使用this,默认状态时pending状态this.value=undefined;//存放resolve传递出来的值fn(this.resolve,this.reject);//构造函数接收一个函数当参数,函数中有两个参数resolve函数和reject函数}resolve(res){//更改Promise的状态从pending->fulfilledif(this.status===PENDING){this.status=FULFILLED;this.value=res;}}reject(res){if(this.status===PENDING){this.status=REJECTEDthis.value=res;}}
}
new MyPromise((resolve,reject)=>{resolve("hello")  
})    //Uncaught TypeError: Cannot read properties of undefined (reading 'status')  类型错误 对象和属性不匹配

这里报错的原因主要是因为this指向的问题,实例化之后,resolve中的this指向的是undefined

解决方案改为箭头函数即可:因为箭头函数的this指向不会发生改变,始终指向定义时所在的对象

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise{constructor(fn){this.status = PENDING;// 默认初始状态是pendingthis.value=undefined;//存放resolve传递出来的值fn(this.resolve,this.reject);}// 注意点1:resolve和reject函数要用箭头函数,否则this指向undefinedresolve=(res)=>{// resolve函数要做的把状态改为fulfilled,并把resolve的值传出去if(this.status===PENDING){this.status=FULFILLED;this.value=res;}}reject=(res)=>{if(this.status===PENDING){this.status=REJECTEDthis.value=res;}}
}new MyPromise((resolve,reject)=>{resolve("hello")
})

2.实现then功能

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise{constructor(fn){this.status = PENDING;// 默认初始状态是pendingthis.value=undefined;//存放resolve传递出来的值fn(this.resolve,this.reject);}// 注意点1:resolve和reject函数要用箭头函数,否则this指向undefinedresolve=(res)=>{// resolve函数要做的把状态改为fulfilled,并把resolve的值传出去if(this.status===PENDING){this.status=FULFILLED;this.value=res;}}reject=(res)=>{if(this.status===PENDING){this.status=REJECTEDthis.value=res;}}/**************then的实现*********************///then()中有两个参数,一个是resolve的处理函数,一个是reject的处理函数 通过status的状态来判断执行的是resolve还是rejectthen(onFulfilled,onRejected){if(this.status===FULFILLED){onFulfilled(this.value)//resolve的参数}if(this.status===REJECTED){onRejected(this.value)}}
}
new MyPromise((resolve,reject)=>{reject("hello")
}).then((res)=>{console.log("成功",res)
},(error)=>{console.log("失败",error)
})

有一个细节问题,对于原生promise,如果then中传入的不是函数,运行是不会报错的,如下

但是我们的代码运行很明显是会报错的,因为我们是直接将他们当作函数来用的,下面我们做一下处理

const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise{constructor(fn){this.status = PENDING;this.value=undefined;//存放resolve传递出来的值fn(this.resolve,this.reject);}// 注意点1:resolve和reject函数要用箭头函数,否则this指向undefinedresolve=(res)=>{if(this.status===PENDING){this.status=FULFILLED;this.value=res;}}reject=(res)=>{if(this.status===PENDING){this.status=REJECTEDthis.value=res;}}then(onFulfilled,onRejected){/**************************** 加入如下判断 判断onFulfilled的类型是否为函数,如果是直接处理,如果不是把他们当作空函数*******************************/onFulfilled=typeof(onFulfilled)=="function"?onFulfilled:()=>{}onRejected=typeof(onRejected)=="function"?onRejected:()=>{}/*******************************************************************/      if(this.status===FULFILLED){onFulfilled(this.value)}if(this.status===REJECTED){onRejected(this.value)}}
}new MyPromise((resolve,reject)=>{resolve("hello")
}).then("",(error)=>{console.log("失败",error)
});

3.实现异步功能

/*对于默认的promise*/
console.log(1);
new Promise((resolve,reject)=>{console.log(2);resolve("hello")}).then((res)=>{console.log(res)});console.log(3)
//输出结果1 2 3 hello
---------------------------------------------------------------------------------
/*对于我们现在的MyPromise,还未实现异步*/console.log(1);new MyPromise((resolve,reject)=>{console.log(2);resolve("hello")}).then((res)=>{console.log(res)});console.log(3)
//输出结果 1 2 hello 3

解决方案:在then()中的resolve和reject处理中加入定时器延迟执行:

问题暂时可以解决。

但是:对于这个案例,正常应该输出1 2 3 4 hello

但是我们封装的promise显示的是1 2 4 3 hello

        const PENDING = "PENDING";const FULFILLED = "FULFILLED";const REJECTED = "REJECTED";class MyPromise {constructor(fn) {this.status = PENDING;this.value = undefined;//存放resolve传递出来的值/*定义两个数组 存放resolve和reject后的值 使用数组是因为使用链式调用时不止一个resolve*/this.resovleArr = [];this.rejectArr = []fn(this.resolve, this.reject);}resolve = (res) => {/******延迟执行**********/setTimeout(() => {if (this.status === PENDING) {this.status = FULFILLED;this.value = res;this.resovleArr.forEach(cb => {cb(res)})}})}reject = (res) => {setTimeout(() => {if (this.status === PENDING) {this.status = REJECTEDthis.value = res;this.rejectArr.forEach(cb => {cb(res)})}    })}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) == "function" ? onFulfilled : () => { }onRejected = typeof (onRejected) == "function" ? onRejected : () => { }if (this.status === PENDING) {/*****将方法存储起来 等到resolve和reject的时候再使用*****/this.resovleArr.push(onFulfilled);this.rejectArr.push(onRejected)/*****将方法存储起来*****/}if (this.status === FULFILLED) {/*********这里需要设置延迟****************/setTimeout(() => {onFulfilled(this.value)})	}if (this.status === REJECTED) {/*********这里需要设置延迟****************/setTimeout(() => {onRejected(this.value)})}}}
  1. 最后我们的then可以链式调用,then函数要返回的是Promise
//完整版的 MyPromise 源码
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";
class MyPromise {constructor(fn) {this.status = PENDING;this.value = undefined;//存放resolve传递出来的值this.resovleArr = [];this.rejectArr = []fn(this.resolve, this.reject);}resolve = (res) => {setTimeout(() => {if (this.status === PENDING) {this.status = FULFILLED;this.value = res;this.resovleArr.forEach(cb => {cb(res)})}})}reject = (res) => {setTimeout(() => {if (this.status === PENDING) {this.status = REJECTEDthis.value = res;this.rejectArr.forEach(cb => {cb(res)})}    })}then(onFulfilled, onRejected) {// then函数要返回Promise才能链式调用return new MyPromise((resolve, reject) => {/**************************** 加入如下判断*******************************/onFulfilled = typeof (onFulfilled) == "function" ? onFulfilled : () => { }onRejected = typeof (onRejected) == "function" ? onRejected : () => { }/*******************************************************************/if (this.status === PENDING) {this.resovleArr.push(onFulfilled);this.rejectArr.push(onRejected)}if (this.status === FULFILLED) {setTimeout(() => {onFulfilled(this.value)})}if (this.status === REJECTED) {setTimeout(() => {onRejected(this.value)})}})}
}

Promise完整源码–黑马

需要完成的:

  • 实现Promise的核心功能
  • Promise的实例方法: catchfinally
  • Promise的静态方法: resolverejectraceallallSettledany
  • Promise/A+标准,并跑通872个单元测试

构造函数

需求:

  • 实现MyPromise类,可以用如下的方式实例化
  • 实例化时传入回调函数
const p = new MyPromise((resolve, reject) => {resolve('success')// reject('error')
})
- 回调函数立刻执行
- 回调函数接收函数`resolve`和`reject`

核心步骤

  1. 定义类MyPromise
  2. 添加构造函数constructor
  3. 定义resolve/reject
  4. 执行回调函数
// 1. 定义类MyPromise
class MyPromise {//  2. 添加构造函数constructorconstructor(func) {//  3. 定义resolve/reject// 注意点1:resolve和reject函数要用箭头函数const resolve = (res) => {}const reject = (res) => {}//  4. 执行回调函数func(resolve, reject)}
}

**面试回答:**手写Promise-构造函数

  1. 定义类MyPromise,内部添加构造函数constructor,构造函数需要接收回调函数func
  2. 在构造函数中定义resolvereject
  3. 构造函数内部调用func并将resolvereject传入:func(resolve,reject)

状态及原因

需求:

  • MyPromise增加 state属性,只能是如下3个值
    1. pending:待定,默认状态
    2. fulfilled:已兑现,操作成功
    3. rejected:已拒绝,操作失败
  • MyPromise增加result属性,记录成功/失败原因
  • 调用resolvereject,修改状态,并记录成功/失败原因
const p = new HMPromise((resolve, reject) => {resolve('success') // pending -> fulfilled// reject('error') // pending -> rejected
})
p.state // 状态
p.result // 原因

核心步骤:

  • 定义常量保存状态,避免硬编码
  • MyPromise中定义
    1. 属性:state保存状态,result成功/失败原因
    2. 修改state的私有方法,修改状态并记录result
    3. 注意:state只有在pending时,才可以修改,且不可逆
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
class MyPromise {constructor(func) {// 1. 添加状态this.state = PENDING;// 2. 添加原因this.result = undefined;// 3. 调整resolve和reject// 4. 状态不可逆// 改状态:pending->fulfilled// 记录原因const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = result}}// 改状态:pending->rejected// 记录原因const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = result}}func(resolve, reject);}
}

**面试回答:**手写Promise-状态、成功or失败原因

  • 定义3个常量用来保存状态,pending,fulfilled,rejected
  • MyPromise内部定义属性stateresult分别用来保存状态和原因
  • 调用resolve时传入具体原因,如果状态为pending则更改状态并记录兑现原因
  • 调用reject时传入具体原因,如果状态为pending则更改状态并记录拒绝原因

then方法

then方法–成功和失败回调

需求:

  1. then方法的回调函数1: 状态变为fulfilled时触发,并获取成功结果
  2. then方法的回调函数2: 状态变为rejected时触发,并获取失败原因
  3. then方法的回调函数1或2没有传递的特殊情况处理,参考:then方法的参数
const p = new MyPromise((resolve, reject) => {resolve('success')// reject('error')
})
p.then(res => {console.log('成功回调:', res)
}, err => {console.log('失败回调:', err)
})

核心步骤:

  1. 增加then方法,根据不同的状态执行对应的回调函数,并传入result
    1. 参数1:成功的回调函数
    2. 参数2:失败的回调函数
  2. 判断参数
    1. 没有传递onFulfilled,onRejected
    2. 设置默认值(参考文档)如果 onFulfilled 不是一个函数,则内部会被替换为一个恒等函数((x) => x),它只是简单地将兑现值向前传递。如果 onRejected 不是一个函数,则内部会被替换为一个抛出器函数((x) => { throw x; }),它会抛出它收到的拒绝原因。
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = result}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = result}}func(resolve, reject);}// 1. 添加实例方法then(onFulfilled, onRejected) {// 2. 参数判断// 如果onFulfilled不是一个函数,则内部会被替换为一个恒等函数(x) => x,它只是简单地将兑现值向前传递。// 如果onRejected不是一个函数,则内部会被替换为一个抛出器函数(x) => { throw x; },它会抛出它收到的拒绝原因。onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }// 3. 执行成功回调// 4. 执行失败回调if (this.state === FULFILLED) {onFulfilled(this.result)} else if (this.state === REJECTED) {onRejected(this.result)}}
}

面试回答:手写Promise-then方法-成功和失败回调

  1. 添加then方法,接收2个回调函数:
    1. 成功回调onFulfilled
    2. 失败回调onRejected
  2. 判断传入的onFulfilledonRejected是否为函数,如果不是设置默认值
  3. 根据状态调用onFulfilledonRejected并传入兑现或拒绝原因
then方法-异步和多次调用

需求:

  1. 实例化传入的回调函数,内部支持异步操作
  2. then方法支持多次调用(非链式编程)
const p = new MyPromise((resolve, reject) => {setTimeout(() => {resolve('success')// reject('error')}, 2000);
})
p.then(res => {console.log('then1:', res)
}, err => {console.log('then1:', err)
})
p.then(res => {console.log('then2:', res)
}, err => {console.log('then2:', err)
})

核心步骤:

  1. 定义属性:保存传入的回调函数:[]
  2. 保存回调函数:状态为pending
  3. 调用成功回调:resolve内部
  4. 调用失败回调:reject内部
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;// 1. 定义实例属性 保存调用then时状态为pending时的回调函数this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = result// 3. 调用成功的回调this.resolveCb.forEach(p => p() )}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = result// 4. 调用失败的回调this.rejectCb.forEach(p => p() )}}func(resolve, reject);}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }if (this.state === FULFILLED) {onFulfilled(this.result)} else if (this.state === REJECTED) {onRejected(this.result)} else if (this.state === PENDING) {// 2. 保存回调函数this.resolveCb.push(() => {onFulfilled(this.result)})this.rejectCb.push(() => {onRejected(this.result)})}}
}

**面试回答:**手写Promise-then方法-异步和多次调用

  1. MyPromise添加属性resolveCBrejectCB
    1. resolveCB:保存then方法调用时状态为pending状态的回调函数
    2. rejectCB:保存then方法调用时状态为pending状态的回调函数
    3. 格式为数组
  2. 调整then方法中当前状态为pending时,保存回调函数。
  3. 构造函数内部调整resolvereject的逻辑:
    1. 调用resolve时取出数组resolveCB中的所有回调函数进行调用
    2. 调用reject时取出数组rejectCB中的所有回调函数进行调用

异步任务

需求:

让then方法的回调函数以异步任务的方式执行

console.log('top')
const p = new MyPromise((resolve, reject) => {resolve('success')
})
p.then(res => {console.log(res)
})
console.log('bottom')

核心步骤:

调整then中的逻辑,fulFilled,rejected,pending3种状态时的回调函数,使用定时器延迟进行包装

const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = resultthis.resolveCb.forEach(p => p())}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = resultthis.rejectCb.forEach(p => p())}}func(resolve, reject);}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }if (this.state === FULFILLED) {// 1. fulfilled状态的回调函数用定时器包装setTimeout(() => {onFulfilled(this.result)}, 0);} else if (this.state === REJECTED) {// 2. rejected状态的回调函数用定时器包装setTimeout(() => {onRejected(this.result)}, 0);} else if (this.state === PENDING) {this.resolveCb.push(() => {// 3. pending状态的回调函数用定时器包装setTimeout(() => {onFulfilled(this.result)}, 0);})this.rejectCb.push(() => {// 4. pending状态的回调函数用定时器包装setTimeout(() => {onRejected(this.result)}, 0);})}}
}

**面试回答:**手写Promise-异步任务

调整then中的逻辑,fulFilled,rejected,pending3种状态时的回调函数,使用定时器延迟进行包装

链式编程

链式编程-fulfilled状态

需求:

  1. then的链式编程
  2. 目前只考虑then的第一个回调函数
const p = new MyPromise((resolve, reject) => {resolve(1)
})
const p2 = p.then(res => {throw 'error'// return p2// return 2/* return new MyPromise((resolve, reject) => {//   resolve('HMPromise-res')reject("HMPromise-err")}) */
})
p2.then(res => {console.log('p2-res:', res)
}, err => {console.log('p2-err:', err)
})
1. 内部出现异常
2. 返回普通值
3. 返回Promise
4. 重复引用

核心步骤:

  1. 调整then方法,返回一个新的MyPromise对象
  2. 使用try-catch捕获内部异常,并通过reject传递
  3. 内部获取onFulfilled的执行结果判断是否为 MyPromise 实例
    1. 若是,调用返回值的then方法,获取兑现和拒绝的原因并通过resolvereject传递即可
    2. 若不是,将返回值resolve传递
  4. 判断onFulfilled函数的返回值是否和then方法内部返回的MyPromise相同,如果相同抛出错误new TypeError('Chaining cycle detected for promise #<Promise>')
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = resultthis.resolveCb.forEach(p => p())}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = resultthis.rejectCb.forEach(p => p())}}func(resolve, reject);}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }// 1. 返回新的Promise实例const p2 = new MyPromise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {// 2. try-catch处理内部异常try {// 3. 获取回调函数返回值const x = onFulfilled(this.result)// 4. 处理返回值// 4.3 重复引用if (x === p2) {throw new TypeError("Chaining cycle detected for promise #<Promise>")}// 4.2 返回值是Promiseif (x instanceof MyPromise) {x.then(res => {resolve(res)}, err => {reject(err)})} else {// 4.1 返回值是普通值resolve(x)}} catch (error) {reject(error)}}, 0);} else if (this.state === REJECTED) {setTimeout(() => {onRejected(this.result)}, 0);} else if (this.state === PENDING) {this.resolveCb.push(() => {setTimeout(() => {onFulfilled(this.result)}, 0);})this.rejectCb.push(() => {setTimeout(() => {onRejected(this.result)}, 0);})}})return p2}
}

面试回答:手写Promise-链式编程-fulfilled状态

  1. 链式编程的本质then方法会返回一个新的MyPromise对象
  2. 将原本的代码迁移到返回的MyPromise对象的回调函数中
  3. 内部通过try-catch捕获异常,出现异常通过reject传递异常
  4. 获取onFulfilled的执行结果,判断是否为 MyPromise 实例
    1. 若是,调用返回值的then方法,获取兑现和拒绝的原因并通过resolvereject传递即可
    2. 若不是,将返回值通过resolve传递
  5. 处理重复引用,判断onFulfilled函数的返回值是否和then方法内部返回的MyPromise相同,如果相同抛出错误。
链式编程-rejected状态

需求:

  1. then的第二个回调函数,执行reject时的链式编程
const p = new MyPromise((resolve, reject) => {reject(1)
})
const p2 = p.then(undefined, err => {throw 'error'// return p2// return 2// return new MyPromise((resolve, reject) => {//   resolve('HMPromise-res')//   // reject('HMPromise-err')// })
})
p2.then(res => {console.log('p2-res:', res)
}, err => {console.log('p2-err:', err)
})

核心步骤:

  1. 处理内部异常:onRejected的异常
  2. 获取返回值:onRejected的返回值
  3. fulfilled状态中的处理逻辑抽取为函数resolvePromise并复用
  4. fulfilledrejected状态中调用函数resolvePromise
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
// 3. then()方法中实现链式编程-处理返回值操作的抽取函数
function resolvePromise(p2, x, resolve, reject) {if (x === p2) {throw new TypeError("Chaining cycle detected for promise #<Promise>")}if (x instanceof MyPromise) {x.then(res => {resolve(res)}, err => {reject(err)})} else {resolve(x)}
}
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = resultthis.resolveCb.forEach(p => p())}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = resultthis.rejectCb.forEach(p => p())}}func(resolve, reject);}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }const p2 = new MyPromise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {try {const x = onFulfilled(this.result)// 5. fulfilled状态下改为调用函数resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === REJECTED) {setTimeout(() => {//    1. 处理内部异常try {// 2. 获取返回值const x = onRejected(this.result)// 4. 调用函数resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === PENDING) {this.resolveCb.push(() => {setTimeout(() => {onFulfilled(this.result)}, 0);})this.rejectCb.push(() => {setTimeout(() => {onRejected(this.result)}, 0);})}})return p2}
}

**面试回答:**手写Promise-链式编程-rejected状态

  1. 判断onRejected可能出现的异常,如果出现通过reject传递
  2. 获取onRejected函数的执行结果
  3. fulfilled状态时的处理逻辑抽取为函数,rejected状态时调用函数复用逻辑
链式编程-pending状态

需求:

  1. 执行异步操作时,支持链式编程
const p = new MyPromise((resolve, reject) => {setTimeout(() => {resolve(1)}, 2000)
})
const p2 = p.then(res => {throw 'error'// return p2// return 2// return new MyPromise((resolve, reject) => {//   resolve('resolve-2')//   // reject('reject-2')// })
})
p2.then(res => {console.log('p2-res:', res)
}, err => {console.log('p2-err:', err)
})

核心步骤:

  1. 处理异常:
    1. pending状态时推入回调函数数组时增加try-catch
  2. 获取返回值:
    1. 推入数组时,增加获取返回值的操作
  3. 调用上一节封装的函数resolvePromise
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
function resolvePromise(p2, x, resolve, reject) {if (x === p2) {throw new TypeError("Chaining cycle detected for promise #<Promise>")}if (x instanceof MyPromise) {x.then(res => {resolve(res)}, err => {reject(err)})} else {resolve(x)}
}
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = resultthis.resolveCb.forEach(p => p())}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = resultthis.rejectCb.forEach(p => p())}}func(resolve, reject);}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }const p2 = new MyPromise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {try {const x = onFulfilled(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === REJECTED) {setTimeout(() => {try {const x = onRejected(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === PENDING) {this.resolveCb.push(() => {setTimeout(() => {// 1. 处理异常try {// 2. 获取返回值const x = onFulfilled(this.result)// 3. 调用函数resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);})this.rejectCb.push(() => {setTimeout(() => {// 1. 处理异常try {// 2. 获取返回值const x = onRejected(this.result)// 3. 调用函数resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);})}})return p2}
}

**面试回答:**手写Promise-链式编程-pending状态

  1. then方法中pending状态时推入数组的函数增加try-catch捕获异常
  2. 获取推入数组的回调函数的返回值
  3. 调用上一节封装的函数并传入获取的值

实例方法-Promise.catch()

需求:

  1. 实现实例方法catch,可以实现如下调用
const p = new HMPromise((resolve, reject) => {reject('reject-error')// throw 'throw-error'
})
p.then(res => {console.log('res:', res)
}).catch(err => {console.log('err:', err)
})

核心步骤:

  1. 参考文档,catch等同于:then(undefined,onRejected)
  2. 直接添加catch方法,内部调用then
  3. 使用try-catch包裹constructor中的func捕获异常
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
function resolvePromise(p2, x, resolve, reject) {if (x === p2) {throw new TypeError("Chaining cycle detected for promise #<Promise>")}if (x instanceof MyPromise) {x.then(res => {resolve(res)}, err => {reject(err)})} else {resolve(x)}
}
class MyPromise {constructor(func) {this.state = PENDING;this.result = undefined;this.resolveCb = [];this.rejectCb = [];const resolve = (result) => {if (this.state = PENDING) {this.state = FULFILLEDthis.result = resultthis.resolveCb.forEach(p => p())}}const reject = (result) => {if (this.state = PENDING) {this.state = REJECTEDthis.result = resultthis.rejectCb.forEach(p => p())}}// 2. 处理异常try {func(resolve, reject);} catch (error) {reject(error)}}then(onFulfilled, onRejected) {onFulfilled = typeof (onFulfilled) === "function" ? onFulfilled : x => xonRejected = typeof (onRejected) === "function" ? onRejected : x => { throw x; }const p2 = new MyPromise((resolve, reject) => {if (this.state === FULFILLED) {setTimeout(() => {try {const x = onFulfilled(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === REJECTED) {setTimeout(() => {try {const x = onRejected(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);} else if (this.state === PENDING) {this.resolveCb.push(() => {setTimeout(() => {try {const x = onFulfilled(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);})this.rejectCb.push(() => {setTimeout(() => {try {const x = onRejected(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}}, 0);})}})return p2}// catch方法  允许链式编程// MDN官网上对catch的实现方法是:Promise.prototype.then(undefined,onRejected)的一种简写形式// 1. 直接添加catch方法,内部调用thencatch(onRejected) {return this.then(undefined, onRejected)}
}

面试回答:手写Promise-实例方法catch

  1. 定义catch方法,接收拒绝的回调函数onRejected
  2. catch方法的本质是内部调用then方法
  3. 调用形式为第一个回调函数传入undefined,第二个回调函数传入onRejected即可
  4. 链式调用,return
  5. 使用try-catch包裹constructor中的func捕获异常—这一步建议放在构造函数那一步中

实例方法-Promise.finally()

需求:

  1. 无论成功失败都会执行finally的回调函数
  2. 回调函数不接受任何参数
const p = new MyPromise((resolve, reject) => {// resolve('resolve-res')// reject('reject-error')// throw 'throw-error'
})
p.then(res => {console.log('res:', res)
}).catch(err => {console.log('err:', err)
}).finally(() => {console.log('finally')
})

核心步骤:

  1. 参考文档:finally方法类似于调用then(onFinally,onFinally),且不接受任何回调函数
  2. 链式调用,return。
// 实例化方法 finally 无论成功失败都要执行的函数 支持链式编程(要return)
// MDN官网上对finally的实现方法是:类似于调用 then(onFinally,onFinally)
finally(onFinally) {return this.then(onFinally,onFinally)
}

面试回答:手写Promise-实例方法finally

  1. 添加finally方法,接收最终执行的回调函数onFinally
  2. finally方法的本质为内部调用then方法
  3. 调用形式为第一个和第二个回调函数均传入onFinally即可

静态方法-Promise.resolve()

静态方法-Promise.reject()

静态方法-Promise.race()

静态方法-Promise.all()

静态方法-Promise.allSettled()

静态方法-Promise.any()

十六.find()

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined;找到满足条件,后续代码不执行。

用法:

  • find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
  • 如果没有符合条件的元素返回 undefined
  • find() 对于空数组,函数是不会执行的。
  • find() 并没有改变数组的原始值。
  • array.find(function(currentValue, index, arr),thisValue),其中currentValue为当前项,index为当前索引,arr为当前数组

例子:

const inventory = [{name: 'apples', quantity: 2},{name: 'bananas', quantity: 0},{name: 'cherries', quantity: 5}
];function findCherries(item) { return item.name === 'bananas';
}console.log(inventory.find(findCherries)); // { name: 'bananas', quantity: 0 }

十七.面试题

一、参考:判断一个对象上是否包含 一个属性的几种方法

1,“!==”进行判断,返回是布尔值,这种方法很常见,例如:

let  obj = {name:"zhang" , age:20};
obj.name !=== undefined// true 表示有这个属性
obj.sex !=== undefined// fasle 表示无 sex 这个属性
obj.toString !=== undefined; // true 表示对象继承toString属性

2,in的语法:attr in obj;返回是 布尔值,in方法可以检测对象的所有属性,不管是私有还是公有,只要有都显示true

'name' in obj; // true 表示有这个属性
'sex'   in obj;// fasle 表示无 sex 这个属性
'toString'   in obj; // true 表示对象继承toString属性

in 与上面的undefined效果是一样的,但是唯一不同点是 in 可以区分 undefined 的属性值

let  obj = {value:undefined};
obj.value === undefined;  //true
'value'  in obj ; //true

3、for … in:判断一个属性是否在对象上

var obj={ name:‘张学友’,age:18}
var isExsit =“name”in obj;// name 没有加引号,浏览器默认为变量,会报错,未定义
console.log(isExsit); // true

4、hasOwnProperty() 方法 是检测对象是都某一属性名,返回布尔值,这里只能检测对象的私有属性,继承属性检测不出来

let  obj = {name:"zhang" , age:20};
obj.hasOwnProperty(‘name’); // true 表示有这个属性
obj.hasOwnProperty(‘sex’);// fasle 表示无 sex 这个属性
obj.hasOwnProperty(‘toString’);// fasle 表示没有继承这个属性

5、hasPubProperty:用来检查属性是否为对象的公有属性

function foo() {this.name = 'foo'this.sayHi = function () {console.log('Say Hi')}
}foo.prototype.sayGoodBy = function () {console.log('Say Good By')
}let myPro = new foo()console.log(myPro.hasOwnProperty('sayHi')) // true
console.log(myPro.hasOwnProperty('sayGoodBy')) // true

6、typeof x;返回的是它的数据类型;
注:typeof-----检测数据类型作用,不能细分object下面的对象,数组,正则...

 typeof true; //  'boolean'typeof "abc"; //'string'typeof function() {};// 'function'typeof {};//  'object'typeof [];// 'object'typeof null//object

7、instanceof------即可检测是否是当前实例的类,还可以检查一个实例是否属于这个类
注:可以判断是否是一个数组

var arr=new Array;
arr instanceof Array// true =>是为true,不是为false
var arr=[]
arr instanceof Array; // true =>是为true,不是为false

8、includes():

var name = "王汉炎";    //目标字符串
name.includes('炎');   //  true;返回的是一个布尔值search(keywords) {return this.list.filter(item => {if (item.name.includes(keywords)) {// 判断是否包含,包含 true,不包含 falsereturn item}})
}

9、indexof():返回某个指定的字符串值在字符串中首次出现的位置。
stringObject.indexOf(searchvalue,fromindex)

searchvalue:必需。规定需检索的字符串值
fromindex:可选;规定在字符串中开始检索的位置。它的合法取值是 0 到 stringObject.length - 1。如省略该参数,则将从字符串的首字符开始检索。

var nameList = ['A', 'B', 'C', 'D', 'E', 'F'];    //目标字符串
nameList .indexOf('C') > -1;   //  true;返回的是一个布尔值
  • indexOf() 方法对大小写敏感!
  • 如果要检索的字符串值没有出现,则该方法返回 -1。

10、isPrototypeOf():检测一个对象是否是另一个对象的原型。或者说一个对象是否被包含在另一个对象的原型链中

var p = {x:1};//定义一个原型对象
var o = Object.create(p);//使用这个原型创建一个对象
p.isPrototypeOf(o);//=>true:o继承p
Object.prototype.isPrototypeOf(p);//=> true p继承自Object.prototype

11,【ES6】for of :
for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。

// 示例 一:
var arr = ['nick','freddy','mike','james'];
for(var item of arr){   console.log(item); // nick,freddy,mike,james,undefined  遍历数组中的每一项
}// 示例二:
var arr = [{ name:'nick', age:18 },{ name:'freddy', age:24 },{ name:'mike', age:26 },{ name:'james', age:34 }
];
for(var item of arr){   console.log(item.name,item.age);
}
// 'nick',18
// 'freddy',24
// 'mike',26
// 'james',34
// undefined

与 for in 的区别
1,for in可以遍历对象,for of无法循环遍历对象,

var userMsg = {0: 'nick', 1: 'freddy', 2: 'mike', 3: 'james'
};for(var key in userMsg){console.log(key, userMsg[key]); 
}
// 0: 'nick'
// 1: 'freddy'
// 2: 'mike'
// 3: 'james'for(var item of userMsg){   console.log(item);// TypeError: userMsg is not iterable
}

2, 遍历输出结果不同

var arr = ['nick','freddy','mike','james'];
for(var key in arr){console.log(key);   // 0 1 2 3 返回索引值
}for(var item of arr){   console.log(item);// nick,freddy,mike,james
}

3, for in 会遍历所有自定义属性,for of不会遍历新加的属性

var arr = ['nick','freddy','mike','james'];
arr.name = "数组";for(var key in arr){console.log(key+': '+arr[key]); 
}
// 0: nick
// 1: freddy
// 2: mike
// 3: james
// name: 数组for(var item of arr){   console.log(item);//  nick,freddy,mike,james
}

4, for of 可以正确响应break、continue和return语句,但是for in 不行

以上的几种方法就是我暂时学到的几种方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/450998.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习中,师傅b站泷羽sec——xss挖掘过程

某职业技术学院网站xss挖掘&#xff1a; 资产归纳 例如&#xff1a;先把功能点都看一遍&#xff0c;大部分都是文章 根据信息搜集第一课学习到一般主站的防御力是比较强的&#xff0c;出现漏洞的点不是对新手不友好。 在资产验证过程中还是把主站看了一遍 没有发现有攻击的机会…

未来人工智能的发展对就业市场的影响 人工智能在生活中的相关

人工智能&#xff08;Artificial Intelligence&#xff09;&#xff0c;英文缩写为AI.是新一轮科技革命和产业变革的重要驱动力量&#xff0c; 是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门新的技术科学. 人工智能的发展对就业市场的影响主要…

论文笔记:RelationPrompt :Zero-Shot Relation Triplet Extraction

论文来源: ACL Findings 2022 论文链接:https://arxiv.org/pdf/2203.09101.pdf 论文代码:http://github.com/declare-lab/RelationPrompt 本篇论文是由阿里达摩院自然语言智能实验室于2022年发表的关于零样本关系抽取的顶会论文,本篇博客将记录我在阅读过程中的一些笔记…

修改pq_default.ini禁用降噪,解决S905X3电视盒硬解视频画质模糊、严重涂抹得像油画、水彩画的问题

笔者使用一台处理器芯片为 S905X3 的电视盒将近一年&#xff0c;性能比之前的 RK3328 的盒子有所提升&#xff0c;但我对它视频解码方面感到越来越不爽&#xff0c;该盒子的硬解视频总是开启美颜降噪和锐化&#xff0c;导致硬解视频的画质模糊&#xff0c;细节都被磨平&#xf…

使用Jenkins部署项目

部署中的痛点 为什么要用Jenkins&#xff1f;我说下我以前开发的痛点&#xff0c;在一些中小型企业&#xff0c;每次开发一个项目完成后&#xff0c;需要打包部署&#xff0c;可能没有专门的运维人员&#xff0c;只能开发人员去把项目打成一个exe包&#xff0c;可能这个项目已…

OPENSSL-2023/11/10学习记录-C/C++对称分组加密DES

对称分组加密常用算法&#xff1a; DES 3DES AES 国密SM4 对称分组加密应用场景&#xff1a; 文件或者视频加密 加密比特币私钥 消息或者配置项加密 SSL通信加密 对称分组加密 使用异或实现一个简易的对称加密算法 A明文 B秘钥 AB密文AB (AB)B A 密码补全和初始化 数…

第六节——从深层剖析qsort的使用(让你不再害怕指针)

文章目录 1.什么是回调函数2.qsort的使用qsort排序整形数据qsort排序结构体数据qsort排序字符串数据 3.qsort的模拟实现 1.什么是回调函数 回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为参数传递给另⼀个函数&#xff0c;当…

Python画笔案例-087 绘制 旋转的文字

1、绘制 旋转的文字 通过 python 的turtle 库绘制 旋转的文字,如下图: 2、实现代码 绘制 旋转的文字,以下为实现代码: """旋转的文字.py """ import time from turtle import * from write_patch import *screen = Screen

【JPCS独立出版 | 福州大学主办 | 有确定的ISSN号】第三届可再生能源与电气科技国际学术会议(ICREET 2024)

第三届可再生能源与电气科技国际学术会议&#xff08;ICREET 2024&#xff09; 2024 3rd International Conference on Renewable Energy and Electrical Technology ICREET 2024已成功申请JPCS - Journal of Physics: Conference Series (ISSN:1742-6596) 独立出版&#xf…

架构设计笔记-16-嵌入式系统架构设计理论与实践

目录 知识要点 嵌入式微处理器 存储器&#xff08;memory&#xff09; 内&#xff08;外&#xff09;总线逻辑 嵌入式操作系统&#xff08;Embedded Operating System&#xff0c;EOS&#xff09; 通用中间件 嵌入式中间件的一般架构 典型嵌入式中间件系统 案例分析 1…

搭建mongodb单机部署-认证使用

搭建mongodb单机部署-认证使用 实现思路 先将配置文件配置好&#xff0c;使用不用认证的启动命令启动docker&#xff0c;然后创建账号并制定角色。在使用开启认证的命令重新启动容器就好。 这里我并没有说先停止容器&#xff0c;删掉容器重新创建容器。是因为我的启动命令中…

机器学习—Motivations

学习了线性回归&#xff0c;它预测了一个数字&#xff0c;接下来学习分类&#xff0c;输入变量y只能接收少数几个可能的值中的一个&#xff0c;而不是无限范围内的任何数字。事实证明&#xff0c;线性回归不是分类问题的好算法。这将引入一种不同的算法&#xff0c;叫做Logisti…

立仪科技:光谱共焦传感器精准测量玻璃

光谱共焦测量技术作为一种创新的光学检测方法&#xff0c;近年来在工业领域引起了广泛关注。 它以其高精度、非接触式的特点&#xff0c;特别适用于透明或半透明材料如玻璃的厚度和表面形貌测量。 接下来&#xff0c;立仪科技小编将深入探讨光谱共焦技术在玻璃测量上的应用及其…

【MySQL】增删改查-进阶(一)

目录 &#x1f334;数据库约束 &#x1f6a9;约束类型 &#x1f6a9;NOT NULL &#x1f6a9;UNIQUE &#x1f6a9;DEFAULT &#x1f6a9;PRIMARY KEY &#x1f6a9;FOREIGN KEY &#x1f6a9;CHECK &#x1f384;表的设计 &#x1f6a9;一对一 &#x1f6a9;一对多 …

Spring Boot知识管理:智能搜索与分析

3系统分析 3.1可行性分析 通过对本知识管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本知识管理系统采用JAVA作为开发语言&#xff0c;Spring Boot框…

如何做好SQL 数据库安全

随着信息技术的迅猛发展&#xff0c;数据库在现代信息系统中的重要性日益凸显。无论是电子商务平台、金融系统还是社交媒体应用&#xff0c;数据库都是其核心组件之一。其中&#xff0c;SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;数据库…

微信小程序使用MQTT连接阿里云

目录 一、新建项目和项目整体配置​ 二、MQTT 下载引入和配置连接​ 三、阿里云配置 1、创建产品及设备 2、数据进行云流转 四、创建 MQTT 连接​ 五、微信小程序配置 六、效果展示 1、微信小程序发送控制命令 2、LED台灯反馈LED状态 七、微信小程序项目完整代码 一…

论文笔记:PTR: Prompt Tuning with Rules for Text Classification

Abstract 手动设计大量语言提示麻烦且易出错&#xff0c;而自动生成的提示&#xff0c;在非小样本场景下验证其有效性昂贵且耗时。因此&#xff0c;提示调优以处理多类别分类任务仍然具有挑战。为此&#xff0c;本文提出使用规则进行多类别文本分类提示调优&#xff08;PTR&…

Linux发展与基础

Linux基础知识 Shell 命令执行环境&#xff1a; 命令提示符的组成&#xff1a;(用户名主机名)-[当前路径]权限提示符,例&#xff1a;&#xff08;kali㉿kali)-[~]$ ~ 表示所在目录为家目录:其中root用户的家目录是/root&#xff0c;普通用户的家目录在/home下 # 表示用户的权…

C#学习笔记(二)

C#学习笔记&#xff08;二&#xff09; 第 二 章 命名空间和类、数据类型、变量和代码规范一、命名空间-namespace1. 作用与具体表达形式-using2. 命名空间如何分类&#xff1f;3. 命名空间的命名规范 第 二 章 命名空间和类、数据类型、变量和代码规范 深水区 一、命名空间-…