那些高级/资深的前端是如何回答JavaScript面试题的 (一)

本文详细探讨了JavaScript闭包的概念、原理和面试中的常见问题。通过讲解执行上下文、变量对象、作用域链以及闭包如何产生和影响变量回收,帮助读者深入理解闭包的本质。此外,还涵盖了Node.js模块机制、异步I/O、V8垃圾回收、Buffer模块、WebSocket、HTTPS、进程通信和中间件等前端进阶面试题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

开始做面试题系列文章。经验源自各大面市场,少部分别人面我,大部分我面别人

欢迎杠精们一起来开杠,会很有意思。直接进入正题。

Question 1: JS闭包,你了解多少?`

应该有面试官问过你:

  1. 什么是闭包?
  2. 闭包有哪些实际运用场景?
  3. 闭包是如何产生的?
  4. 闭包产生的变量如何被回收?

这些问题其实都可以被看作是同一个问题,那就是面试官在问你:你对JS闭包了解多少?

来总结一下我听到过的答案,尽量完全复原候选人面试的时候说的原话。

答案1: 就是一个function里面return了一个子函数,子函数访问了外面那个函数的变量。

答案2: for循环里面可以用闭包来解决问题。

for(var i = 0; i < 10; i++){
    setTimeout(()=>console.log(i),0)
}
// 控制台输出10遍10.
for(var i = 0; i < 10; i++){
    (function(a){
    setTimeout(()=>console.log(a),0)
    })(i)
}
 // 控制台输出0-9
复制代码

答案3: 当前作用域产产生了对父作用域的引用。

答案4: 不知道。是跟浏览器的垃圾回收机制有关吗?

开杠了。请问,小伙伴的答案和以上的内容有多少相似程度?

其实,拿着这些问题好好想想,你就会发现这些问题都只是为了最终那一个问题。

闭包的底层实现原理

1. JS执行上下文

我们都知道,我们手写的js代码是要经过浏览器V8进行预编译后才能真正的被执行。例如变量提升、函数提升。举个栗子。

// 栗子:
var d = 'abc';
function a(){
    console.log("函数a");
};
console.log(a);   // ƒ a(){ console.log("函数a"); }
a();		      // '函数a'
var a = "变量a";  
console.log(a);   // '变量a'
a();			  // a is not a function
var c = 123;
 
// 输出结果及顺序:
// ƒ a(){ console.log("函数a"); }
// '函数a'
// '变量a'
// a is not a function

// 栗子预编后相当于:
function a(){
    console.log("函数a");
};
var d;
console.log(a);  // ƒ a(){ console.log("函数a"); }
a(); 			 // '函数a'

a = "变量a";     // 此时变量a赋值,函数声明被覆盖

console.log(a); // "变量a"
a(); 		// a is not a function

复制代码

那么问题来了。 请问是谁来执行预编译操作的?那这个谁又是在哪里进行预编译的?

是的,你的疑惑没有错。js代码运行需要一个运行环境,那这个环境就是执行上下文。 是的,js运行前的预编译也是在这个环境中进行。

js执行上下文分三种:

  • 全局执行上下文: 代码开始执行时首先进入的环境。
  • 函数执行上下文:函数调用时,会开始执行函数中的代码。
  • eval执行上下文:不建议使用,可忽略。

那么,执行上下文的周期,分为两个阶段:

  • 创建阶段 创建词法环境 生成变量对象(VO),建立作用域链作用域链作用域链(重要的事说三遍) 确认this指向,并绑定this
  • 执行阶段。这个阶段进行变量赋值,函数引用及执行代码。

你现在猜猜看,预编译是发生在什么时候?

噢,我忘记说了,其实与编译还有另一个称呼:执行期上下文

预编译发生在函数执行之前。预编译四部曲为:

  1. 创建AO对象
  2. 找形参和变量声明,将变量和形参作为AO属性名,值为undefined
  3. 将实参和形参相统一
  4. 在函数体里找到函数声明,值赋予函数体。最后程序输出变量值的时候,就是从AO对象中拿。

所以,预编译真正的结果是:


var AO = {
    a = function a(){console.log("函数a");};
    d = 'abc'
}
复制代码

我们重新来。

1. 什么叫变量对象?

变量对象是 js 代码在进入执行上下文时,js 引擎在内存中建立的一个对象,用来存放当前执行环境中的变量。

2. 变量对象(VO)的创建过程

变量对象的创建,是在执行上下文创建阶段,依次经过以下三个过程:

  • 创建 arguments 对象。 对于函数执行环境,首先查询是否有传入的实参,如果有,则会将参数名是实参值组成的键值对放入arguments 对象中。否则,将参数名和 undefined组成的键值对放入 arguments 对象中。
//举个栗子 
function bar(a, b, c) {
    console.log(arguments);  // [1, 2]
    console.log(arguments[2]); // undefined
}
bar(1,2)
复制代码
  • 当遇到同名的函数时,后面的会覆盖前面的。
console.log(a); // function a() {console.log('Is a ?') }
function a() {
    console.log('Is a');
}
function a() {
  console.log('Is a ?')
}

/**
ps: 在执行第一行代码之前,函数声明已经创建完成.
后面的对之前的声明进行了覆盖。
**/
复制代码
  • 检查当前环境中的变量声明并赋值为undefined。当遇到同名的函数声明,为了避免函数被赋值为 undefined ,会忽略此声明
console.log(a); // function a() {console.log('Is a ?') }
console.log(b); // undefined
function a() {
  console.log('Is a ');
}
function a() {
console.log('Is a ?');
}
var b = 'Is b';
var a = 10086;

/**
这段代码执行一下,你会发现 a 打印结果仍旧是一个函数,而 b 则是 undefined。
**/
复制代码

根据以上三个步骤,对于变量提升也就知道是怎么回事了。

3. 变量对象变为活动对象

执行上下文的第二个阶段,称为执行阶段,在此时,会进行变量赋值,函数引用并执行其他代码,此时,变量对象变为活动对象。

我们还是举上面的例子:

console.log(a); // function a() {console.log('fjdsfs') }
console.log(b); // undefined
function a() {
   console.log('Is a');
}
function a() {
 console.log('Is a?');
}
var b = 'Is b';
console.log(b); // 'Is b'
var a = 10086; 
console.log(a);  // 10086
var b = 'Is b?';
console.log(b); // 'Is b?'
复制代码

在上面的代码中,代码真正开始执行是从第一行 console.log() 开始的,自这之前,执行上下文是这样的:

// 创建过程
EC= {
  VO: {}; // 创建变量对象
  scopeChain: {}; // 作用域链
}
VO = {
  argument: {...}; // 当前为全局上下文,所以这个属性值是空的
  a: <a reference> // 函数 a  的引用地址
  b: undefiend  // 见上文创建变量对象的第三步
}

复制代码

词法作用域(Lexical scope)

这里想说明,我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript的一个复杂之处在于它如何查找变量,如果在函数执行上下文中找不到变量,它将在调用上下文中寻找它,如果在它的调用上下文中没有找到,就一直往上一级,直到它在全局执行上下文中查找为止。(如果最后找不到,它就是 undefined)。

再来举个栗子:

 1: let top = 0; // 
 2: function createWarp() {
 3:   function add(a, b) {
 4:     let ret = a + b
 5:     return ret
 6:   }
 7:   return add
 8: }
 9: let sum = createWarp()
10: let result = sum(top, 8)
11: console.log('result:',result)


复制代码

分析过程如下:

  • 在全局上下文中声明变量top 并赋值为0.
  • 2 - 8行。在全局执行上下文中声明了一个名为 createWarp 的变量,并为其分配了一个函数定义。其中第3-7行描述了其函数定义,并将函数定义存储到那个变量(createWarp)中。
  • 第9行。我们在全局执行上下文中声明了一个名为 sum 的新变量,暂时,值为 undefined。
  • 第9行。遇到(),表明需要执行或调用一个函数。那么查找全局执行上下文的内存并查找名为 createWarp 的变量。 明显,已经在步骤2中创建完毕。接着,调用它。
  • 调用函数时,回到第2行。创建一个新的createWarp执行上下文。我们可以在 createWarp 的执行上下文中创建自有变量。js 引擎createWarp 的上下文添加到调用堆栈(call stack)。因为这个函数没有参数,直接跳到它的主体部分.
  • 3 - 6 行。我们有一个新的函数声明,在createWarp执行上下文中创建一个变量 add。add 只存在于 createWarp 执行上下文中, 其函数定义存储在名为 add 的自有变量中。
  • 第7行,我们返回变量 add 的内容。js引擎查找一个名为 add 的变量并找到它. 第4行和第5行括号之间的内容构成该函数定义。
  • createWarp 调用完毕,createWarp 执行上下文将被销毁。add 变量也跟着被销毁。 但 add 函数定义仍然存在,因为它返回并赋值给了 sum 变量。 (ps: 这才是闭包产生的变量存于内存当中的真相)
  • 接下来就是简单的执行过程,不再赘述。。
  • ……
  • 代码执行完毕,全局执行上下文被销毁。sum 和 result 也跟着被销毁。

小结一下

现在,如果再让你回答什么是闭包,你能答出多少?

其实,大家说的都对。不管是函数返回一个函数,还是产生了外部作用域的引用,都是有道理的。

所以,什么是闭包?

  • 解释一下作用域链是如何产生的。
  • 解释一下js执行上下文的创建、执行过程。
  • 解释一下闭包所产生的变量放在哪了。
  • 最后请把以上3点结合起来说给面试官听。

另外,假如被问到Event loop、执行栈(EC Stack)、调用栈(Call Stack),请你一定要明确一件事情,执行栈和调用栈它们不是一个东西。 很多文章都不写清楚两者的区别,或者干脆就说他们就是一个东西。后面有时间,为会专门文它们写一篇文章为大家解惑

祝,君无往不利。

作者:在剥我的壳

原文链接:
https://juejin.cn/post/6971727286856843295

前端进阶61道大厂面试题

Node 模块机制

1、请介绍一下 node 里面的模块是什么

2 、请介绍一下 require 的模块加载机制

3、加载模块时,为什么每个模块都有__dirname,__filename 属性呢,new Module 的时候我们看到 1.1 部分没有这两个属性的,那么这两个属性是从哪里来的

4、我们知道 node 导出模块有两种方式,一种是 exports.xxx=xxx 和 Module.exports={}有什么区别吗

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

Node 的异步 I/O

1、 请介绍一下 Node 事件循环的流程

2 、在每个 tick 的过程中,如何判断是否有事件需要处理呢?

3 、请描述一下整个异步 I/O 的流程

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

V8 的垃圾回收机制

1、如何查看 V8 内存使用情况

2、V8 的内存限制是多少,为什么 V8 这样设计

3、V8 的内存分代和回收算法请简单讲一讲

新生代

老生代

标记清楚算法的问题

哪些情况会造成 V8 无法立即回收内存

请谈一下内存泄漏是什么,以及常见内存泄漏的原因,和排查的方法

Buffer 模块

1、新建 Buffer 会占用 V8 分配的内存吗

2、Buffer.alloc 和 Buffer.allocUnsafe 的区别

3、Buffer 的内存分配机制

4、Buffer 乱码问题

webSocket

1、webSocket 与传统的 http 有什么优势

2、webSocket 协议升级是什么,能简述一下吗?

https

1、https 用哪些端口进行通信,这些端口分别有什么用

2、身份验证过程中会涉及到密钥, 对称加密,非对称加密,摘要的概念,请解释一下

3、为什么需要 CA 机构对证书签名

4、https 验证身份也就是 TSL/SSL 身份验证的过程

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

进程通信

1、请简述一下 node 的多进程架构

2、请问创建子进程的方法有哪些,简单说一下它们的区别

3、请问你知道 spawn 在创建子进程的时候,第三个参数有一个 stdio 选项吗,这个选项的作用是什么,默认的值是什么。

4、请问实现一个 node 子进程被杀死,然后自动重启代码的思路

5、在 7.4 在此基础上,实现限量重启,比如我最多让其在 1 分钟内重启 5 次,超过了就报警给运维

6、如何实现进程间的状态共享,或者数据共享

中间件

如果使用过 koa、egg 这两个 Node 框架,请简述其中的中间件原理,最好用代码表示一下

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

如何获取?

转发这篇文章

关注我:私信回复“前端”即可免费领取此份文档

一:

整理了一份2021最新

阿里前端工程师面试手册

手册目录(内容特别全,包含答案解析)

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

那些高级/资深的前端是如何回答JavaScript面试题的 (一)

 

总共170页,2021最新前端面试题含答案解析,查看下方免费获取

添加小助手V:qianduan686免费获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值