js|面试💯💯中常被问到的场景题

514 阅读5分钟

一、 说输出

1、变量提升问题

for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i); 
    }, 1000) // 1秒后一次性输出 5 5 5 5 5
}

2、优化每隔1秒输出0 1 2 3 4


/**
 * 正确
 */
for (let i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i); // 0 1 2 3 4
    }, 1000)
}
/**
 * 最简单的一个循环
 */
for (var i = 0; i < 5; i++) {
    console.log(i); // 0 1 2 3 4 
}
/**
 * 闭包
 */
for (var i = 0; i < 5; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i); //每隔1秒输出0 1 2 3 4
        }, i * 1000)
    })(i)
}

/**
 * 立即执行函数-node环境会报错,浏览器下输出0 1 2 3 4
 */
 for (var i = 0; i < 5; i++) {
    setTimeout((function(i) {
      console.log(i);
    })(i), i * 1000);
 

3、每隔1秒输出5 5 5 5 5

/**
 * 内部没有对i保持引用
 */
for (var i = 0; i < 5; i++) {
    (function () {
        setTimeout(function () {
            console.log(i); //每隔1秒输出5 5 5 5 5
        }, i * 1000)
    })(i)
}
/**
 * 
 */
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000 * i) // 每隔1秒输出一个5
}

4、宏微任务问题

/**
 * Promise  // 2 3 5 4 1
 */
setTimeout(function(){
    console.log(1);
},0)
new Promise(function executor(resolve){
    console.log(2);
    for(var i=0;i<10000;i++){
        i ==9999 && resolve();
    }
    console.log(3);

}).then(function(){
    console.log(4);
})
console.log(5);
const pro = new Promise((resolve, reject) => {
    const innerpro = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 0);
        console.log(2);
        resolve(3);
    });
    innerpro.then(res => console.log(res));
    resolve(4);
    console.log("pro");
})
pro.then(res => console.log(res));
console.log("end");
// 2 pro end 3 4

async必须返回一个promise对象,如果没有,它自己自动封装

5、对async的理解

async必须返回一个promise对象,如果没有,它自己自动封装

/**
 * 
 * @returns async必定返回一个promise对象
 */
async function fn1() {
    return 123
}
function fn2() {
    return 123
}
console.log(fn1()) // Promise { 123 }
console.log(fn2()) // 123

6、await function 执行顺序是从右向左

/**
 * 从右向向左执行
 */
async function async1() {
    console.log('async1 start') // (1)
    await async2()
    console.log('async1 end')  // (4)
}
async function async2() {
    console.log('async2') //(2)
}
async1()
console.log('script start')  // (3)
// async1 start
// async2      
// script start
// async1 end  

7、对js运行机制的理解

/**
 * Promise  // 2 3 5 4 1
 */
 setTimeout(function(){
    console.log(1);
},0)
new Promise(function executor(resolve){
    console.log(2);
    for(var i=0;i<10000;i++){
        i ==9999 && resolve();
    }
    console.log(3);

}).then(function(){
    console.log(4);
})
console.log(5);
 // 2 3 5 4 1

8、宏任务和微任务

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2) //1 2 3 4

二、分析执行顺序

1、经典面试题


/**
 * 经典面试题1
 */
 async function async1() {
    console.log("async1 start");// (2)
    await async2(); // 有问题,先执行返回函数,完成后,执行下一句,也就是 先输出promise2 在输出async1 end 但是输出结果是先 async1 end 在输出promise2
    console.log("async1 end");//(6)
}

async function async2() {
    console.log("async2"); //(3)
}
// 开始执行任务
console.log("script start"); //宏任务1  (1)
setTimeout(function () {
    console.log("setTimeout"); // 宏任务2 (8))
}, 0);

async1();

new Promise(function (resolve) {
    console.log("promise1"); //(4)
    resolve();
}).then(function () {
    console.log("promise2"); //微任务1 (7)
});

console.log("script end");//(5)
// script start
// async1 start
// async2
// promise1
// script end
// async1 end
// promise2
// setTimeout

2、分析

(1) 直接打印同步代码script start 2个函数声明没有调用,先不管

(2) 将setTimeout放入宏任务中,包裹的代码可以理解成宏任务2

(3) 调用async1,直接输出同步代码async1 start

(4) 遇到await从右向左执行,先执行async2输出 async2

(5) 阻塞async的执行,先执行async外的同步代码,遇到promise,直接执行同步代码promise1

(6) 运行到promise.then,发现这是一个微任务,加入当前宏任务的微任务队列中

(7) 继续执行同步代码script end

(8) 同步代码全部执行完成,回到async内部,await执行完成,执行await后面的代码 async1 end

(9) 全部的宏任务1执行完成,在执行微任务,输出promise2

(10) 宏任务1和宏任务1中微任务全部完成,进行下一个宏任务,输出setTimeout

3、宏任务和微任务

image.png

4、经典面试题

