摘要:最近在一個前端學習群里,有人拋出了這么一道面試題。以下表示形式的是函數表達式,有多種形式。函數聲明式的函數名是可修改的。重新聲明變量通過上面的分析解釋,希望你可以掌握這道面試題,舉一反三。原文鏈接理解一道面試題
最近在一個前端學習群里,有人拋出了這么一道 JS 面試題。
var foo = 1; (function foo(){ foo = 100; console.log(foo); }()) console.log(foo);
我一看,這不很簡單嗎?IIFE 局部的 foo 本來指向函數本身,但后來被修改成 100 了,所以局部的 foo 打印 100。全局的 foo 還是保留原來的值,所以全局的 foo 打印 1。
然后我復制代碼到控制臺運行,發現先打印函數體 foo(){...},然后再打印 1。
我猜想的第一個打印結果錯了,一番查找資料終于搞懂了,于是有了這篇文章。
函數聲明式 vs 函數表達式以下表示形式的是函數聲明式,簡單說就是 function 前面沒有任何運算符,其實就下面一種形式。
function name() { ... }
以下表示形式的是函數表達式,有多種形式。
var fun = function name() { ... } // 函數前帶有 + - * / () && || 等運算符號 (function name(){ ... }()) // 又或者 +function name(){ ... }()
能認出函數聲明式與函數表達式后,我們來看看兩者有什么區別。
函數提升稍微了解 JS 的都知道變量提升(variable hoisting),除此之外還有函數提升(function hoisting),也就是說下面的代碼是正常運行的。
foo(); // running function foo() { console.log("running"); }
但是函數提升只對函數聲明式有效,對函數表達式不生效,下面的代碼就會報錯。
foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log("running"); }
區別一:函數聲明式會提升函數定義,而函數表達式不提升函數定義。這一區別只是想給大家復習知識點,并不是本文的重點。
函數名綁定的作用域先看看下面函數的表示形式,記住它有助于接下來的說明。
function BindingIdentifier (FormalParameters) { FunctionBody }
函數聲明式和函數表達式的另外一個關鍵區別是,看函數名(BindingIdentifier)綁定到哪個作用域下。
先看下 ECMAScript 是怎么描述這一區別的。
The BindingIdentifier in a FunctionExpression can be referenced from inside the FunctionExpression"s FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the BindingIdentifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.
上面說 BindingIdentifier(函數的引用) 可以用于在函數表達式內遞歸調用自身。而且函數表達式的 BindingIdentifier 只綁定在該函數內部,不污染外部的作用域,外部作用域也無法訪問到 BindingIdentifier。
區別二:函數聲明式的 BindingIdentifier 綁定在聲明時的作用域下,函數表達式的 BindingIdentifier 綁定在函數內部的作用域下。
背后的原因說了這么多,好像還沒說的真正的原因。是的,前面的內容只是鋪墊,有了上面的內容,才能更好理解背后的原因。
解釋前先說原因:
函數表達式的函數名是不可修改的(ImmutableBinding)。但如果你真修改了,在非嚴格模式下會靜默失敗,在嚴格模式下會報錯(Uncaught TypeError: Assignment to constant variable)。
函數聲明式的函數名是可修改的(MutableBinding)。
原因出自《You-Dont-Know-JS》的一個 issue,這一 issue 已被作者納入第二版(second edition)的編寫中。
The production FunctionExpression : function Identifier ( FormalParameterListopt ) { FunctionBody } is evaluated as follows: ... Call the CreateImmutableBinding concrete method of envRec passing the String value of Identifier as the argument. ...
調用 CreateImmutableBinding 創建 Immutable"s 函數名。
For each FunctionDeclaration f in code, in source text order do ... If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments. ...
調用 CreateMutableBinding 創建 Mutable"s 函數名。
當然也可以從 ECMAScript 規范中找到原因:Runtime Semantics: Evaluation。
至于語言為什么要這么規定,我也沒想明白,如果有知道的同學可以分享一下。
分析下代碼那回頭再分析下一開始的示例,從每一行注釋可以幫助理解背后的原因。
var foo = 1; // 在外部作用域聲明foo=1 // IIFE是典型的函數表達式 (function foo(){ // 函數名foo,引用函數自身,綁定在函數內部,不污染外部作用域 foo = 100; // 這里修改了foo,但規范規定不能修改,但不會報錯 console.log(foo); // 還是引用函數自身 }()) console.log(foo); // 外部作用域一直是1
同樣的代碼,當函數運行在嚴格模式下,報錯提示說:“不能賦值給常量”。也就是說函數表達式的函數名被定義成常量,無法再修改了。
var foo = 1; (function foo(){ "use strict"; // 嚴格模式 foo = 100; // Uncaught TypeError: Assignment to constant variable console.log(foo); }()) console.log(foo);
為了幫助對比理解,下面給出了函數聲明式的示例及解釋,下面的代碼無論在非嚴格模式還是嚴格模式下都打印100,也就是說函數聲明式的函數名可以被修改。
// foo是函數聲明式 function foo(){ // 函數名foo,引用函數自身,綁定在聲明時的作用域下 foo = 100; // 修改了foo,函數聲明式內可以重新修改函數名 console.log(foo); // 100 } foo();
如果在函數表達式內使用 var foo = 100; 來重新聲明變量,那這個變量就不是不可修改的(ImmutableBinding),所以內部的 foo 打印 100。
var foo = 1; (function foo(){ var foo = 100; // 重新聲明變量 console.log(foo); // 100 }()) console.log(foo); // 1
通過上面的分析解釋,希望你可以掌握這道面試題,舉一反三。
如果你喜歡這篇文章,請關注我,我會持續輸出更多原創且高質量的內容。
原文鏈接:【理解】一道 JS 面試題
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/103665.html
摘要:項目組長給我看了一道面試別人的面試題。打鐵趁熱,再來一道題來加深下理解。作者以樂之名本文原創,有不當的地方歡迎指出。 showImg(https://segmentfault.com/img/bVbur0z?w=600&h=400); 剛入職新公司,屬于公司萌新一枚,一天下午對著屏幕看代碼架構時。BI項目組長給我看了一道面試別人的JS面試題。 雖然答對了,但把理由說錯了,照樣不及格。 ...
摘要:今天看見一道面試題答案是多少答案是對方法不太了解就去搜了一下,里面也包含了對這道面試題的詳解。方法返回一個由原數組中的每個元素調用一個指定方法后返回值組成的新數組。使用方法處理數組時,數組元素的范圍在方法第一次調用之前就已經確定了。 今天看見一道面試題:[1,2,3].map(parseInt)答案是多少?答案是[1,NaN,NaN] 對map()方法不太了解就去搜了一下:Array....
摘要:想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出個,可是你真的懂為什么嗎為什么是輸出為什么是輸出個這兩個問題在我腦邊縈繞。同步任務都好理解,一個執行完執行下一個。本文只是我對這道面試題的一點思考,有誤的地方望批評指正。 想必面試題刷的多的同學對下面這道題目不陌生,能夠立即回答出輸出10個10,可是你真的懂為什么嗎?為什么是輸出10?為什么是輸出10個10?這兩個問題在我腦...
摘要:下面我們來使用面向對象類圖這里就不再畫了首先面試題中所提到的我們都可以看成類,比如停車場是一個類吧,它里面的車位是一個類吧,攝像頭,屏幕。。。 以下是某場的一道面試題(大概): 1、一個停車場,車輛入場時,攝像頭記錄下車輛信息2、屏幕上顯示所接收的車輛的信息情況(車牌號)以及各層車位的車位余量3、停車場一共四層車位,其中的三層都為普通車位,還有一層為特殊車位(體現在停車計費價格上面的不...
閱讀 2752·2021-09-24 09:47
閱讀 4377·2021-08-27 13:10
閱讀 3026·2019-08-30 15:44
閱讀 1291·2019-08-29 12:56
閱讀 2600·2019-08-28 18:07
閱讀 2620·2019-08-26 14:05
閱讀 2573·2019-08-26 13:41
閱讀 1272·2019-08-26 13:33