Skip to content

6.27道this-27道Promise #8

@webVueBlog

Description

@webVueBlog

27道this-45道Promise

参考:【建议👍】再来40道this面试题酸爽继续(1.2w字用手整理)

参考:【建议星星】要就来45道Promise面试题一次爽到底(1.1w字用心整理)

this的5种绑定方式:

  1. 默认绑定(非严格模式下 this 指向全局对象,严格模式下 this 会绑定到 undefined)
  2. 隐式绑定(当函数引用有 上下文对象 时,如 obj.foo() 的调用方式,那么 foo 内的 this 指向 obj)
  3. 显示绑定(通过 call() 或者 apply() 方法直接指定 this 的绑定对象,如 foo.call(obj))
  4. new绑定
  5. 箭头函数绑定(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中的例外情况 当作为参数被传递时 会发生隐式绑定丢失

image

第一红圈,是隐式绑定,因为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

  1. 使用.call()或者.apply()的函数是会直接执行的
  2. bind()是创建一个新的函数,需要手动调用才会执行
  3. .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"
  1. this永远指向最后调用它的那个对象
  2. 匿名函数的this永远指向window
  3. apply 和 call 会直接执行,bind是创建新函数,需要手动调用
  4. 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实现

  1. 函数内的this表示的就是调用的函数
  2. 可以将上下文传递进去, 并修改this的指向
  3. 返回一个函数
  4. 可以传入参数
  5. 柯里化
  6. 一个绑定的函数也能使用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它的执行顺序:

  1. 一开始整个脚本作为一个宏任务执行
  2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  3. 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  4. 执行浏览器UI线程的渲染工作
  5. 检查是否有Web Worker任务,有则执行
  6. 执行完本轮的宏任务,回到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'
  1. 从上至下,先遇到new Promise,执行该构造函数中的代码promise1
  2. 碰到resolve函数, 将promise1的状态改变为resolved, 并将结果保存下来
  3. 碰到promise1.then这个微任务,将它放入微任务队列
  4. promise2是一个新的状态为pending的Promise
  5. 执行同步代码1, 同时打印出promise1的状态是resolved
  6. 执行同步代码2,同时打印出promise2的状态是pending
  7. 宏任务执行完毕,查找微任务队列,发现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

1653293346(1)

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);

1653293497(1)

总结

  1. Promise的状态一经改变就不能再改变。
  2. .then.catch 都会返回一个新的 Promise
  3. catch不管被连接到哪里,都能捕获上层未捕捉过的错误。
  4. 在Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)。
  5. Promise 的 .then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。
  6. .then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。
  7. .then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。
  8. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。
  9. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch是.then第二个参数的简便写法。
  10. .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

1653295550(1)

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

1653296785(1)

18

1653296865(1)

返回任意一个非 promise 的值都会被包裹成 promise 对象,因此这里的return new Error('error!!!')也被包裹成了return Promise.resolve(new Error('error!!!'))。

19

1653296946(1)

.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

20

1653297140(1)

.then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里。

21

Promise.resolve('1')的值会进入成功的函数,Promise.reject('2')的值会进入失败的函数

1653297399(1)

如果把第二个参数去掉,就进入了catch()中

1653297450(1)

22

1653297619(1)

由于Promise调用的是resolve(),因此.then()执行的应该是success()函数,可是success()函数抛出的是一个错误,它会被后面的catch()给捕获到,而不是被fail1函数捕获。

23

  1. .finally()方法不管Promise对象最后的状态如何都会执行
  2. .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
  3. 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。

1653298094(1)

24

1653298291(1)

25

1653298486

  1. 首先定义了两个函数promise1和promise2,先不管接着往下看。
  2. promise1函数先被调用了,然后执行里面new Promise的同步代码打印出promise1
  3. 之后遇到了resolve(1),将p的状态改为了resolved并将结果保存下来。
  4. 此时promise1内的函数内容已经执行完了,跳出该函数
  5. 碰到了promise1().then(),由于promise1的状态已经发生了改变且为resolved因此将promise1().then()这条微任务加入本轮的微任务列表(这是第一个微任务)
  6. 这时候要注意了,代码并不会接着往链式调用的下面走,也就是不会先将.finally加入微任务列表,那是因为.then本身就是一个微任务,它链式后面的内容必须得等当前这个微任务执行完才会执行,因此这里我们先不管.finally()
  7. 再往下走碰到了promise2()函数,其中返回的new Promise中并没有同步代码需要执行,所以执行reject('error')的时候将promise2函数中的Promise的状态变为了rejected
  8. 跳出promise2函数,遇到了promise2().catch(),将其加入当前的微任务队列(这是第二个微任务),且链式调用后面的内容得等该任务执行完后才执行,和.then()一样。
  9. OK, 本轮的宏任务全部执行完了,来看看微任务列表,存在promise1().then(),执行它,打印出1,然后遇到了.finally()这个微任务将它加入微任务列表(这是第三个微任务)等待执行
  10. 再执行promise2().catch()打印出error,执行完后将finally2加入微任务加入微任务列表(这是第四个微任务)
  11. OK, 本轮又全部执行完了,但是微任务列表还有两个新的微任务没有执行完,因此依次执行finally1和finally2。

26

.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。

.race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。

27

在间隔一秒后,控制台会同时打印出1, 2, 3,还有一个数组[1, 2, 3]。

1653299401(1)

  1. Promise.all()的作用是接收一组异步任务,然后并行执行异步任务,并且在所有异步操作执行完后才执行回调。
  2. .race()的作用也是接收一组异步任务,然后并行执行异步任务,只保留取第一个执行完成的异步操作的结果,其他的方法仍在执行,不过执行结果会被抛弃。
  3. Promise.all().then()结果中数组的顺序和Promise.all()接收到的数组顺序一致。
  4. all和race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被then的第二个参数或者后面的catch捕获;但并不会影响数组中其它的异步任务的执行。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions