【前端面试题】变量提升、闭包、promise

飞书面试

题目1:

async function foo() {
  console.log('foo start');
  await bar();
  console.log('foo end');
}

async function bar() {
  console.log('bar start');
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('bar promise');
      resolve();
    }, 1000);
  });
}

console.log('script start');
foo();
setTimeout(() => {
  console.log('settimeout');
}, 0);

分析:
首先打印script start。
然后调用foo函数,在foo函数内部先打印foo start,接着等待bar函数。
在bar函数内部,先打印bar start,然后返回一个 1 秒后resolve的Promise。
1 秒后,Promise解决,打印bar promise,然后回到foo函数,打印foo end。
最后执行setTimeout中的函数,打印settimeout。

script start
foo start
bar start
bar promise
foo end
settimeout

以下是几道包含 awaitasync 以及 new 立即执行相关内容,考查执行顺序的题目及详细解析:

题目 1:

async function asyncFunc1() {
    console.log('asyncFunc1 start');
    const result = await new Promise((resolve) => {
        console.log('Inside new Promise of asyncFunc1');
        resolve(10);
    });
    console.log('asyncFunc1 got result:', result);
    console.log('asyncFunc1 end');
}

async function asyncFunc2() {
    console.log('asyncFunc2 start');
    const innerPromise = new Promise((resolve) => {
        console.log('Inside new Promise of asyncFunc2');
        setTimeout(() => {
            resolve(20);
            console.log('Timeout resolved in asyncFunc2');
        }, 500);
    });
    const result = await innerPromise;
    console.log('asyncFunc2 got result:', result);
    console.log('asyncFunc2 end');
}

console.log('Script start');
asyncFunc1();
asyncFunc2();
console.log('Script end');
解析:
  1. 整体执行开始
    首先会执行同步代码,遇到 console.log('Script start');,会直接打印出 Script start

  2. 调用 asyncFunc1 函数

    • 进入 asyncFunc1 函数后,先打印 asyncFunc1 start,这是函数内的第一个同步代码执行的打印。
    • 接着遇到 await 关键字以及后面跟着的 new Promisenew Promise 的构造函数是同步执行的,所以会立即打印 Inside new Promise of asyncFunc1,然后这个 Promiseresolve,但是由于 await 的存在,函数在此处暂停执行,等待 Promise 完成(其实这里已经完成了,因为是立即 resolve 的),把执行权交回主程序。
  3. 调用 asyncFunc2 函数

    • 进入 asyncFunc2 函数后,先打印 asyncFunc2 start,接着执行 new Promise 的构造函数,这是同步操作,会打印 Inside new Promise of asyncFunc2。不过这个 Promise 内部设置了一个 setTimeoutsetTimeout 是异步的,会在 500 毫秒后执行 resolve,所以当前 asyncFunc2 函数执行到 await 处也暂停了,把执行权交回主程序。
  4. 继续主程序的同步代码
    会执行到 console.log('Script end');,打印出 Script end

  5. 异步操作完成后继续执行

    • 此时主程序同步代码执行完毕,开始处理异步队列中的任务。
    • 对于 asyncFunc1,之前 awaitPromise 已经 resolve,继续执行后面的代码,打印 asyncFunc1 got result: 10asyncFunc1 end
    • 对于 asyncFunc2,500 毫秒后 setTimeout 里的 resolve 执行,然后继续执行后面代码,打印 Timeout resolved in asyncFunc2asyncFunc2 got result: 20asyncFunc2 end

预期输出顺序

Script start
asyncFunc1 start
Inside new Promise of asyncFunc1
asyncFunc2 start
Inside new Promise of asyncFunc2
Script end
asyncFunc1 got result: 10
asyncFunc1 end
Timeout resolved in asyncFunc2
asyncFunc2 got result: 20
asyncFunc2 end

题目 2:

async function firstAsync() {
    console.log('First async start');
    const result1 = await new Promise((resolve) => {
        console.log('Promise in firstAsync created');
        setTimeout(() => {
            resolve(5);
            console.log('Timeout resolved in firstAsync');
        }, 300);
    });
    console.log('First async got result:', result1);
    console.log('First async end');
}

async function secondAsync() {
    console.log('Second async start');
    const result2 = await new Promise((resolve) => {
        console.log('Promise in secondAsync created');
        resolve(10);
    });
    console.log('Second async got result:', result2);
    console.log('Second async end');
}

