【前端面试题】变量提升、闭包、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 函数内部,闭包能够访问它。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/889575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Mysql】索引下推、索引合并详解

文章目录 1. 索引下推&#xff08;Index Condition Pushdown, ICP&#xff09;定义工作机制实现过程优化的典型场景 2. 索引合并&#xff08;Index Merge&#xff09;定义索引合并方式使用限制 3. 对比与应用场景选用建议 这篇文章就简单的给大家介绍下索引下推、索引合并 1. 索…

智能时代的基石:神经网络

智能时代的基石&#xff1a;神经网络 第一节&#xff1a;神经网络简介 课程目标 本节课程旨在全面介绍神经网络的基本概念、结构以及其在历史发展中的重要里程碑。通过深入理解神经网络的工作原理和演变过程&#xff0c;学员将能够掌握神经网络在现实世界中的多种应用&#…

8_Sass 颜色函数 --[CSS预处理]

Sass 提供了一系列的颜色函数&#xff0c;允许开发者在 CSS 中动态地创建和操作颜色。这些函数可以用于生成调色板、调整颜色的亮度或饱和度、混合颜色等&#xff0c;从而提高样式表的灵活性和可维护性。以下是 Sass 中一些常用的颜色函数及其用法示例&#xff1a; 1. adjust-…

java和go语言的优劣

Java 和 Go 是两种非常流行的编程语言&#xff0c;各自拥有独特的特性和优势。它们在不同的应用场景中表现出色&#xff0c;选择哪种语言取决于具体的项目需求、团队技能以及个人偏好。下面我们将从多个维度对比 Java 和 Go 的优劣&#xff0c;帮助您更好地理解这两种语言的特点…

【力扣算法】234.回文链表

快慢指针&#xff1a;一个指针走两步&#xff0c;一个指针走一步&#xff0c;当快指针走到链表末尾时&#xff0c;慢指针走到中间位置。 逆转链表&#xff1a;根据指针位置分成两个表&#xff0c;逆转第二个表。 按序判断就可以&#xff0c;如果是相同就是回文&#xff0c;反之…

ThreadLocal内存泄漏数据脏读问题

ThreadLocal是Java中的一个类&#xff0c;用于解决多线程环境下的并发问题。以下是对ThreadLocal的详细解释&#xff1a; 定义&#xff1a; ThreadLocal&#xff0c;即线程局部变量&#xff0c;是Java提供的一种线程隔离的变量存储机制。每个线程都会有一个独立的ThreadLocal变…

工业大数据分析算法实战-day04

文章目录 day04统计分析概率分布参数估计假设检验 统计分布拟合1.基于核函数的非参数方法2. 单概率分布的参数化拟合3. 混合概率分布估计 线性回归模型1. OLS模型&#xff08;普通最小二乘法&#xff09;2. OLS模型检验3. 鲁棒线性回归4. 结构复杂度惩罚&#xff08;正则化&…

vue3-tp8-Element:对话框实现

效果 参考框架 Dialog 对话框 | Element Plus 具体实现 一、建立view页面 /src/views/TestView.vue 二、将路径写入路由 /src/router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vueconst router create…

VMware ESXi上创建Ubuntu虚拟机并实现远程SSH访问全攻略

文章目录 前言1. 在VMware ESXI中创建Ubuntu虚拟机2. Ubuntu开启SSH远程服务3. 安装Cpolar工具4. 使用SSH客户端远程访问Ubuntu5. 固定TCP公网地址 前言 本文主要介绍如何在VMware ESXi上创建一台Ubuntu 22.04虚拟机&#xff0c;并通过Cpolar内网穿透工具配置公网地址&#xf…

HQChart使用教程30-K线图如何对接第3方数据42-DRAWTEXTREL,DRAWTEXTABS数据结构

HQChart使用教程30-K线图如何对接第3方数据42-DRAWTEXTREL,DRAWTEXTABS数据结构 效果图DRAWTEXTREL示例数据结构说明nametypecolorDrawVAlignDrawAlignDrawDrawTypeDrawDataFont DRAWTEXTABS示例数据结构说明nametypecolorDrawVAlignDrawAlignDrawDrawTypeDrawDataFont 效果图 …

