Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6.27道this-27道Promise #8

Open
webVueBlog opened this issue May 22, 2022 · 0 comments
Open

6.27道this-27道Promise #8

webVueBlog opened this issue May 22, 2022 · 0 comments

Comments

@webVueBlog
Copy link
Owner

webVueBlog commented May 22, 2022

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捕获;但并不会影响数组中其它的异步任务的执行。
@webVueBlog webVueBlog changed the title 6.27道this-45道Promise 6.27道this-27道Promise May 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant