Output 输出类
类型转换类
- 题目
- 答案
'true' == true
'true' == true // false
// ==> NaN == 1
// ==> false
- 题目
- 答案
'false' == false
'false' == false // false
// ==> NaN == 0
// ==> false
== 会把两边转成数字再比较:字符串 'false' 转数字是 NaN,布尔值 false 转数字是 0,NaN == 0 为 false。同理 'true' == true 是 NaN == 1,也是 false。
- 题目
- 答案
null == ''
null == '' // false
null 不等于任何值除了 null 和 undefined
- 题目
- 答案
['x'] == 'x'
['x'] == 'x' // true
== 一边是对象、一边是字符串时,会把对象转成原始值:先 valueOf(),数组的 valueOf 仍是数组本身,于是再 toString() 得到 'x',所以 ['x'] == 'x' 为 true。不是对数组做 Number 转换。
- 题目
- 答案
[1, 2, 3] == [1, 2, 3]
[1, 2, 3] == [1, 2, 3] // false
当运算符两边类型相同时,不会执行类型转换,两个数组的内存地址不一样,所以返回 false。
- 题目
- 答案
true + false
true + false // 1
+ 运算符会触发 number 类型转换,因此上面转为 1 + 0。
- 题目
- 答案
[1] > null
[1] > null // true
// ==> '1' > 0
// ==> 1 > 0
// ==> true
比较运算符 > 执行 number 类型隐式转换。
- 题目
- 答案
"foo" + + "bar"
"foo" + + "bar" // "fooNaN"
// ==> "foo" + (+"bar")
// ==> "foo" + NaN
// ==> "fooNaN"
一元 + 运算符比二元 + 运算符具有更高的优先级。所以 + bar 表达式先求值。一元加号执行字符串 bar 的 number 类型转换。因为字符串不代表一个有效的数字,所以结果是 NaN。在第二步中,计算表达式 'foo' + NaN。
- 题目
- 答案
0 || "0" && {}
0 || "0" && {} // {}
// ==> (0 || '0') && {}
// ==> (false || true) && true
// ==> true && true
// ==> true
逻辑运算符 || 和 && 将值转为 boolean 型,但是会返回原始值(不是 boolean)
- 题目
- 答案
{} + [] + {} + [1]
{} + [] + {} + [1] // '0[object Object]1'
// ==> +[] + {} + [1]
// ==> 0 + {} + [1]
// ==> 0 + '[object Object]' + '1'
// ==> '0[object Object]1'
所有的操作数都不是原始类型,所以会按照从左到右的顺序执行 number 类型的隐式转换,object 和 array 类型的 valueOf() 方法返回它们本身,所以直接忽略,执行 toString() 方法。 这里的技巧是,第一个 {} 不被视为 object,而是块声明语句,因此它被忽略。计算从 +[] 表达式开始,该表达式通过 toString() 方法转换为空字符串,然后转换为 0
- 题目
- 答案
! + [] + [] + ![]
! + [] + [] + ![] // 'truefalse'
// ==> !(+[]) + [] + (![])
// ==> !0 + [] + false
// ==> true + [] + false
// ==> true + '' + false
// ==> 'truefalse'
一元运算符优先执行,+[] 转为 number 类型 0,![] 转为 boolean 型 false。
- 题目
- 答案
new Date(0) - 0
new Date(0) - 0 // 0
// ==> 0 - 0
// ==> 0
'-' 运算符执行 number 类型隐式转换对于 Date 型的值,Date.valueOf() 返回到毫秒的时间戳。
- 题目
- 答案
new Date(0) + 0
new Date(0) + 0 // 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
// ==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)' + 0
// ==> 'Thu Jan 01 1970 02:00:00 GMT+0200 (EET)0'
'+' 运算符触发默认转换,因此使用 toString() 方法,而不是 valueOf()。
- 题目
- 答案
'ca' < 'bd'
'ca' < 'bd' // false
比较符两边都是字符串时,会比较字母表顺序。
- 题目
- 答案
Boolean(new Boolean(false))
Boolean(new Boolean(false)) // true
this 类
- 题目
- 答案
var a = 10
var obj = {
a: 20,
say: () => {
console.log(this.a)
}
}
obj.say()
var anotherObj = {
a: 30
}
obj.say.apply(anotherObj)
var a = 10
var obj = {
a: 20,
say: () => {
console.log(this.a)
}
}
obj.say()
var anotherObj = {
a: 30
}
obj.say.apply(anotherObj)
// 10
// 10
箭头函数时不绑定 this,且 this 来自原其父级所处的上下文,所以首先会打印全局中的 a 的值 10。后面虽然让 say 方法指向了另外一个对象,但是仍不能改变箭头函数的特性,它的 this 仍然是指向全局的,所以依旧会输出 10。
- 题目
- 答案
var obj = {
say: function () {
var f1 = () => {
console.log("1111", this);
}
f1();
},
pro: {
getPro: () => {
console.log(this);
}
}
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
var obj = {
say: function () {
var f1 = () => {
console.log("1111", this);
}
f1();
},
pro: {
getPro: () => {
console.log(this);
}
}
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
// 1111 window 对象
// 1111 obj 对象
// window 对象
o():o 在全局执行,而 f1 是箭头函数,this 指向其父级的 this,其父级 say 方法的 this 指向的是全局作用域,所以会打印出window;obj.say():谁调用 say,say 的 this 就指向谁,所以此时 this 指向的是 obj 对象;obj.pro.getPro():箭头函数时不绑定 this,getPro 处于 pro 中,而对象不构成单独的作用域,所以箭头函数的 this 就指向了全局作用域 window。
- 题目
- 答案
var myObject = {
foo: "bar",
func: function () {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function () {
console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();
var myObject = {
foo: "bar",
func: function () {
var self = this;
console.log(this.foo);
console.log(self.foo);
(function () {
console.log(this.foo);
console.log(self.foo);
}());
}
};
myObject.func();
// bar
// bar
// undefined
// bar
- 首先 func 由 myObject 调用,this 指向 myObject。又因为 var self = this; 所以 self 指向 myObject。
- 而立即执行匿名函数表达式由 window 调用的,this 指向 window。立即执行匿名函数的作用域处于 myObject.func 的作用域中,在这个作用域找不到 self 变量,沿着作用域链向上查找 self 变量,找到了指向 myObject 对象的 self。
- 题目
- 答案
var a = 1;
function printA() {
console.log(this.a);
}
var obj = {
a: 2,
foo: printA,
bar: function () {
printA();
}
}
obj.foo();
obj.bar();
var foo = obj.foo;
foo();
var a = 1;
function printA() {
console.log(this.a);
}
var obj = {
a: 2,
foo: printA,
bar: function () {
printA();
}
}
obj.foo();
obj.bar();
var foo = obj.foo;
foo();
// 2
// 1
// 1
obj.foo():foo 的 this 指向 obj 对象,所以 a 会输出 2;obj.bar():printA 在 bar 方法中执行,所以此时 printA 的 this 指向的是 window,所以会输出 1;foo():foo 是在全局对象中执行的,所以其 this 指向的是 window,所以会输出 1。
- 题目
- 答案
var x = 3;
var y = 4;
var obj = {
x: 1,
y: 6,
getX: function () {
var x = 5;
return function () {
return this.x;
}();
},
getY: function () {
var y = 7;
return this.y;
}
}
console.log(obj.getX())
console.log(obj.getY())
var x = 3;
var y = 4;
var obj = {
x: 1,
y: 6,
getX: function () {
var x = 5;
return function () {
return this.x;
}();
},
getY: function () {
var y = 7;
return this.y;
}
}
console.log(obj.getX())
console.log(obj.getY())
// 3
// 6
- 匿名函数的 this 指向全局对象,所以 this 指向 window,打印出 3;
- getY 由 obj 调用,所以其 this 指向 obj 对象,打印出 6。
作用域类
- 题目
- 答案
(function(){
var x = y = 1;
})();
var z;
console.log(y);
console.log(z);
console.log(x);
(function(){
var x = y = 1;
})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
var x = y = 1 从右往左执行:先执行 y = 1,y 没有声明,成为全局变量;再把 y 赋给用 var 声明的局部变量 x。所以函数外打印 y 是 1,打印 x 报 ReferenceError。
- 题目
- 答案
// a
function Foo() {
getName = function () {
console.log(1);
}
return this;
}
// b
Foo.getName = function () {
console.log(2);
}
// c
Foo.prototype.getName = function () {
console.log(3);
}
// d
var getName = function () {
console.log(4);
}
// e
function getName() {
console.log(5);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
// a
function Foo() {
getName = function () {
console.log(1);
}
return this;
}
// b
Foo.getName = function () {
console.log(2);
}
// c
Foo.prototype.getName = function () {
console.log(3);
}
// d
var getName = function () {
console.log(4);
}
// e
function getName() {
console.log(5);
}
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
Foo.getName():Foo 为一个函数对象,对象都可以有属性,b 处定义 Foo 的 getName 属性为函数,输出 2;getName():这里看 d、e 处,d 为函数表达式,e 为函数声明,两者区别在于变量提升,函数声明的 5 会被后边函数表达式的 4 覆盖;Foo().getName():这里要看 a 处,在 Foo 内部将全局的 getName 重新赋值为 console.log(1) 的函数,执行 Foo()返回 this,这个 this 指向 window,Foo().getName() 即为 window.getName(),输出 1;getName():上面 3 中,全局的 getName 已经被重新赋值,所以这里依然输出 1;new Foo.getName():这里等价于new (Foo.getName()),先执行 Foo.getName(),输出 2,然后 new 一个实例;new Foo().getName():这里等价于(new Foo()).getName(), 先 new 一个 Foo 的实例,再执行这个实例的 getName 方法,但是这个实例本身没有这个方法,所以去原型链__protot__上边找,实例.protot === Foo.prototype,所以输出 3;new new Foo().getName():这里等价于new (new Foo().getName()),如上述 6,先输出 3,然后 new 一个new Foo().getName()的实例。
setTimeout 类 + 怎么解决
- 题目
- 答案
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
回答:
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
// 5 5 5 5 5
因为 setTimeout 是异步函数,所以会先把循环全部执行完毕,这时候 i 已经是 5 了,再依次执行 setTimeout 输出一堆 5。
解决方式:可以通过 var 改为 let 的方式,在每次循环时产生独立的块级作用域,重新声明和赋值:
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, i * 1000)
}
也可以通过 setTimeout 的第三个参数,在定时器执行时作为参数传入执行函数来实现:
for (var i = 0; i < 5; i++) {
setTimeout(
(j) => {
console.log(j)
},
i * 1000,
i
)
}
还可以使用闭包的方式,使用立即执行函数将当前 i 的值固定在参数上,然后通过闭包执行函数执行参数来实现:
for (var i = 0; i < 5; i++) {
;
(function (j) {
setTimeout(() => {
console.log(j)
}, j * 1000)
})(i)
}
Promise 类
- 题目
- 答案
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
const promise = new Promise((resolve, reject) => {
console.log(1);
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
// 1
// 2
// 4
- Promise 构造函数里的代码是同步的,所以先打印 1、2,再打印 4
then的回调才是微任务,而且必须等 Promise 状态变成 fulfilled / rejected 才会入队- 这里一直 pending,所以不会输出 3
- 微任务是在当前宏任务结束后、下一个宏任务开始前执行,不是等「所有宏任务」都跑完
- 题目
- 答案
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);
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
- 首先遇到 Promise 构造函数,会先执行里面的内容,打印 1
- 遇到定时器 steTimeout,它是一个宏任务,放入宏任务队列
- 继续向下执行,打印出 2
- 由于 Promise 的状态此时还是 pending,所以 promise.then 先不执行
- 继续执行下面的同步任务,打印出 4
- 此时微任务队列没有任务,继续执行下一轮宏任务,执行 steTimeout
- 首先执行 timerStart,然后遇到了 resolve,将 promise 的状态改为 resolved
- 且保存结果并将之前的 promise.then 推入微任务队列,再执行 timerEnd
- 执行完这个宏任务,就去执行微任务 promise.then,打印出 resolve 的结果
- 题目
- 答案
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');
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
- 首先,Promise.resolve().then 是一个微任务,加入微任务队列
- 执行 timer1,它是一个宏任务,加入宏任务队列
- 继续执行下面的同步代码,打印出 start
- 第一轮宏任务执行完
- 开始执行微任务 Promise.resolve().then,打印出 promise1
- 遇到 timer2,它是一个宏任务,将其加入宏任务队列
- 此时宏任务队列有两个任务,分别是 timer1、timer2
- 第一轮微任务就执行完
- 开始执行第二轮宏任务,首先执行定时器 timer1,打印 timer1
- 遇到 Promise.resolve().then,它是一个微任务,加入微任务队列
- 开始执行微任务队列中的任务,打印 promise2
- 最后执行宏任务 timer2 定时器,打印出 timer2
- 题目
- 答案
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);
})
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
- 这个题目考察的是 Promise 的状态在发生变化之后,就不会再发生变化
- 开始状态由 pending 变为 resolve,说明已经变为已完成状态
- 下面的两个状态的就不会再执行,同时下面的 catch 也不会捕获到错误
- 题目
- 答案
Promise.resolve('1')
.then(res => {
console.log(res)
})
.finally(() => {
console.log('finally1')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log(res)
})
Promise.resolve('1')
.then(res => {
console.log(res)
})
.finally(() => {
console.log('finally1')
})
Promise.resolve('2')
.finally(() => {
console.log('finally2')
return '我是finally2返回的值'
})
.then(res => {
console.log(res)
})
// 1
// finally2
// finally1
// 2
- .finally() 方法不管 Promise 对象最后的状态如何都会执行
- .finally() 方法的回调函数不接受任何的参数
- 也就是说在 .finally() 函数中无法知道 Promise 最终的状态是 resolved 还是 rejected
- 它最终返回的默认会是一个上一次的 Promise 对象值
- 不过如果抛出的是一个异常则返回异常的 Promise 对象
- finally 本质上是 then 方法的特例
- 题目
- 答案
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
console.log(1)
setTimeout(() => {
console.log(2)
})
new Promise(resolve => {
console.log(3)
resolve(4)
}).then(d => console.log(d))
setTimeout(() => {
console.log(5)
new Promise(resolve => {
resolve(6)
}).then(d => console.log(d))
})
setTimeout(() => {
console.log(7)
})
console.log(8)
// 1
// 3
// 8
// 4
// 2
// 5
// 6
// 7
- 首先执行 script 代码,打印出 1;
- 遇到第一个定时器,加入到宏任务队列;
- 遇到 Promise,执行代码,打印出 3,遇到 resolve,将其加入到微任务队列;
- 遇到第二个定时器,加入到宏任务队列;
- 遇到第三个定时器,加入到宏任务队列;
- 继续执行 script 代码,打印出 8,第一轮执行结束;
- 执行微任务队列,打印出第一个 Promise 的 resolve 结果:4;
- 开始执行宏任务队列,执行第一个定时器,打印出 2;
- 此时没有微任务,继续执行宏任务中的第二个定时器,首先打印出 5,遇到 Promise,首选打印出 6,遇到 resolve,将其加入到微任务队列;
- 执行微任务队列,打印出 6;
- 执行宏任务队列中的最后一个定时器,打印出 7。
- 题目
- 答案
Promise.resolve().then(() => {
console.log('1');
throw 'Error';
}).then(() => {
console.log('2');
}).catch(() => {
console.log('3');
throw 'Error';
}).then(() => {
console.log('4');
}).catch(() => {
console.log('5');
}).then(() => {
console.log('6');
});
Promise.resolve().then(() => {
console.log('1');
throw 'Error';
}).then(() => {
console.log('2');
}).catch(() => {
console.log('3');
throw 'Error';
}).then(() => {
console.log('4');
}).catch(() => {
console.log('5');
}).then(() => {
console.log('6');
});
// 1
// 3
// 5
// 6
在这道题目中,我们需要知道,无论是 thne 还是 catch 中,只要 throw 抛出了错误,就会被 catch 捕获,如果没有 throw 出错误,就继续执行后面的 then。
- 题目
- 答案
setTimeout(function () {
console.log(1);
}, 100);
new Promise(function (resolve) {
console.log(2);
resolve();
console.log(3);
}).then(function () {
console.log(4);
new Promise((resove, reject) => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 10);
})
});
console.log(7);
console.log(8);
setTimeout(function () {
console.log(1);
}, 100);
new Promise(function (resolve) {
console.log(2);
resolve();
console.log(3);
}).then(function () {
console.log(4);
new Promise((resove, reject) => {
console.log(5);
setTimeout(() => {
console.log(6);
}, 10);
})
});
console.log(7);
console.log(8);
// 2
// 3
// 7
// 8
// 4
// 5
// 6
// 1
- 首先遇到定时器,将其加入到宏任务队列;
- 遇到 Promise,首先执行里面的同步代码,打印出 2,遇到 resolve,将其加入到微任务队列,执行后面同步代码,打印出 3;
- 继续执行 script 中的代码,打印出 7 和 8,至此第一轮代码执行完成;
- 执行微任务队列中的代码,首先打印出 4,如遇到 Promise,执行其中的同步代码,打印出 5,遇到定时器,将其加入到宏任务队列中,此时宏任务队列中有两个定时器;
- 第一个定时器是 100ms,第二个是 10ms,所以先执行第二个,打印出 6;
- 此时微任务队列为空,继续执行宏任务队列,打印出 1。
Async / Await 类
- 题目
- 答案
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
console.log('start')
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
async1();
console.log('start')
// async1 start
// async2
// start
// async1 end
- 首先执行函数中的同步代码 async1 start
- 之后遇到了 await,它会阻塞 async1 后面代码的执行
- 因此会先去执行 async2 中的同步代码 async2,然后跳出 async1;
- 跳出 async1 函数后,执行同步代码 start;
- 在一轮宏任务全部执行完之后,再来执行 await 后面的内容 async1 end
- 题目
- 答案
async function async1() {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
async function async1() {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
})
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
// script start
// async1 start
// promise1
// script end
async1 里 await 后面的 Promise 没有 resolve,状态一直是 pending,所以 await 之后的代码不会执行,async1 后面的 .then 也不会执行。
- 题目
- 答案
async function async1() {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
async function async1() {
console.log('async1 start');
await new Promise(resolve => {
console.log('promise1')
resolve('promise1 resolve')
}).then(res => console.log(res))
console.log('async1 success');
return 'async1 end'
}
console.log('srcipt start')
async1().then(res => console.log(res))
console.log('srcipt end')
// script start
// async1 start
// promise1
// script end
// promise1 resolve
// async1 success
// async1 end
这里是对上面一题进行了改造,加上了 resolve。
- 题目
- 答案
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(resolve => {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end')
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
}
console.log("script start");
setTimeout(function () {
console.log("setTimeout");
}, 0);
async1();
new Promise(resolve => {
console.log("promise1");
resolve();
}).then(function () {
console.log("promise2");
});
console.log('script end')
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout
- 开头定义了 async1 和 async2 两个函数,但是并未执行,执行 script 中的代码,所以打印出 script start;
- 遇到定时器 setTimeout,它是一个宏任务,将其加入到宏任务队列;
- 之后执行函数 async1,首先打印出 async1 start;
- 遇到 await,执行 async2,打印出 async2,并阻断后面代码的执行,将后面的代码加入到微任务队列;
- 然后跳出 async1 和 async2,遇到 Promise,打印出 promise1;
- 遇到 resolve,将其加入到微任务队列,然后执行后面的 script 代码,打印出 script end;
- 之后就该执行微任务队列了,首先打印出 async1 end,然后打印出 promise2;
- 执行完微任务队列,就开始执行宏任务队列中的定时器,打印出 setTimeout。
process.nextTick 类
- 题目
- 答案
console.log('1');
setTimeout(function () {
console.log('2');
process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
process.nextTick(function () {
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
process.nextTick(function () {
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
})
console.log('1');
setTimeout(function () {
console.log('2');
process.nextTick(function () {
console.log('3');
})
new Promise(function (resolve) {
console.log('4');
resolve();
}).then(function () {
console.log('5')
})
})
process.nextTick(function () {
console.log('6');
})
new Promise(function (resolve) {
console.log('7');
resolve();
}).then(function () {
console.log('8')
})
setTimeout(function () {
console.log('9');
process.nextTick(function () {
console.log('10');
})
new Promise(function (resolve) {
console.log('11');
resolve();
}).then(function () {
console.log('12')
})
})
// 1
// 7
// 6
// 8
// 2
// 4
// 3
// 5
// 9
// 11
// 10
// 12
queueMicrotask 类
- 题目
- 答案
Promise.resolve().then(() => console.log('promise1'));
queueMicrotask(() => console.log('microtask1'));
console.log('sync');
queueMicrotask(() => console.log('microtask2'));
Promise.resolve().then(() => console.log('promise2'));
Promise.resolve().then(() => console.log('promise1'));
queueMicrotask(() => console.log('microtask1'));
console.log('sync');
queueMicrotask(() => console.log('microtask2'));
Promise.resolve().then(() => console.log('promise2'));
// sync
// promise1
// microtask1
// microtask2
// promise2
Promise.then 和 queueMicrotask 进的是同一个微任务队列,按入队顺序 FIFO 执行;先同步代码跑完,再一次性清空整个微任务队列。queueMicrotask 比 Promise.resolve().then() 语义更清晰、开销更小,是标准的微任务入队 API。
微任务递归类(饿死宏任务)
- 题目
- 答案
setTimeout(() => console.log('timeout'), 0);
(function loop() {
queueMicrotask(() => {
console.log('tick');
loop(); // 无限递归产生新微任务
});
})();
// tick 会无限打印下去,timeout 永远不执行
原因:事件循环每轮「取一个宏任务、清空整个微任务队列」再进入渲染。递归的微任务让队列永远清不完,渲染和后面的宏任务永远排不上——这比宏任务死循环更糟,页面直接卡死。经典陷阱:在 Promise.then 里递归 Promise.resolve().then(...) 也是同样效果。
requestAnimationFrame 类
- 题目
- 答案
requestAnimationFrame(() => console.log('raf'));
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
// sync
// promise
// raf
rAF 既不是宏任务也不是微任务:它在渲染流程的专门阶段执行,位于微任务清空之后、样式计算和绘制之前,每帧一次。所以同步代码先跑,然后微任务队列清空,最后才轮到 rAF。⚠️ 陷阱:rAF 和 setTimeout(0) 的先后是不确定的,取决于下一帧有没有到达,面试别押这两个的顺序。rAF 适合做写 DOM 前的最后一次计算,不适合做长计算。
MessageChannel 类
- 题目
- 答案
const { port1, port2 } = new MessageChannel();
port1.onmessage = () => console.log('message');
port2.postMessage('hi');
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
const { port1, port2 } = new MessageChannel();
port1.onmessage = () => console.log('message');
port2.postMessage('hi');
Promise.resolve().then(() => console.log('promise'));
console.log('sync');
// sync
// promise
// message
MessageChannel 的 onmessage 回调是宏任务,必然晚于微任务。考点延伸:React 的 Scheduler 就是用 MessageChannel 制造宏任务来让出主线程,配合约 5ms 的时间切片;不用 setTimeout(0) 是因为嵌套 setTimeout 有 4ms 最小延迟限制,累积起来浪费严重。
MutationObserver 类
- 题目
- 答案
const target = document.getElementById('box');
const observer = new MutationObserver(() => console.log('mutation'));
observer.observe(target, { attributes: true });
Promise.resolve().then(() => console.log('promise1'));
target.setAttribute('data-x', '1');
Promise.resolve().then(() => console.log('promise2'));
// promise1
// mutation
// promise2
MutationObserver 的回调是微任务,在 DOM 发生变化的那一刻入队。promise1 在同步代码里先入队,所以先执行;setAttribute 触发 mutation 回调入队,排在 promise2 前面;最后执行 promise2。这是浏览器环境的规范行为(Node 无 DOM,无法本地运行)。
Generator / yield 类
- 题目
- 答案
function* gen() {
console.log('start');
const a = yield 1;
console.log('a =', a);
const b = yield 2;
console.log('b =', b);
return 3;
}
const it = gen();
console.log(it.next());
console.log(it.next('x'));
console.log(it.next('y'));
console.log(it.next());
function* gen() {
console.log('start');
const a = yield 1;
console.log('a =', a);
const b = yield 2;
console.log('b =', b);
return 3;
}
const it = gen();
console.log(it.next());
console.log(it.next('x'));
console.log(it.next('y'));
console.log(it.next());
// start
// {value: 1, done: false}
// a = x
// {value: 2, done: false}
// b = y
// {value: 3, done: true}
// {done: true}
要点:generator 函数体不执行,直到第一次 next() 才跑到第一个 yield;next(参数) 传的值会成为上一个 yield 表达式的结果;return 之后生成器结束,再 next() 只返回 {done: true}。
- 题目
- 答案
async function* nums() {
yield 1;
yield 2;
yield 3;
}
(async () => {
for await (const n of nums()) console.log('n =', n);
})();
async function* nums() {
yield 1;
yield 2;
yield 3;
}
(async () => {
for await (const n of nums()) console.log('n =', n);
})();
// n = 1
// n = 2
// n = 3
for await...of 用来消费异步迭代器(AsyncIterator),每次迭代会 await 下一个值;普通 for...of 消费不了异步迭代器。
class / super / static 类
- 题目
- 答案
class Parent {
static who() { return 'Parent'; }
hello() { return `hello ${this.name}`; }
}
class Child extends Parent {
constructor() { super(); this.name = 'child'; }
static who() { return 'Child'; }
hello() { return super.hello() + '!'; }
}
console.log(Child.who());
console.log(new Parent().hello());
console.log(new Child().hello());
console.log(Object.getPrototypeOf(Child) === Parent);
class Parent {
static who() { return 'Parent'; }
hello() { return `hello ${this.name}`; }
}
class Child extends Parent {
constructor() { super(); this.name = 'child'; }
static who() { return 'Child'; }
hello() { return super.hello() + '!'; }
}
console.log(Child.who());
console.log(new Parent().hello());
console.log(new Child().hello());
console.log(Object.getPrototypeOf(Child) === Parent);
// Child
// hello undefined
// hello child!
// true
要点:static 方法按类查找,子类可以覆盖父类;super.hello() 调的是父类方法但 this 指向子类实例;类的继承(extends)在原型层面是 Child.__proto__ === Parent,而普通实例继承是 child.__proto__ === Child.prototype,两者别混。
- 题目
- 答案
class Counter {
static instances = 0;
static { console.log('static block'); }
#count = 0;
inc() { this.#count++; return this.#count; }
get count() { return this.#count; }
}
const c1 = new Counter();
const c2 = new Counter();
Counter.instances += 2;
c1.inc();
c1.inc();
console.log('count:', c1.count, 'instances:', Counter.instances);
class Counter {
static instances = 0;
static { console.log('static block'); }
#count = 0;
inc() { this.#count++; return this.#count; }
get count() { return this.#count; }
}
const c1 = new Counter();
const c2 = new Counter();
Counter.instances += 2;
c1.inc();
c1.inc();
console.log('count:', c1.count, 'instances:', Counter.instances);
// static block
// count: 2 instances: 2
static 块在类定义时执行一次;私有字段 #count 是实例隔离的,c1 的计数不影响 c2,且外部无法直接访问(访问会语法报错)。
Promise 新 API 类
- 题目
- 答案
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve('resolved'), 0);
promise.then((v) => console.log(v));
Promise.allSettled([Promise.resolve(1), Promise.reject('err')])
.then((r) => console.log(r.map((x) => x.status).join(',')));
Promise.any([Promise.reject('a'), Promise.resolve('b')])
.then((v) => console.log('any:', v));
console.log('sync');
const { promise, resolve } = Promise.withResolvers();
setTimeout(() => resolve('resolved'), 0);
promise.then((v) => console.log(v));
Promise.allSettled([Promise.resolve(1), Promise.reject('err')])
.then((r) => console.log(r.map((x) => x.status).join(',')));
Promise.any([Promise.reject('a'), Promise.resolve('b')])
.then((v) => console.log('any:', v));
console.log('sync');
// sync
// fulfilled,rejected
// any: b
// resolved
allSettled 和 any 的 then 回调是同步注册的微任务,先入队先执行;withResolvers 的 resolve 在 setTimeout 宏任务里触发,所以 promise.then 最后输出。withResolvers 一次性拿到 promise 和 resolve/reject,不用再包一层闭包。
Symbol.toPrimitive 类
- 题目
- 答案
const obj = {
[Symbol.toPrimitive](hint) {
console.log('hint:', hint);
return hint === 'string' ? '42' : 42;
},
valueOf() { console.log('valueOf'); return 100; },
toString() { console.log('toString'); return '200'; },
};
console.log(obj + 1);
console.log(`${obj}`);
console.log(String(obj));
console.log(+obj);
const obj = {
[Symbol.toPrimitive](hint) {
console.log('hint:', hint);
return hint === 'string' ? '42' : 42;
},
valueOf() { console.log('valueOf'); return 100; },
toString() { console.log('toString'); return '200'; },
};
console.log(obj + 1);
console.log(`${obj}`);
console.log(String(obj));
console.log(+obj);
// hint: default → 43
// hint: string → 42
// hint: string → 42
// hint: number → 42
有 Symbol.toPrimitive 时它优先于 valueOf/toString,且能拿到 hint(default/string/number)精确控制三种场景:+ 运算和 == 比较是 default,模板字符串和 String() 是 string,一元 + 和 Number() 是 number。
- 题目
- 答案
const o2 = { valueOf() { return 3; }, toString() { return '4'; } };
console.log(o2 + 1);
console.log(`${o2}`);
const o2 = { valueOf() { return 3; }, toString() { return '4'; } };
console.log(o2 + 1);
console.log(`${o2}`);
// 4
// 4
没有 toPrimitive 时:default 提示先试 valueOf(3+1=4);模板字符串是 string 提示先试 toString('4')。两者答案恰好都是 4,但走的路径不同——改一下返回值就能看出区别。
暂时性死区类
- 题目
- 答案
var hoisted = 'ok';
try {
console.log(typeof tdzVar);
} catch (e) {
console.log('TDZ:', e.constructor.name);
}
let tdzVar = 2;
console.log(typeof hoisted, typeof tdzVar);
var hoisted = 'ok';
try {
console.log(typeof tdzVar);
} catch (e) {
console.log('TDZ:', e.constructor.name);
}
let tdzVar = 2;
console.log(typeof hoisted, typeof tdzVar);
// TDZ: ReferenceError
// string number
let/const 有「暂时性死区」:从作用域开始到声明语句执行完之前,访问该变量直接抛 ReferenceError。注意 typeof 一个不存在的变量返回 undefined 不报错,但 typeof 一个 TDZ 中的变量会报错——这是和 var 最明显的差异。
- 题目
- 答案
var a = 'outer';
{
try {
console.log(a);
} catch (e) {
console.log('block TDZ:', e.constructor.name);
}
let a = 'inner';
}
var a = 'outer';
{
try {
console.log(a);
} catch (e) {
console.log('block TDZ:', e.constructor.name);
}
let a = 'inner';
}
// block TDZ: ReferenceError
块内声明 let a 后,整个块从开始就进入 a 的 TDZ——即使外层有同名 var a,块内也访问不到,读到的不是 outer。这就是「遮蔽 + 死区」的组合陷阱。