JavaScript面试里,价值最高的往往不是冷门API,而是把闭包和执行上下文放在明面上,却依旧能让一大批候选人当场翻车的题目。下面两道代码陷阱,一道测试你对过期闭包的警觉,一道撕开你对绑定规则的理解破洞,逐一拆解之后,你会看清自己之前“用得对但说不清”的认知盲区到底有多大。
一、过期闭包:你以为变量在联动,其实值早已凝固
先看工厂函数:返回两个内部方法,一个递增计数器,另一个打印信息。连续两次调用递增后再执行打印,几乎所有人第一反应是“Count is 2”。而实际输出却是Count is 0。陷阱并不在于闭包没形成,而在于你忽略了变量里装的是原始值,不是表达式。
变量绑定没错,但字符串快照不会再变
调用createIncrement()时,引擎立刻建立词法环境,count初始为0,而message通过模板字面量当场求值,变成字符串"Count is 0"。increment和log两个内部函数虽然闭包住了同一个环境,但increment操作的是可变绑定count,而log读取的是早已凝固的message。两次递增后count确实从0变成1再变2,但message从未重新求值,始终是赋值那一刻的快照。
闭包只捕获变量引用,不捕获表达式
很多人的直觉是“message里用了count,就该跟着联动”。事实是,闭包只会捕获变量引用,模板字面量只是语法糖,它在赋值时就会被一次性求值为原始值。字符串一旦生成就和当时的值绑死,后续变化再也不会自动反映进来。换成对象或getter,行为才会截然不同;题目偏偏用string,考察的正是你对这个冷硬特性的警觉。
想要动态输出,就在打印时重新求值
修复方式很简单:把console.log(message)改成console.log(\`Count is ${count}\`)。让模板字面量在每次调用log时重新计算,它才会去读当前环境的count值。不改也行,但你得承认,当初那个message存在的唯一意义,就是充当一份活生生的过期样本。
二、上下文丢失:同一个方法体内的三种绑定面孔
第二段代码把对象方法、普通函数和箭头函数塞在同一个greet方法里。调用user.greet()后,输出为Hello, Alex!、Normal: undefined、Arrow: Alex。结果一出,不少写了三五年JavaScript的人也得在心里重新梳理绑定的优先级。
方法调用:只看调用点前面的那个“点”
greet()以user.greet()的形式执行,JavaScript的规则简单直接:谁点出来的方法,运行时的上下文就是谁。于是this.name顺利拿到"Alex"。一旦换成const fn = user.greet; fn()这种剥离调用,输出立刻变脸——这是动态绑定的基线,也是最熟悉的第一张面孔。
普通函数孤立调用时没有归属
内部声明的innerNormal虽然是function定义,但它在执行时前面没有任何对象引用,属于孤立调用。在严格模式(模块和class都默认严格)下,函数内部的上下文就是undefined,访问undefined.name自然得到undefined。非严格环境下虽然会回退到全局对象,但全局的name默认为空字符串,同样拿不到目标值,这就是“运行时取决调用方式”带来的直接后果。
箭头函数不绑定自己的上下文,只认出生时的外层作用域
innerArrow是箭头函数,自身不存在上下文绑定。它会在定义时直接从外层greet的作用域里拷贝当前的上下文,就像给当时的环境拍了一张静态快照。外层调用时环境指向user,于是箭头函数内部也跟着指向user,老老实实打出Alex。这道题最锋利的地方在于,同一个方法体内,三种绑定逻辑各走各的路,稍不留神就会混为一谈。
三、这两道题到底在测量什么
表面看,一题考过期闭包,一题考动态绑定;往深一层,它们都在逼你区分“变量绑定”和“值”,区分“定义时的环境”和“调用时的上下文”。
很多人背熟了闭包延长生命周期、箭头函数不改变指向这些结论,却没真正理解底层机制。一旦题目把多个特性压进同一个执行空间,先前靠记忆维持的“正确感”就会瞬间崩塌。大厂面试正是用这种看似简单的代码,快速筛选出能穿透现象、抓住内存模型的候选人。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.