【电子元器件】电感基础知识

本文章是笔者整理的备忘笔记。希望在帮助自己温习避免遗忘的同时&#xff0c;也能帮助其他需要参考的朋友。如有谬误&#xff0c;欢迎大家进行指正。 一、 电感的基本工作原理 1. 电感的基本工作原理如下&#xff1a; &#xff08;1&#xff09; 当线圈中有电流通过时&#…

ES动态索引——日志es索引数据按月份存储

一、定义ES索引 NoArgsConstructor AllArgsConstructor Data Accessors(chain true) Document(indexName "charge_pile_log" Constants.ES_TIME_DYNAMIC_INDEX) //(索引名称动态&#xff0c;前面固定&#xff0c;后面月份) public class ChargePileLogESDomain {…

大模型的文件有哪些?

在大模型仓库&#xff08;如Hugging Face&#xff09;中&#xff0c;例如&#xff1a;https://modelscope.cn/models/ZhipuAI/glm-4-9b-chat/files&#xff0c;通常会发现以下几类文件&#xff1a; 模型权重文件&#xff1a;存储训练好的模型参数&#xff0c;是模型推理和微调…

Python pyinstaller图形化打包工具

Python pyinstaller图形化打包工具 1.简介&#xff1a; 一个使用Python PYQT5制作的关于pyinstaller打包工具&#xff0c;代替传统的cmd黑窗口模式打包页面&#xff0c;实现更快捷方便的python打包体验。资源已打包&#xff0c;大家可自行下载。 相关功能&#xff1a; 识别…

canal安装使用

简介 canal [kənl]&#xff0c;译意为水道/管道/沟渠&#xff0c;主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据订阅和消费 工作原理 canal 模拟 MySQL slave 的交互协议&#xff0c;伪装自己为 MySQL slave &#xff0c;向 MySQL master 发送 dump 协议…

专业140+总分400+北京理工大学826信号处理导论考研经验北理工电子信息与通信工程,真题,大纲,参考书。

考研总分400&#xff0c;专业826信号处理导论&#xff08;信号与系统和dsp&#xff09;140&#xff0c;成功上岸北理工&#xff0c;虽然已经一段时间&#xff0c;但是后劲很大&#xff0c;每每回想还是昨日事&#xff0c;群里同学多次要求分享自己的一些经验&#xff0c;感谢大…

pdb调试器详解

文章目录 1. 启动 pdb 调试器1.1 在代码中插入断点1.2 使用命令行直接调试脚本 2. 常用调试命令2.1 基本命令2.2 高级命令2.3 断点操作 3. 调试过程示例4. 调试技巧4.1 条件断点4.2 自动启用调试4.2.1 运行程序时指定 -m pdb4.2.2在代码中启用 pdb.post_mortem4.2.3 使用 sys.e…

基于Spring Boot的同城宠物照看系统的设计与实现

一、摘要 在快节奏的现代生活中&#xff0c;宠物已成为许多家庭不可或缺的一部分。然而&#xff0c;宠物照看服务的需求也随之增长。为了满足这一需求&#xff0c;我们设计并实现了一款同城宠物照看系统&#xff0c;该系统利用Java技术和MySQL数据库&#xff0c;为用户提供一个…

PHP Cookie

Cookie 是什么&#xff1f; cookie 常用于识别用户。cookie 是一种服务器留在用户计算机上的小文件。每当同一台计算机通过浏览器请求页面时&#xff0c;这台计算机将会发送 cookie。通过 PHP&#xff0c;您能够创建并取回 cookie 的值。 如何创建 Cookie&#xff1f; setcoo…

【Qt】QWidget中的常见属性及其作用(一)

目录 一、 enabled 例子&#xff1a; 二、geometry 例子&#xff1a; window fram 例子 &#xff1a; 四、windowTiltle 五、windowIcon 例子&#xff1a; qrc机制 创建qrc文件 例子&#xff1a; qt中的很多内置类都是继承自QWidget的&#xff0c;因此熟悉QWidget的…