console.log('Main script start');
firstAsync();
secondAsync();
console.log('Main script end');
解析:
  1. 开始执行
    先执行同步代码,遇到 console.log('Main script start');,打印 Main script start

  2. 调用 firstAsync 函数

    • 进入 firstAsync 函数,打印 First async start,接着遇到 await 及后面的 new Promisenew Promise 的构造函数同步执行,打印 Promise in firstAsync created,但里面的 setTimeout 是异步的,300 毫秒后才会 resolve,所以函数执行到 await 处暂停,执行权交回主程序。
  3. 调用 secondAsync 函数

    • 进入 secondAsync 函数,打印 Second async start,然后执行 new Promise 的构造函数,打印 Promise in secondAsync created,此 Promise 立即 resolve,不过由于 await,会暂停在此处等待 Promise 完成(实际已经完成),执行权交回主程序。
  4. 继续主程序同步代码
    执行 console.log('Main script end');,打印 Main script end

  5. 异步操作完成后继续执行

    • 对于 secondAsync,之前 awaitPromise 已完成,继续执行后面代码,打印 Second async got result: 10Second async end
    • 300 毫秒后,firstAsyncsetTimeoutresolve 执行,然后继续执行后面代码,打印 Timeout resolved in firstAsyncFirst async got result: 5First async end

预期输出顺序

Main script start
First async start
Promise in firstAsync created
Second async start
Promise in secondAsync created
Main script end
Second async got result: 10
Second async end
Timeout resolved in firstAsync
First async got result: 5
First async end

题目 3:

async function outerAsync() {
    console.log('Outer async start');
    const innerPromise = new Promise((resolve) => {
        console.log('Inner promise created');
        const innerAsync = async () => {
            console.log('Inner async start');
            const result = await new Promise((innerResolve) => {
                console.log('Inner inner promise created');
                innerResolve(15);
            });
            console.log('Inner async got result:', result);
            console.log('Inner async end');
            resolve(result);
        };
        innerAsync();
    });
    const outerResult = await innerPromise;
    console.log('Outer async got result:', outerResult);
    console.log('Outer async end');
}

console.log('Script begins');
outerAsync();
console.log('Script continues');
解析:
  1. 开始执行
    执行同步代码,打印 Script begins

  2. 调用 outerAsync 函数

    • 进入 outerAsync 函数,打印 Outer async start
    • 接着执行 new Promise 的构造函数,打印 Inner promise created
    • 然后定义并调用 innerAsync 函数,进入 innerAsync 函数后,打印 Inner async start,再遇到 await 及后面的 new Promise,这个 new Promise 的构造函数同步执行,打印 Inner inner promise created,然后立即 resolve,由于 await,暂停在这等待 Promise 完成(实际已完成),执行权交回 outerAsync 函数中 new Promise 的代码处(也就是 innerAsync 函数外面那层 Promise)。
  3. 继续 outerAsync 函数
    此时 innerAsync 函数里的 await 暂停了,但 outerAsync 函数里 new Promise 中定义的 innerAsync 函数已经执行了,接着执行 resolve(result)result 就是 innerAsyncawait 得到的值 15),然后由于 outerAsync 函数里也有 await 等待这个 new Promise,会暂停在这,执行权交回主程序。

  4. 继续主程序同步代码
    执行 console.log('Script continues');,打印 Script continues

  5. 异步操作完成后继续执行

    • 此时主程序同步代码执行完了,开始处理异步队列。outerAsync 函数里 awaitnew Promise 已经 resolve,继续执行后面代码,打印 Outer async got result: 15Outer async end

预期输出顺序

Script begins
Outer async start
Inner promise created
Inner async start
Inner inner promise created
Script continues
Inner async got result: 15
Inner async end
Outer async got result: 15
Outer async end

这些题目涵盖了 asyncawaitnew Promise(包括立即执行 resolve 和带异步操作如 setTimeout 后执行 resolve 的情况)之间的交互以及执行顺序的考查,希望能帮助你加深对这部分知识的理解和掌握。

当然可以,以下是原始代码和将 var 改为 let 后的代码的对比,以及它们的输出结果和解释。

var提升/闭包

var result = [];
var a = 3;
var total = 0;

function foo(a) {
   var i = 0;
   for (; i < 3; i++) {
       result[i] = function() {
           total += i * a;
           console.log(total);
       };
   }
}

foo(1);
result[0]();
result[1]();
result[2]();

输出结果:

3
6
9

解释:

  • var 声明的 i 是函数作用域的,所以在循环结束后,i 的值为 3。
  • 每个闭包都捕获了同一个 i 变量,因此当这些闭包被调用时,它们都使用 i 的最终值 3。
  • a 的值在 foo 函数调用时被设置为 1,并且由于 var 声明的变量是函数作用域的,所以所有闭包都使用这个值。