/**
 * 经典面试题2
 */
async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2() {
    console.log('async2 start');
    return new Promise((resolve, reject) => {
        resolve();
        console.log('async2 promise');
    })
}
//任务开始
console.log('script start');
setTimeout(function () {
    console.log('setTimeout');
}, 0);
async1();
new Promise(function (resolve) {
    console.log('promise1');
    resolve();
}).then(function () {
    console.log('promise2');
}).then(function () {
    console.log('promise3');
});
console.log('script end');
// script start
// async1 start  
// async2 start  
// async2 promise
// promise1      
// script end    
// promise2      
// promise3      
// async1 end    
// setTimeout

(1)执行同步代码script start

(2)遇到setTimeout放入下一个宏任务中

(3)调用async1,直接输出同步代码async1 start'

(4)遇到await,执行async2,直接输出同步函数async2 start

async2 promise

(5) 阻塞代码,执行async之外的同步代码 promise1

(6) 遇到then,是微任务,放入微任务中promise2

(7) 遇到then,是微任务,放到微任务中 promise3

(8) 执行同步代码'script end'

(9) 所有同步代码完成,回到async中,但是此时await并没有执行完成,await等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果,那何时能拿到运算结果呢?回忆平时我们用promise,调用resolve后,何时能拿到运算结果?是不是需要在then的第一个参数里,才能拿到结果。所以这里的 await Promise.resolve() 就类似于Promise.resolve(undefined).then((undefined) => { })把then的第一个回调参数 (undefined) => {} 推入微任务队列。

(10) 执行微任务,输出 promise2

(11)执行微任务,输出 promise3

(12) 执行微任务,没什么内容

(13) 这时候,then执行完成,await async2()语句结束,后面的代码不再被阻塞,所以打印 async1 end

(14) 执行宏任务2 setTimeout

image.png

5、await等待的不同处理

/**
 * await 等待的是一个非promise 1 2 3 4 5 6
 */
async function async1() {
    console.log(1);
    await async2()
    console.log(5);
}
function async2() {
    console.log(2);
}
async1();
new Promise(function (resolve) {
    console.log(3);
    resolve()
}).then(function () {
    console.log(6);
})
console.log(4);
/**
 * await等待的是一个promise 1 2 3 4 5 6
 */
async function async1() {
    console.log(1);
    await async2()
    console.log(6);
}
async function async2() {
    console.log(2);
    return new Promise((resolve, reject) => {
        resolve();
    })
}
async1();
new Promise(function (resolve) {
    console.log(3);
    resolve()
}).then(function () {
    console.log(5);
})
console.log(4);

image.png

6、setTimeout

console.log('script start')  //1. 打印 script start
setTimeout(function(){
    console.log('settimeout')  // 3. 打印 settimeout
})
console.log('script end')  //2. 打印 script start
// 输出顺序:script start->script end->settimeout

7、Promise

console.log('script start')
let promise1 = new Promise(function (resolve) {
    console.log('promise1')
    resolve()
    console.log('promise1 end')
}).then(function () {
    console.log('promise2')
})
setTimeout(function(){
    console.log('settimeout')
})
console.log('script end')
// 输出顺序: script start->promise1->promise1 end->script end->promise2->settimeout

8、async/await

async function async1(){
   console.log('async1 start');
    await async2();
    console.log('async1 end')
}
async function async2(){
    console.log('async2')
}
console.log('script start');
async1();
console.log('script end')
// 输出顺序:script start->async1 start->async2->script end->async1 end

三、灵魂3问

1、js为什么是单线程

想一个问题

我有2个线程,线程1,线程2,如果js是多线程,那么他可以同时对一个dom进行操作,线程1删除了dom,而线程2编辑dom,同时下达2个会矛盾的操作,会导致浏览器的崩溃。

2、为什么需要异步?

如果js没有异步,那只能依照顺序执行,如果上一段程序执行了很长时间,那么之后的代码就会阻塞,对于用户而言,阻塞就意味着页面的假死,会带来非常不好的用户体验。

3、单线程是怎么实现异步的?

单线程通过事件循环实现异步

js的事件循环,同步和异步

js的事件循环,宏任务和微任务

setTimeout(function(){
     console.log('定时器开始啦')
 });
 
 new Promise(function(resolve){
     console.log('马上执行for循环啦');
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log('执行then函数啦')
 });
 
 console.log('代码执行结束');

按照同步和异步的想法,应该输出:

【马上执行for循环啦 --- 代码执行结束 --- 定时器开始啦 --- 执行then函数啦】

但是实际的输出结果是:

【马上执行for循环啦 ---代码执行结束---执行then函数啦---定时器开始啦】

参考👀

什么是 Event Loop?

Excuse me?这个前端面试在搞事!

8张图让你一步步看清 async/await 和 promise 的执行顺序

10分钟理解JS引擎的执行机制

微任务、宏任务与Event-Loop