27道this-45道Promise
参考:【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)
参考:【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)
this的5种绑定方式:
- 默认绑定(非严格模式下 this 指向全局对象,严格模式下 this 会绑定到 undefined)
- 隐式绑定(当函数引用有 上下文对象 时,如 obj.foo() 的调用方式,那么 foo 内的 this 指向 obj)
- 显示绑定(通过 call() 或者 apply() 方法直接指定 this 的绑定对象,如 foo.call(obj))
- new绑定
- 箭头函数绑定(this的指向由外层作用域决定的)
1
默认绑定:
var a = 10;
function foo() {
console.log(this.a);
}
foo(); // 10
相当于
window.a = 10;
function foo() {
console.log(this.a);
}
window.foo();
2
严格模式下
"use strict";
var a = 10;
function foo() {
// this1 undefined
console.log('this1', this);
// 10
console.log(window.a);
// this知道了值,那么这个就报错
// TypeError: Cannot read properties of undefined (reading 'a')
console.log(this.a)
}
// 这里先执行 window.foo打印 foo的函数 f foo() {...}
console.log(window.foo)
// this2 Window
console.log('this2', this);
foo(); // 执行foo()函数,在严格模式下
3
是因为改用 let 或者 const ,变量都不会绑定到 window 上的:
let a = 10;
const b = 20;
function foo() {
// undefined
console.log(this.a);
// undefined
console.log(this.b);
}
foo();
// undefined
console.log(window.a);
4
var a = 1
function foo() {
var a = 2
// foo()函数内的 this 指向的是window,因为window调用的foo
// Window{...}
console.log(this);
// window下的a
// 1
console.log(this.a);
}
foo();
5
var a = 1;
function foo() {
var a = 2;
function inner () {
// this.a this 指向window
// 1
console.log(this.a);
}
// 函数内的函数, 看清楚调用
inner();
}
foo();
6
function foo() {
console.log(this.a);
}
var obj = { a: 1, foo }
var a = 2;
obj.foo(); // 1
var obj = {
a: 1,
foo: function() {
console.log(this.a);
}
}
var a = 2;
obj.foo();
7
function foo() {
console.log(this.a);
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
// this 指向的是obj执行的时候,打印出来的是obj对象中的a
obj.foo(); // 1
// window下的a
foo2(); // 2
8
function foo () {
console.log(this.a)
};
var obj = { a: 1, foo };
var a = 2;
var foo2 = obj.foo;
var obj2 = { a: 3, foo2: obj.foo }
// obj.foo()中的this指向调用者obj
obj.foo(); // 1
// 调用者window,使用foo()中的this指向window
foo2(); // 2
// obj.foo 调用者是 ojb2,使用的foo()中的this指向 obj2
obj2.foo2(); // 3
9
function foo () {
console.log(this.a)
}
function doFoo (fn) {
// obj.foo 函数内this发生了改变,指向了window
console.log(this)
fn() // this.a 指向 window 2
}
var obj = { a: 1, foo }
var a = 2
doFoo(obj.foo)
10
如果你把一个函数当成参数传递到另一个函数的时候,也会发生隐式丢失的问题,且与包裹着它的函数的this指向无关。在非严格模式下,会把该函数的this绑定到window上,严格模式下绑定到undefined。
因为doFoo本来就是obj2调用的 所以doFoo指向obj2 , 因为foo这个函数本来就是window的a就是 2
他只是把obj的foo传进了doFoo 是在doFoo函数内部执行
这是this中的例外情况 当作为参数被传递时 会发生隐式绑定丢失
第一红圈,是隐式绑定,因为doFoo作为obj2的属性被调用,所以第5行中的this指向obj2
第二个红圈,obj.foo作为参数被传递,此时foo中的this会丢失原有的隐式绑定,可以理解为foo作为一个单独的函数被调用,此时和obj脱离的关系
所以第6行等于wondow.foo了
倒数第二行,当把函数进行赋值传递的时候,变量会指向函数的引用,这个时候会发生隐式绑定的丢失,函数的调用会采用默认策略
function foo () {
console.log(this.a)
}
function doFoo (fn) { // this.a
console.log(this) // 指向obj2对象,{ a: 3, doFoo: f }
// obj.foo() 打印this.a 为2,也就是window下的
fn() // 2
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
// obj2.doFoo 调用函数,指向是obj2,因为是obj2调用它
// obj.foo this.a
obj2.doFoo(obj.foo)
// 使用严格模式
"use strict"
function foo () {
console.log(this.a)
}
function doFoo (fn) {
console.log(this)
fn()
}
var obj = { a: 1, foo }
var a = 2
var obj2 = { a: 3, doFoo }
obj2.doFoo(obj.foo)
{ a:3, doFoo: f }
Uncaught TypeError: Cannot read property 'a' of undefined
11
- 使用
.call()
或者.apply()
的函数是会直接执行的
bind()
是创建一个新的函数,需要手动调用才会执行
.call()
和.apply()
用法基本类似,不过call接收若干个参数,而apply接收的是一个数组
function foo () {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo() // 2
foo.call(obj) // 1
foo.apply(obj) // 1
foo.bind(obj) // 并不会执行
call、apply、bind接收到的第一个参数是空或者null、undefined的话,则会忽略这个参数。
function foo () {
console.log(this.a)
}
var a = 2
foo.call() // 2
foo.call(null) // 2
foo.call(undefined) // 2
12
谁调用的函数,函数内的this指向的就是谁。
对于setTimeout中的函数,这里存在隐式绑定的隐式丢失,也就是当我们将函数作为参数传递时会被隐式赋值,回调函数丢失this绑定,因此这时候setTimeout中的函数内的this是指向window的。
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
setTimeout(function () {
console.log(this) // Window{...}
console.log(this.a) 3
}, 0)
}
}
var a = 3
obj2.foo1() // 2
obj2.foo2()
13
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
setTimeout(function () {
console.log(this) // { a: 1 }
console.log(this.a)
}.call(obj1), 0)
}
}
var a = 3
obj2.foo1() // 2
obj2.foo2() // 1
// 2
// { a: 1 }
// 1
// obj2.foo2.call(obj1)
// 这种写法的话,我改变的就是foo2函数内的this的指向了,但是我们知道,foo2函数内this的指向和setTimeout里函数的this是没有关系的,因为调用定时器的始终是window。
14
var obj1 = {
a: 1
}
var obj2 = {
a: 2,
foo1: function () {
console.log(this.a)
},
foo2: function () {
// 调用inner函数的依然是window
function inner () {
console.log(this) // Window{...}
console.log(this.a) // 3
}
inner()
}
}
var a = 3
obj2.foo1() // 2
obj2.foo2() //
15
function foo () {
console.log(this.a)
}
var obj = { a: 1 }
var a = 2
foo() // 2
foo.call(obj) // 1
// foo().call(obj)开始会执行foo()函数,打印出2
// 但是会对foo()函数的返回值执行.call(obj)操作,可是我们可以看到foo()函数的返回值是undefined,因此就会报错了。
foo().call(obj)
16
function foo () {
console.log(this.a)
return function () {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
// 第一个数字2自然是foo()输出的,虽然foo()函数也返回了一个匿名函数,但是并没有调用它呀,只有写成foo()(),这样才算是调用匿名函数。
foo() // 2
// 第二个数字1是foo.call(obj)输出的,由于.call()是紧跟着foo的,所以改变的是foo()内this的指向,并且.call()是会使函数立即执行的,因此打印出1,同理,它也没有调用返回的函数。
foo.call(obj) // 1
// 在执行完foo()之后,会返回一个匿名函数,并且后面使用了.call(obj)来改变这个匿名函数的this指向并调用了它,所以输出了1。
foo().call(obj) // 2 1
17
call是会直接执行函数的,bind是返回一个新函数,但不会执行。
function foo () {
console.log(this.a)
return function () {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
// foo()会执行没错,打印出了2。
foo()
// foo.bind(obj)却不会执行,它返回的是一个新函数。
foo.bind(obj)
// foo().bind(obj)只会执行前面的foo()函数,打印出2,.bind(obj)只是将foo()返回的匿名函数显式绑定this而已,并没有调用。
foo().bind(obj)
18
function foo () {
console.log(this.a)
return function () {
console.log(this.a)
}
}
var obj = { a: 1 }
var a = 2
foo.call(obj)() // 1 2
19
var obj = {
a: 'obj',
foo: function () {
console.log('foo:', this.a)
return function () {
console.log('inner:', this.a)
}
}
}
var a = 'window'
var obj2 = { a: 'obj2' }
// obj.foo()自然是打印出foo: obj和inner: window
obj.foo()()
// obj.foo.(obj2)()其实也没啥可疑惑的了,打印出foo: obj2和inner: window
obj.foo.call(obj2)()
// 打印出foo: obj和inner: obj2
obj.foo().call(obj2)
20
var obj = {
a: 1,
foo: function (b) {
b = b || this.a
return function (c) {
console.log(this.a + b + c)
}
}
}
var a = 2
var obj2 = { a: 3 }
// 6
obj.foo(a).call(obj2, 1)
// 将foo函数内的this指向了obj2 a: 3
// b开始是undefined的,但是又因为有一句b = b || this.a,使得b变为了3
// 调用匿名函数,且和这个匿名函数内的this应该是指向window的
// 6
obj.foo.call(obj2)(1)
21
function foo1 () {
console.log(this.a)
}
var a = 1
var obj = {
a: 2
}
var foo2 = function () {
foo1.call(obj)
}
foo2() // 2
// 这里foo2函数内部的函数foo1我们使用call来显式绑定obj,就算后面再用call来绑定window也没有用了。
foo2.call(window) // 2
22
function foo1 (b) {
console.log(`${this.a} + ${b}`)
return this.a + b
}
var a = 1
var obj = {
a: 2
}
var foo2 = function () {
return foo1.call(obj, ...arguments)
}
var num = foo2(3)
console.log(num)
'2 + 3'
5
23
function foo (item) {
console.log(item, this.a)
}
var obj = {
a: 'obj'
}
var a = 'window'
var arr = [1, 2, 3]
// arr.forEach(foo, obj)
// arr.map(foo, obj)
arr.filter(function (i) {
console.log(i, this.a)
return i > 2
}, obj)
// 如果我们没有传递第二个参数obj的话,this.a打印出来的肯定就是window下的a了,但是传入了之后将obj显示绑定到第一个参数函数上。
1 "obj"
2 "obj"
3 "obj"
- this永远指向最后调用它的那个对象
- 匿名函数的this永远指向window
- apply 和 call 会直接执行,bind是创建新函数,需要手动调用
- forEach,map, filter函数的第二个参数也是能显式绑定this的
24
箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined。
箭头函数 它里面的this是由外层作用域来决定的,且指向函数定义时的this而非执行时。
var obj = {
name: 'obj',
foo1: () => {
console.log(this.name)
},
foo2: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
}
}
var name = 'window'
obj.foo1()
obj.foo2()()
// 'window'
// 'obj'
// 'obj'
var name = 'window'
var obj1 = {
name: 'obj1',
foo: function () {
console.log(this.name)
}
}
var obj2 = {
name: 'obj2',
foo: () => {
console.log(this.name)
}
}
// 不使用箭头函数的obj1.foo()是由obj1调用的,所以this.name为obj1。
obj1.foo()
// 使用箭头函数的obj2.foo()的外层作用域是window,所以this.name为window。
obj2.foo()
// 'obj1'
// 'window'
25
var name = 'window'
var obj1 = {
name: 'obj1',
foo1: function () {
console.log(this.name)
return () => {
console.log(this.name)
}
},
foo2: () => {
console.log(this.name)
return function () {
console.log(this.name)
}
}
}
var obj2 = {
name: 'obj2'
}
obj1.foo1.call(obj2)() // 'obj2' 'obj2'
obj1.foo1().call(obj2) // 'obj1' 'obj1'
obj1.foo2.call(obj2)() // 'window' 'window'
obj1.foo2().call(obj2) // 'window' 'obj2'
26
function Foo (value) {
this.value = value
}
Foo.prototype.getValue = () => console.log(this.value)
const foo1 = new Foo(1)
foo1.getValue() // undefined
const Foo = (value) => {
this.value = value;
}
const foo1 = new Foo(1)
// 事实上直接就报错了 Uncaught TypeError: Foo is not a constructor
console.log(foo1);
const button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'Clicked button';
});
27
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo();
})();
// 使用了"use strict"开启严格模式会使得"use strict"以下代码的this为undefined,也就是这里的立即执行函数中的this是undefined。
// 但是调用foo()函数的依然是window,所以foo()中的this依旧是window,所以会打印出2
// 如果你是使用this.foo()调用的话,就会报错了,因为现在立即执行函数中的this是undefined
// 或者将"use strict"放到foo()函数里面,也会报错。
手写一个new实现
function create() {
// 1.获取构造函数,并且删除 arguments 中的第一项
var Con = [].shift.call(arguments);
// 2.创建一个空对象并链接到构造函数的原型,使它能访问原型中的属性
var obj = Object.create(Con.prototype);
// 3.使用apply改变构造函数中this的指向实现继承,使obj能访问到构造函数中的属性
var res = Con.apply(this, arguments);
// 4.优先返回构造函数返回的对象
return res instanceof Object ? res : obj;
}
手写一个call实现
ES3实现:
function fnFactory(context) {
let unique_fn = "fn";
while(context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.myCall = function(context) {
context = (context !== null && context !== undefined) ? Object(context) : window;
let args = [];
for(let i = 1, len = arguments.length; i < len; i++) {
args.push("arguments[" + i + "]");
}
let fn = fnFactory(context);
context[fn] = this;
let result = eval("context[fn](" + args + ")");
delete context[fn];
return result;
}
ES6实现:
Function.prototype.myCall = function(context) {
context = (context !== null && context !== undefined) ? Object(context) : window;
let fn = Symbol();
context[fn] = this;
let args = [...arguments].slice(1);
let result = context[fn](...args);
delete context[fn];
return result;
}
function fnFactory(context) {
var unique_fn = "fn";
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.call2 = function(context) {
// 1. 若是传入的context是null或者undefined时指向window;
// 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
context = (context !== null && context !== undefined) ? Object(context) : window;
// 3. 创建一个独一无二的fn函数的命名
var fn = fnFactory(context);
// 4. 这里的this就是指调用call的那个函数
// 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
context[fn] = this;
// 6. 定义一个数组用于放arguments的每一项的字符串: ['agruments[1]', 'arguments[2]']
var args = [];
// 7. 要从第1项开始, 第0项是context
for (var i = 1, l = arguments.length; i < l; i++) {
args.push("arguments[" + i + "]");
}
// 8. 使用eval()来执行fn并将args一个个传递进去
var result = eval("context[fn](" + args + ")");
// 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
delete context[fn];
// 10. 函数fn可能会有返回值, 需要将其返回
return result;
};
手写一个apply实现
ES3实现:
function fnFactory(context) {
var unique_fn = "fn";
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
context = context ? Object(context) : window;
var fn = fnFactory(context);
context[fn] = this;
var result;
if (!arr) {
result = context[fn]();
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
result = eval("context[fn](" + args + ")");
}
delete context[fn];
return result;
};
ES6实现:
Function.prototype.apply3 = function(context, arr) {
context = context ? Object(context) : window;
let fn = Symbol();
context[fn] = this;
let result = arr ? context[fn](...arr) : context[fn]();
delete context[fn];
return result;
};
function fnFactory(context) {
var unique_fn = "fn";
while (context.hasOwnProperty(unique_fn)) {
unique_fn = "fn" + Math.random();
}
return unique_fn;
}
Function.prototype.apply2 = function(context, arr) {
// 1. 若是传入的context是null或者undefined时指向window;
// 2. 若是传入的是原始数据类型, 原生的call会调用 Object() 转换
context = context ? Object(context) : window;
// 3. 创建一个独一无二的fn函数的命名
var fn = fnFactory(context);
// 4. 这里的this就是指调用call的那个函数
// 5. 将调用的这个函数赋值到context中, 这样之后执行context.fn的时候, fn里的this就是指向context了
context[fn] = this;
var result;
// 6. 判断有没有第二个参数
if (!arr) {
result = context[fn]();
} else {
// 7. 有的话则用args放每一项的字符串: ['arr[0]', 'arr[1]']
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push("arr[" + i + "]");
}
// 8. 使用eval()来执行fn并将args一个个传递进去
result = eval("context[fn](" + args + ")");
}
// 9. 给context额外附件了一个属性fn, 所以用完之后需要删除
delete context[fn];
// 10. 函数fn可能会有返回值, 需要将其返回
return result;
};
手写一个bind实现
- 函数内的this表示的就是调用的函数
- 可以将上下文传递进去, 并修改this的指向
- 返回一个函数
- 可以传入参数
- 柯里化
- 一个绑定的函数也能使用new操作法创建对象, 且提供的this会被忽略
Function.prototype.bind2 = function(context) {
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function() {
var innerArgs = Array.prototype.slice.call(arguments);
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
);
};
var fNOP = function() {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
Function.prototype.bind2 = function(context) {
// 1. 判断调用bind的是不是一个函数
if (typeof this !== "function") {
throw new Error(
"Function.prototype.bind - what is trying to be bound is not callable"
);
}
// 2. 外层的this指向调用者(也就是调用的函数)
var self = this;
// 3. 收集调用bind时的其它参数
var args = Array.prototype.slice.call(arguments, 1);
// 4. 创建一个返回的函数
var fBound = function() {
// 6. 收集调用新的函数时传入的其它参数
var innerArgs = Array.prototype.slice.call(arguments);
// 7. 使用apply改变调用函数时this的指向
// 作为构造函数调用时this表示的是新产生的对象, 不作为构造函数用的时候传递context
return self.apply(
this instanceof fNOP ? this : context,
args.concat(innerArgs)
);
};
// 5. 创建一个空的函数, 且将原型指向调用者的原型(为了能用调用者原型中的属性)
// 下面三步的作用有点类似于 fBoun.prototype = this.prototype 但有区别
var fNOP = function() {};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
// 8. 返回最后的结果
return fBound;
};
Promise
event loop它的执行顺序:
- 一开始整个脚本作为一个宏任务执行
- 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
- 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
- 执行浏览器UI线程的渲染工作
- 检查是否有Web Worker任务,有则执行
- 执行完本轮的宏任务,回到2,依次循环,直到宏任务和微任务队列都为空
微任务包括:MutationObserver,Promise.then() 或 catch(),Promise为基础开发的其它技术,比如 fetch API , V8的垃圾回收过程,Node独有的process.nextTick。
宏任务包括: script,setTimeout,setInterval,setImmediate,I/O,UI rendering
1
// 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
})
// 然后执行同步代码1,此时promise1没有被resolve或者reject,因此状态还是pending
console.log('1', promise1);
'promise1'
'1' Promise{<pending>}
2
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve('success')
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 从上至下,先遇到new Promise,执行其中的同步代码1
// 再遇到resolve('success'), 将promise的状态改为了resolved并且将值保存下来
// 继续执行同步代码2
// 跳出promise,往下执行,碰到promise.then这个微任务,将其加入微任务队列
// 执行同步代码4
// 本轮宏任务全部执行完毕,检查微任务队列,发现promise.then这个微任务且状态为resolved,执行它。
1 2 4 3
3
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
1 2 4
// 在promise中并没有resolve或者reject
// 因此promise.then并不会执行,它只有在被改变了状态之后才会执行。
4
const promise1 = new Promise((resolve, reject) => {
console.log('promise1')
resolve('resolve1')
})
const promise2 = promise1.then(res => {
console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);
'promise1'
'1' Promise{<resolved>: 'resolve1'}
'2' Promise{<pending>}
'resolve1'
- 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
- 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
- 碰到promise1.then这个微任务,将它放入微任务队列
- promise2是一个新的状态为pending的Promise
- 执行同步代码1, 同时打印出promise1的状态是resolved
- 执行同步代码2,同时打印出promise2的状态是pending
- 宏任务执行完毕,查找微任务队列,发现promise1.then这个微任务且状态为resolved,执行它。
5
const fn = () => (new Promise((resolve, reject) => {
console.log(1);
resolve('success')
}))
fn().then(res => {
console.log(res)
})
console.log('start')
1
'start'
'success'
fn函数它是直接返回了一个new Promise的,而且fn函数的调用是在start之前,所以它里面的内容应该会先执行。
6
const fn = () =>
new Promise((resolve, reject) => {
console.log(1);
resolve("success");
});
console.log("start");
fn().then(res => {
console.log(res);
});
"start"
1
"success"
之前我们很容易就以为看到new Promise()就执行它的第一个参数函数了,其实这是不对的
我们得注意它是不是被包裹在函数当中,如果是的话,只有在函数调用的时候才会执行。
7
console.log('start')
setTimeout(() => {
console.log('time')
})
Promise.resolve().then(() => {
console.log('resolve')
})
console.log('end')
'start'
'end'
'resolve'
'time'
刚开始整个脚本作为一个宏任务来执行,对于同步代码直接压入执行栈进行执行,因此先打印出start和end。
setTimout作为一个宏任务被放入宏任务队列(下一个)
Promise.then作为一个微任务被放入微任务队列
本次宏任务执行完,检查微任务,发现Promise.then,执行它
接下来进入下一个宏任务,发现setTimeout,执行。
8
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
1
2
4
"timerStart"
"timerEnd"
"success"
从上至下,先遇到new Promise,执行该构造函数中的代码1
然后碰到了定时器,将这个定时器中的函数放到下一个宏任务的延迟队列中等待执行
执行同步代码2
跳出promise函数,遇到promise.then,但其状态还是为pending,这里理解为先不执行
执行同步代码4
一轮循环过后,进入第二次宏任务,发现延迟队列中有setTimeout定时器,执行它
首先执行timerStart,然后遇到了resolve,将promise的状态改为resolved且保存结果并将之前的promise.then推入微任务队列
继续执行同步代码timerEnd
宏任务全部执行完毕,查找微任务队列,发现promise.then这个微任务,执行它。
9
setTimeout(() => {
console.log('timer1');
setTimeout(() => {
console.log('timer3')
}, 0)
}, 0)
setTimeout(() => {
console.log('timer2')
}, 0)
console.log('start')
start
timer1
timer2
timer3
setTimeout(() => {
console.log('timer1');
Promise.resolve().then(() => {
console.log('promise')
})
}, 0)
setTimeout(() => {
console.log('timer2')
}, 0)
console.log('start')
'start'
'timer1'
'promise'
'timer2'
Promise.then是微任务,它会被加入到本轮中的微任务列表,而定时器timer3是宏任务,它会被加入到下一轮的宏任务中。
10
Promise.resolve().then(() => {
console.log('promise1');
const timer2 = setTimeout(() => {
console.log('timer2')
}, 0)
});
const timer1 = setTimeout(() => {
console.log('timer1')
Promise.resolve().then(() => {
console.log('promise2')
})
}, 0)
console.log('start');
'start'
'promise1'
'timer1'
'promise2'
'timer2'
刚开始整个脚本作为第一次宏任务来执行,我们将它标记为宏1,从上至下执行
遇到Promise.resolve().then这个微任务,将then中的内容加入第一次的微任务队列标记为微1
遇到定时器timer1,将它加入下一次宏任务的延迟列表,标记为宏2,等待执行(先不管里面是什么内容)
执行宏1中的同步代码start
第一次宏任务(宏1)执行完毕,检查第一次的微任务队列(微1),发现有一个promise.then这个微任务需要执行
执行打印出微1中同步代码promise1,然后发现定时器timer2,将它加入宏2的后面,标记为宏3
第一次微任务队列(微1)执行完毕,执行第二次宏任务(宏2),首先执行同步代码timer1
然后遇到了promise2这个微任务,将它加入此次循环的微任务队列,标记为微2
宏2中没有同步代码可执行了,查找本次循环的微任务队列(微2),发现了promise2,执行它
第二轮执行完毕,执行宏3,打印出timer2
11
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000)
})
const promise2 = promise1.then(() => {
throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
console.log('promise1', promise1)
console.log('promise2', promise2)
}, 2000)
从上至下,先执行第一个new Promise中的函数,碰到setTimeout将它加入下一个宏任务列表
跳出new Promise,碰到promise1.then这个微任务,但其状态还是为pending,这里理解为先不执行
promise2是一个新的状态为pending的Promise
执行同步代码console.log('promise1'),且打印出的promise1的状态为pending
执行同步代码console.log('promise2'),且打印出的promise2的状态为pending
碰到第二个定时器,将其放入下一个宏任务列表
第一轮宏任务执行结束,并且没有微任务需要执行,因此执行第二轮宏任务
先执行第一个定时器里的内容,将promise1的状态改为resolved且保存结果并将之前的promise1.then推入微任务队列
该定时器中没有其它的同步代码可执行,因此执行本轮的微任务队列,也就是promise1.then,它抛出了一个错误,且将promise2的状态设置为了rejected
第一个定时器执行完毕,开始执行第二个定时器中的内容
打印出'promise1',且此时promise1的状态为resolved
打印出'promise2',且此时promise2的状态为rejected
12
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("success");
console.log("timer1");
}, 1000);
console.log("promise1里的内容");
});
const promise2 = promise1.then(() => {
throw new Error("error!!!");
});
console.log("promise1", promise1);
console.log("promise2", promise2);
setTimeout(() => {
console.log("timer2");
console.log("promise1", promise1);
console.log("promise2", promise2);
}, 2000);
总结
- Promise的状态一经改变就不能再改变。
.then
和 .catch
都会返回一个新的 Promise
。
catch
不管被连接到哪里,都能捕获上层未捕捉过的错误。
- 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)。
- Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。
- .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
- .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
- .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
- .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。
- .finally方法也是返回一个Promise,他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。
13
构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用 。
const promise = new Promise((resolve, reject) => {
resolve("success1");
reject("error");
resolve("success2");
});
promise
.then(res => {
console.log("then: ", res);
}).catch(err => {
console.log("catch: ", err);
})
"then: success1"
14
catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
至于then3也会被执行,那是因为catch()也会返回一个Promise,且由于这个Promise没有返回值,所以打印出来的是undefined。
15
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
1
2
Promise可以链式调用,不过promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般我们任务的链式调用一样return this。
上面的输出结果之所以依次打印出1和2,那是因为resolve(1)之后走的是第一个then方法,并没有走catch里,所以第二个then中的res得到的实际上是第一个then的返回值。
且return 2会被包装成resolve(2)。
16
Promise.reject(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
console.log(err);
return 3
})
.then(res => {
console.log(res);
});
1
3
因为reject(1)此时走的就是catch,且第二个then中的res得到的就是catch中的返回值。
17
18
返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))。
19
.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
20
.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。
21
Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数
如果把第二个参数去掉,就进入了catch()中
22
由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。
23
- .finally()方法不管Promise对象最后的状态如何都会执行
- .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
24
25
- 首先定义了两个函数promise1和promise2,先不管接着往下看。
- promise1函数先被调用了,然后执行里面new Promise的同步代码打印出promise1
- 之后遇到了resolve(1),将p的状态改为了resolved并将结果保存下来。
- 此时promise1内的函数内容已经执行完了,跳出该函数
- 碰到了promise1().then(),由于promise1的状态已经发生了改变且为resolved因此将promise1().then()这条微任务加入本轮的微任务列表(这是第一个微任务)
- 这时候要注意了,代码并不会接着往链式调用的下面走,也就是不会先将.finally加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
- 再往下走碰到了promise2()函数,其中返回的new Promise中并没有同步代码需要执行,所以执行reject('error')的时候将promise2函数中的Promise的状态变为了rejected
- 跳出promise2函数,遇到了promise2().catch(),将其加入当前的微任务队列(这是第二个微任务),且链式调用后面的内容得等该任务执行完后才执行,和.then()一样。
- OK, 本轮的宏任务全部执行完了,来看看微任务列表,存在promise1().then(),执行它,打印出1,然后遇到了.finally()这个微任务将它加入微任务列表(这是第三个微任务)等待执行
- 再执行promise2().catch()打印出error,执行完后将finally2加入微任务加入微任务列表(这是第四个微任务)
- OK, 本轮又全部执行完了,但是微任务列表还有两个新的微任务没有执行完,因此依次执行finally1和finally2。
26
.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
27
在间隔一秒后,控制台会同时打印出1, 2, 3,还有一个数组[1, 2, 3]。
- Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
- .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
- Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
- all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。