高频前端面试题汇总之代码输出结果篇---笔记

前端手撕代码之事件循环+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)一些特殊情况:

  1. (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

  1. (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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值