修改后的代码(使用 let,仅修改 i

var result = [];
var a = 3;
var total = 0;

function foo(a) {
    for (let i = 0; i < 3; i++) {
        result[i] = function() {
            total += i * a;
            console.log(total);
        };
    }
}

foo(1);
result[0]();
result[1]();
result[2]();

输出结果:

0
1
3

解释:

  • let 声明的 i 是块作用域的,每个循环迭代都创建了一个新的 i 变量。
  • 每个闭包捕获了其对应迭代的 i 值,因此当这些闭包被调用时,它们分别使用 0、1 和 2。
  • a 的值在 foo 函数调用时被设置为 1,并且由于 var 声明的变量是函数作用域的,所以所有闭包都使用这个值。

全部使用 let 的代码

let result = [];
let a = 3;
let total = 0;

function foo(a) {
    for (let i = 0; i < 3; i++) {
        result[i] = function() {
            total += i * a;
            console.log(total);
        };
    }
}

foo(1);
result[0]();
result[1]();
result[2]();

输出结果:

0
1
3

解释:

  • 与上一个修改后的代码相同,因为 let 声明的 i 是块作用域的,每个闭包捕获了其对应迭代的 i 值。
  • atotal 也是块作用域的,但由于它们在全局作用域中声明,它们的行为与之前使用 var 时相同。

全部使用 let,包括 foo 函数内部的 a

let result = [];
let a = 3;
let total = 0;

function foo() {
    let a = 1; // 这个 'a' 只在 foo 函数内部可见
    for (let i = 0; i < 3; i++) {
        result[i] = function() {
            total += i * a; // 这里使用的是函数内部的 'a'
            console.log(total);
        };
    }
}

foo();
result[0]();
result[1]();
result[2]();

输出结果:

0
1
3

解释:

  • foo 函数内部的 a 被设置为 1,并且由于 let 声明的变量是块作用域的,所以所有闭包都使用这个值。
  • 每个闭包捕获了其对应迭代的 i 值,因此当这些闭包被调用时,它们分别使用 0、1 和 2。

当然,这里是每个练习题的答案和解释:

补充些类似的题目:

题目 1

for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 100 * i);
}

答案: 这段代码会输出 3 3 3

解释: 在这个例子中,var 声明的变量 i 是函数作用域的。当 setTimeout 函数被调用时,它们会将当前的 i 值(在循环结束后为 3)捕获到闭包中。由于所有的 setTimeout 回调都共享同一个 i 变量,它们都打印出循环结束后的 i 值,即 3

题目 2

for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 100 * i);
}

答案: 这段代码会输出 0 1 2

解释: 使用 let 声明的变量 i 是块作用域的,这意味着每次循环迭代都会创建一个新的 i 变量。因此,每个 setTimeout 回调都捕获了其对应迭代的 i 值。当这些回调被执行时,它们打印出各自捕获的 i 值,即 012

题目 3

function createFunctions() {
    var funcs = [];
    for (var i = 0; i < 3; i++) {
        funcs[i] = function() {
            return i * i;
        };
    }
    return funcs;
}

var funcs = createFunctions();
console.log(funcs[0]()); // ?
console.log(funcs[1]()); // ?
console.log(funcs[2]()); // ?

答案: 这段代码会输出 9 9 9

解释: 与题目 1 类似,var 声明的变量 i 是函数作用域的。当这些函数被创建时,它们都捕获了同一个 i 变量。当这些函数被调用时,循环已经结束,i 的值为 3。因此,每个函数都返回 3 * 3,即 9

题目 4

function createFunctions() {
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function() {
            return i * i;
        };
    }
    return funcs;
}

var funcs = createFunctions();
console.log(funcs[0]()); // ?
console.log(funcs[1]()); // ?
console.log(funcs[2]()); // ?

答案: 这段代码会输出 0 1 4

解释: 使用 let 声明的变量 i 是块作用域的,每个循环迭代都创建了一个新的 i 变量。每个闭包都捕获了其对应迭代的 i 值。因此,第一个函数返回 0 * 0,即 0;第二个函数返回 1 * 1,即 1;第三个函数返回 2 * 2,即 4

题目 5

function getClosure() {
    var a = 10;
    function closure() {
        return a;
    }
    return closure;
}

var myClosure = getClosure();
console.log(myClosure()); // ?

答案: 这段代码会输出 10

解释:getClosure 函数中,var 声明的变量 a 是函数作用域的。闭包 closure 捕获了 a 的值。当 myClosure 被调用时,它返回 a 的值,即 10

题目 6

function getClosureWithLet() {
    let b = 20;
    function closure() {
        return b;
    }
    return closure;
}

var myClosure = getClosureWithLet();
console.log(myClosure()); // ?

答案: 这段代码会输出 20

解释:getClosureWithLet 函数中,let 声明的变量 b 是块作用域的。闭包 closure 捕获了 b 的值。当 myClosure 被调用时,它返回 b 的值,即 20。这与使用 var 的情况类似,因为 b 的作用域是 getClosureWithLet 函数内部,闭包能够访问它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值