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.