前端手撕代码之事件循环+this
个人的学习笔记,从各个大佬的文章里做了自己的一个梳理和理解,不是很专业,适合新手。
前端手撕代码之事件循环+this指向
代码来自于https://juejin.cn/post/6959043611161952269#heading-33
首先介绍一下事件循环,事件循环首先要理清stack,webapi,任务队列的执行顺序,任务队列分为宏任务和微任务,分清同步和异步的执行顺序。之后列出经常问到的题型,我根据CUGGZ掘金的文章进行题型总结。
事件循环+变量提升
for (var i = 0; i < 3; i++) {
setTimeout(() => {
console.log(i)
}, 1000)
}
console.log(i)
答案输出:
3
3
3
3
事件循环+promise链(promise消费者之then)
promise.then 是微任务,它会在所有的宏任务执行完之后才会执行,同时需要promise内部的状态发生变化。要不然不出值。
1)明确什么是promise链
2)promise链的几种特殊情况(异常传/穿透,中断Promise串)
a.异常传/穿透
(1)Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
解析:
.then和.catch需要的是一个promise对象值。如果传入的是数字/布尔/字符等非promise对象值,那么默认返回一个fulfilled状态,因为第一个和第二个then中传入的都不是函数,因此发生了透传,将resolve(1)的值直接传到最后一个then里。
答案:
1
(2)
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“OK”);
}, 1000);
});
p.then((value) => {
console.log(111);
throw “失败了”;
//return new Promise(()=>{})
})
.then((value) => {
console.log(222);
})
.then((value) => {
console.log(333);
})
.catch((reason) => {
console.log(reason);
});
解析:
在Promise串中,只需要在末尾添加一个catch方法,串前面任何一个then内容执行失败,都会在末尾被catch。
答案:
111
失败了
b.中断Promise串
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(“OK”);
}, 1000);
});
p.then((value) => {
console.log(111);
//throw “失败了”;
})
.then((value) => {
console.log(222);
return new Promise(()=>{})
})
.then((value) => {
console.log(333);
})
.catch((reason) => {
console.log(reason);
});
解析:
在任何一个then方法中,返回一个状态为pending的Promise,该Promise则会中断。
这个promise碰上的问题在async await之后也会碰到类似的,之后详细说明。
事件循环+promise消费者之catch
Promise.prototype.catch 方法是 .then(null, rejection) 或是 .then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
如果Promise 对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。
小试牛刀:
Promise.resolve(1)
.then(res => {
console.log(res);
return 2;
})
.catch(err => {
return 3;
})
.then(res => {
console.log(res);
});
答案:
1
2
常见考点:
1.异步代码的运行时错误无法被自动 reject 进而被 catch 捕获,而是直接报错:
2.一旦 Promise 对象已经 resolve,其后的运行时错误将被忽略,例子:
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
3…then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环,例子:
const promise = Promise.resolve().then(() => {
return promise;
})
promise.catch(console.err)
答案:
Uncaught (in promise) TypeError: Chaining cycle detected for promise
4.错误只能被捕获一次,如果被then里面的error参数捕获了,就不能被catch再次捕获,例子:
Promise.reject(‘err!!!’)
.then((res) => {
console.log(‘success’, res)
}, (err) => {
console.log(‘error’, err)
}).catch(err => {
console.log(‘catch’, err)
})
答案:
error err!!!
对比:
Promise.resolve()
.then(function success (res) {
throw new Error(‘error!!!’)
}, function fail1 (err) {
console.log(‘fail1’, err)
}).catch(function fail2 (err) {
console.log(‘fail2’, err)
})
答案:
‘fail2’ err!!!
事件循环+promise消费者之finally
1.注意finally的执行顺序:在执行完then或catch指定的回调函数以后,都会执行finally方法指定的回调函数。
Promise.resolve(‘1’)
.then(res => {
console.log(res)
})
.finally(() => {
console.log(‘finally’)
})
Promise.resolve(‘2’)
.finally(() => {
console.log(‘finally2’)
return ‘我是finally2返回的值’
})
.then(res => {
console.log(‘finally2后面的then函数’, res)
})
答案:
1
finally2
finally
finally2后面的then函数 2
个人总结:
1.then在前面则先执行then,finally在前面就先执行finally但要把之前的finally都执行了。
举一反三:
Promise.resolve(‘1’)
.finally(() => {
console.log(‘finally1’)
throw new Error(‘我是finally中抛出的异常’)
})
.then(res => {
console.log(‘finally后面的then函数’, res)
})
.catch(err => {
console.log(‘捕获错误’, err)
})
答案:
‘finally1’
‘捕获错误’ Error: 我是finally中抛出的异常
事件循环+promise.reject/resolve
resolve方法的作用是把promise对象的状态从进行中变成已完成,同时可以向resolve方法传入参数,这个参数会在将来被promise对象的then方法获取,而reject方法也是同样的道理,只不过是把promise对象状态变成失败,同时传入的参数会被catch方法获取而已。
牛刀小试1:
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)
答案:
promise1 Promise {}
promise2 Promise {}
Uncaught (in promise) Error: error!!!
promise1 Promise {: “success”}
promise2 Promise {: Error: error!!}
牛刀小试2:
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
牛刀小试3:
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{: resolve1}
2 Promise{}
resolve1
事件循环+promise.all
Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值,不返回任何结果。
成功的情况:
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))
答案:
1
2
3
[1, 2, 3]
失败的情况:
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(Error: ${x}
, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res))
.catch(err => console.log(err))
答案:
// 1s后输出
1
3
// 2s后输出
2
Error: 2
// 4s后输出
4
事件循环+promise.race
promise.race是指第一个捕获的方法判断成功还是失败
应用场景:经常把异步操作和定时器放在一起,如果定时器先起作用说明超时。
牛刀小试:
成功的情况:
function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
Promise.race([runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log('result: ', res))
.catch(err => console.log(err))
答案:
1
'result: ’ 1
2
3
失败的情况:
function runAsync(x) {
const p = new Promise(r =>
setTimeout(() => r(x, console.log(x)), 1000)
);
return p;
}
function runReject(x) {
const p = new Promise((res, rej) =>
setTimeout(() => rej(Error: ${x}
, console.log(x)), 1000 * x)
);
return p;
}
Promise.race([runReject(0), runAsync(1), runAsync(2), runAsync(3)])
.then(res => console.log("result: ", res))
.catch(err => console.log(err));
答案:
0
Error: 0
1
2
3
事件循环+async+await+(定时器)
异步事件调用的另一种方法和promise的异步调用类似所以遇到的问题也类似。
await==放到了new Promise中,下一行及之后的语句相当于放在Promise.then中。
async await实际上是promise的语法糖
new Promise((resolve, reject) => {
//位置1
resolve()
}).then(() => {
//位置2
})
async function async1() {
console.log(‘async1 start’)
await async2()
console.log(‘async end’)
}
async function async2() {
console.log(‘async2’)
}
async1()
解析:
await后面的代码async2()相当于放在位置1处,下面行的console.log(‘async end’)相当于放在位置2处
题型一:基本模型
牛刀小试:
async function async1() {
console.log(“async1 start”);
await async2();
console.log(“async1 end”);
setTimeout(() => {
console.log(‘timer1’)
}, 0)
}
async function async2() {
setTimeout(() => {
console.log(‘timer2’)
}, 0)
console.log(“async2”);
}
async1();
setTimeout(() => {
console.log(‘timer3’)
}, 0)
console.log(“start”)
答案:
async1 start
async2
start
async1 end
timer2
timer3
timer1
牛刀小试:增加难度加入同步和异步的先后执行顺序判断
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
牛刀小试:增加难度加入resolve
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
牛刀小试:增加难度加入promise
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
题型二:和catch连用
async function async1 () {
await async2();
console.log(‘async1’);
return ‘async1 success’
}
async function async2 () {
return new Promise((resolve, reject) => {
console.log(‘async2’)
reject(‘error’)
})
}
async1().then(res => console.log(res))
答案:
async2
Uncaught (in promise) error
可以看到,如果async函数中抛出了错误,就会终止错误结果,不会继续向下执行。
如果想要让错误不足之处后面的代码执行,可以使用catch来捕获:
async function async1 () {
await Promise.reject(‘error!!!’).catch(e => console.log(e))
console.log(‘async1’);
return Promise.resolve(‘async1 success’)
}
async1().then(res => console.log(res))
console.log(‘script start’)
答案:
script start
error!!!
async1
async1 success
特殊情况:注意定时器的时间,可能有的定时器先执行但等待时间比较长,答案就在之后才出来。
this常见面试题
1)涉及到箭头函数,针对箭头函数我们要明确箭头函数是在对象里还是在函数里,在对象里的话,箭头里的this指向对象的外部。在函数里的话,箭头的this指向函数的内部箭头的外部。
2)判断函数里面是否有独立函数,是否有匿名函数,这两者的this都是指向window。
3)思考优先级:new>call, bind,apply>隐式绑定
4)区分self和this
5)判断x有没有被window覆盖。
作用域&变量提升&闭包 常见面试题
1)作用域:全局作用域,局部作用域,块级作用域,变量作用域,作用域链
举例:
(function(){
var x = y = 1;
})();
var z;
console.log(y); // 1
console.log(z); // undefined
console.log(x); // Uncaught ReferenceError: x is not defined
2)变量提升和函数提升:变量提升的定义还有函数提升,函数分为函数声明和函数表达式两种。
举例1:
function fn1(){
console.log(‘fn1’)
}
var fn2
fn1()
fn2()
fn2 = function() {
console.log(‘fn2’)
}
fn2()
答案:
fn1
Uncaught TypeError: fn2 is not a function
fn2
举例2:
function a() {
var temp = 10;
function b() {
console.log(temp); // 10
}
b();
}
a();
function a() {
var temp = 10;
b();
}
function b() {
console.log(temp); // 报错 Uncaught ReferenceError: temp is not defined
}
a();
3)闭包:沿着作用域向上一级的作用域中寻找参数
举例:
function fun(n, o) {
console.log(o)
return {
fun: function(m){
return fun(m, n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
解析:
这是一道关于闭包的题目,对于fun方法,调用之后返回的是一个对象。我们知道,当调用函数的时候传入的实参比函数声明时指定的形参个数要少,剩下的形参都将设置为undefined值。所以 console.log(o); 会输出undefined。而a就是是fun(0)返回的那个对象。也就是说,函数fun中参数 n 的值是0,而返回的那个对象中,需要一个参数n,而这个对象的作用域中没有n,它就继续沿着作用域向上一级的作用域中寻找n,最后在函数fun中找到了n,n的值是0。了解了这一点,其他运算就很简单了,以此类推。
答案:
undefined 0 0 0
undefined 0 1 2
undefined 0 1 1
4)一些特殊情况:
- (function(){}())与(function(){})()里面执行同步
这两种写法,都是一种 立即执行函数 的写法,即IIFE (Immediately Invoked Function Expression)。这种函数在函数定义的地方就直接执行了。
在 JavaScript中, Function 和 var 都会被提升(变量提升)
对比:
var friendName = ‘World’;
(function() {
if (typeof friendName === ‘undefined’) {
var friendName = ‘Jack’;
console.log('Goodbye ’ + friendName);
} else {
console.log('Hello ’ + friendName);
}
})();
等同于:
var name = ‘World!’;
(function () {
var name;
if (typeof name === ‘undefined’) {
name = ‘Jack’;
console.log('Goodbye ’ + name);
} else {
console.log('Hello ’ + name);
}
})();
牛刀小试:
var a, b
(function () {
console.log(a);
console.log(b);
var a = (b = 3);
console.log(a);
console.log(b);
})()
console.log(a);
console.log(b);
答案:
undefined
undefined
3
3
undefined
3
- (function(){}())与(function(){})()里面调用独立函数。
js中变量的作用域链与定义时的环境有关,与执行时无关。执行环境只会改变this、传递的参数、全局变量等。
var a=3;
function c(){
alert(a);
}
(function(){
var a=4;
c();//调用方法,执行定义时的环境
})();
输出答案:3
var a=3;
function c(){
alert(a);
}
(function(){
a=4;
c();
})();
输出答案:4
这个就涉及到变量作用域。
存在一种变量声明被提升的机制,也就是说函数声明会被提升到作用域的最前面,即使写代码的时候是写在最后面,也还是会被提升至最前面。—函数声明会被函数表达式覆盖。
原型&继承 常见面试题
基本概念:
函数.protoype=object
object.constructor=函数
函数.new一个object=实例
实例.proto=原型对象
难点:object-proto->function.prototype-proto->object.prototype
object-prototytpe->object.prototype