摘要:最近在一個(gè)前端學(xué)習(xí)群里,有人拋出了這么一道面試題。以下表示形式的是函數(shù)表達(dá)式,有多種形式。函數(shù)聲明式的函數(shù)名是可修改的。重新聲明變量通過上面的分析解釋,希望你可以掌握這道面試題,舉一反三。原文鏈接理解一道面試題
最近在一個(gè)前端學(xué)習(xí)群里,有人拋出了這么一道 JS 面試題。
var foo = 1; (function foo(){ foo = 100; console.log(foo); }()) console.log(foo);
我一看,這不很簡單嗎?IIFE 局部的 foo 本來指向函數(shù)本身,但后來被修改成 100 了,所以局部的 foo 打印 100。全局的 foo 還是保留原來的值,所以全局的 foo 打印 1。
然后我復(fù)制代碼到控制臺運(yùn)行,發(fā)現(xiàn)先打印函數(shù)體 foo(){...},然后再打印 1。
我猜想的第一個(gè)打印結(jié)果錯(cuò)了,一番查找資料終于搞懂了,于是有了這篇文章。
函數(shù)聲明式 vs 函數(shù)表達(dá)式以下表示形式的是函數(shù)聲明式,簡單說就是 function 前面沒有任何運(yùn)算符,其實(shí)就下面一種形式。
function name() { ... }
以下表示形式的是函數(shù)表達(dá)式,有多種形式。
var fun = function name() { ... } // 函數(shù)前帶有 + - * / () && || 等運(yùn)算符號 (function name(){ ... }()) // 又或者 +function name(){ ... }()
能認(rèn)出函數(shù)聲明式與函數(shù)表達(dá)式后,我們來看看兩者有什么區(qū)別。
函數(shù)提升稍微了解 JS 的都知道變量提升(variable hoisting),除此之外還有函數(shù)提升(function hoisting),也就是說下面的代碼是正常運(yùn)行的。
foo(); // running function foo() { console.log("running"); }
但是函數(shù)提升只對函數(shù)聲明式有效,對函數(shù)表達(dá)式不生效,下面的代碼就會報(bào)錯(cuò)。
foo(); // Uncaught TypeError: foo is not a function var foo = function () { console.log("running"); }
區(qū)別一:函數(shù)聲明式會提升函數(shù)定義,而函數(shù)表達(dá)式不提升函數(shù)定義。這一區(qū)別只是想給大家復(fù)習(xí)知識點(diǎn),并不是本文的重點(diǎn)。
函數(shù)名綁定的作用域先看看下面函數(shù)的表示形式,記住它有助于接下來的說明。
function BindingIdentifier (FormalParameters) { FunctionBody }
函數(shù)聲明式和函數(shù)表達(dá)式的另外一個(gè)關(guān)鍵區(qū)別是,看函數(shù)名(BindingIdentifier)綁定到哪個(gè)作用域下。
先看下 ECMAScript 是怎么描述這一區(qū)別的。
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(函數(shù)的引用) 可以用于在函數(shù)表達(dá)式內(nèi)遞歸調(diào)用自身。而且函數(shù)表達(dá)式的 BindingIdentifier 只綁定在該函數(shù)內(nèi)部,不污染外部的作用域,外部作用域也無法訪問到 BindingIdentifier。
區(qū)別二:函數(shù)聲明式的 BindingIdentifier 綁定在聲明時(shí)的作用域下,函數(shù)表達(dá)式的 BindingIdentifier 綁定在函數(shù)內(nèi)部的作用域下。
背后的原因說了這么多,好像還沒說的真正的原因。是的,前面的內(nèi)容只是鋪墊,有了上面的內(nèi)容,才能更好理解背后的原因。
解釋前先說原因:
函數(shù)表達(dá)式的函數(shù)名是不可修改的(ImmutableBinding)。但如果你真修改了,在非嚴(yán)格模式下會靜默失敗,在嚴(yán)格模式下會報(bào)錯(cuò)(Uncaught TypeError: Assignment to constant variable)。
函數(shù)聲明式的函數(shù)名是可修改的(MutableBinding)。
原因出自《You-Dont-Know-JS》的一個(gè) 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. ...
調(diào)用 CreateImmutableBinding 創(chuàng)建 Immutable"s 函數(shù)名。
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. ...
調(diào)用 CreateMutableBinding 創(chuàng)建 Mutable"s 函數(shù)名。
當(dāng)然也可以從 ECMAScript 規(guī)范中找到原因:Runtime Semantics: Evaluation。
至于語言為什么要這么規(guī)定,我也沒想明白,如果有知道的同學(xué)可以分享一下。
分析下代碼那回頭再分析下一開始的示例,從每一行注釋可以幫助理解背后的原因。
var foo = 1; // 在外部作用域聲明foo=1 // IIFE是典型的函數(shù)表達(dá)式 (function foo(){ // 函數(shù)名foo,引用函數(shù)自身,綁定在函數(shù)內(nèi)部,不污染外部作用域 foo = 100; // 這里修改了foo,但規(guī)范規(guī)定不能修改,但不會報(bào)錯(cuò) console.log(foo); // 還是引用函數(shù)自身 }()) console.log(foo); // 外部作用域一直是1
同樣的代碼,當(dāng)函數(shù)運(yùn)行在嚴(yán)格模式下,報(bào)錯(cuò)提示說:“不能賦值給常量”。也就是說函數(shù)表達(dá)式的函數(shù)名被定義成常量,無法再修改了。
var foo = 1; (function foo(){ "use strict"; // 嚴(yán)格模式 foo = 100; // Uncaught TypeError: Assignment to constant variable console.log(foo); }()) console.log(foo);
為了幫助對比理解,下面給出了函數(shù)聲明式的示例及解釋,下面的代碼無論在非嚴(yán)格模式還是嚴(yán)格模式下都打印100,也就是說函數(shù)聲明式的函數(shù)名可以被修改。
// foo是函數(shù)聲明式 function foo(){ // 函數(shù)名foo,引用函數(shù)自身,綁定在聲明時(shí)的作用域下 foo = 100; // 修改了foo,函數(shù)聲明式內(nèi)可以重新修改函數(shù)名 console.log(foo); // 100 } foo();
如果在函數(shù)表達(dá)式內(nèi)使用 var foo = 100; 來重新聲明變量,那這個(gè)變量就不是不可修改的(ImmutableBinding),所以內(nèi)部的 foo 打印 100。
var foo = 1; (function foo(){ var foo = 100; // 重新聲明變量 console.log(foo); // 100 }()) console.log(foo); // 1
通過上面的分析解釋,希望你可以掌握這道面試題,舉一反三。
如果你喜歡這篇文章,請關(guān)注我,我會持續(xù)輸出更多原創(chuàng)且高質(zhì)量的內(nèi)容。
原文鏈接:【理解】一道 JS 面試題
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103665.html
摘要:項(xiàng)目組長給我看了一道面試別人的面試題。打鐵趁熱,再來一道題來加深下理解。作者以樂之名本文原創(chuàng),有不當(dāng)?shù)牡胤綒g迎指出。 showImg(https://segmentfault.com/img/bVbur0z?w=600&h=400); 剛?cè)肼毿鹿?,屬于公司萌新一枚,一天下午對著屏幕看代碼架構(gòu)時(shí)。BI項(xiàng)目組長給我看了一道面試別人的JS面試題。 雖然答對了,但把理由說錯(cuò)了,照樣不及格。 ...
摘要:今天看見一道面試題答案是多少答案是對方法不太了解就去搜了一下,里面也包含了對這道面試題的詳解。方法返回一個(gè)由原數(shù)組中的每個(gè)元素調(diào)用一個(gè)指定方法后返回值組成的新數(shù)組。使用方法處理數(shù)組時(shí),數(shù)組元素的范圍在方法第一次調(diào)用之前就已經(jīng)確定了。 今天看見一道面試題:[1,2,3].map(parseInt)答案是多少?答案是[1,NaN,NaN] 對map()方法不太了解就去搜了一下:Array....
摘要:想必面試題刷的多的同學(xué)對下面這道題目不陌生,能夠立即回答出輸出個(gè),可是你真的懂為什么嗎為什么是輸出為什么是輸出個(gè)這兩個(gè)問題在我腦邊縈繞。同步任務(wù)都好理解,一個(gè)執(zhí)行完執(zhí)行下一個(gè)。本文只是我對這道面試題的一點(diǎn)思考,有誤的地方望批評指正。 想必面試題刷的多的同學(xué)對下面這道題目不陌生,能夠立即回答出輸出10個(gè)10,可是你真的懂為什么嗎?為什么是輸出10?為什么是輸出10個(gè)10?這兩個(gè)問題在我腦...
摘要:下面我們來使用面向?qū)ο箢悎D這里就不再畫了首先面試題中所提到的我們都可以看成類,比如停車場是一個(gè)類吧,它里面的車位是一個(gè)類吧,攝像頭,屏幕。。。 以下是某場的一道面試題(大概): 1、一個(gè)停車場,車輛入場時(shí),攝像頭記錄下車輛信息2、屏幕上顯示所接收的車輛的信息情況(車牌號)以及各層車位的車位余量3、停車場一共四層車位,其中的三層都為普通車位,還有一層為特殊車位(體現(xiàn)在停車計(jì)費(fèi)價(jià)格上面的不...
閱讀 2764·2021-09-24 09:47
閱讀 4384·2021-08-27 13:10
閱讀 3036·2019-08-30 15:44
閱讀 1303·2019-08-29 12:56
閱讀 2609·2019-08-28 18:07
閱讀 2627·2019-08-26 14:05
閱讀 2593·2019-08-26 13:41
閱讀 1279·2019-08-26 13:33