摘要:標(biāo)識(shí)符有效性正是導(dǎo)致函數(shù)語(yǔ)句與函數(shù)表達(dá)式不同的關(guān)鍵所在下一小節(jié)我們將會(huì)展示命名函數(shù)表達(dá)式的具體行為。歸根結(jié)底,只有給函數(shù)表達(dá)式取個(gè)名字,才是最穩(wěn)妥的辦法,也就是使用命名函數(shù)表達(dá)式。
前言
網(wǎng)上還沒(méi)用發(fā)現(xiàn)有人對(duì)命名函數(shù)表達(dá)式進(jìn)去重復(fù)深入的討論,正因?yàn)槿绱耍W(wǎng)上出現(xiàn)了各種各樣的誤解,本文將從原理和實(shí)踐兩個(gè)方面來(lái)探討JavaScript關(guān)于命名函數(shù)表達(dá)式的優(yōu)缺點(diǎn)。
簡(jiǎn)單的說(shuō),命名函數(shù)表達(dá)式只有一個(gè)用戶,那就是在==Debug==或者==Profiler==分析的時(shí)候來(lái)描述函數(shù)的名稱,也可以使用函數(shù)名實(shí)現(xiàn)遞歸,但很快你就會(huì)發(fā)現(xiàn)其實(shí)是不切實(shí)際的。當(dāng)然,如果你不關(guān)注調(diào)試,那就沒(méi)什么可擔(dān)心的了,否則,如果你想了解兼容性方面的東西的話,你還是應(yīng)該繼續(xù)往下看看。
我們先開(kāi)始看看,什么叫函數(shù)表達(dá)式,然后再說(shuō)一下現(xiàn)代調(diào)試器如何處理這些表達(dá)式,如果你已經(jīng)對(duì)這方面很熟悉的話,請(qǐng)直接跳過(guò)此小節(jié)。
本文中后半部分說(shuō)了好多JScript,基本上是過(guò)時(shí)的東西,我覺(jué)得直接略過(guò)就行
在ECMAScript中,創(chuàng)建函數(shù)的最常用的兩個(gè)方法是函數(shù)表達(dá)式和函數(shù)聲明,兩者期間的區(qū)別是有點(diǎn)暈,因?yàn)?b>ECMAScript規(guī)范只明確了一點(diǎn):函數(shù)聲明必須帶有標(biāo)示符(==Identifier==)(就是大家常說(shuō)的函數(shù)名稱),而函數(shù)表達(dá)式則可以省略這個(gè)標(biāo)示符:
函數(shù)聲明:
function 函數(shù)名稱 (參數(shù):可選){ 函數(shù)體 }
函數(shù)表達(dá)式:
function 函數(shù)名稱(可選)(參數(shù):可選){ 函數(shù)體 }
所以,可以看出,如果不聲明函數(shù)名稱,它肯定是表達(dá)式,可如果聲明了函數(shù)名稱的話,如何判斷是函數(shù)聲明還是函數(shù)表達(dá)式呢?==ECMAScript==是通過(guò)上下文來(lái)區(qū)分的,如果function foo(){}是作為賦值表達(dá)式的一部分的話,那它就是一個(gè)函數(shù)表達(dá)式,如果function foo(){}被包含在一個(gè)函數(shù)體內(nèi),或者位于程序的最頂部的話,那它就是一個(gè)函數(shù)聲明。
function foo(){} // 聲明,因?yàn)樗浅绦虻囊徊糠? var bar = function foo(){}; // 表達(dá)式,因?yàn)樗琴x值表達(dá)式的一部分 new function bar(){}; // 表達(dá)式,因?yàn)樗莕ew表達(dá)式 (function(){ function bar(){} // 聲明,因?yàn)樗呛瘮?shù)體的一部分 })();
還有一種函數(shù)表達(dá)式不太常見(jiàn),就是被括號(hào)括住的(function foo(){}),他是表達(dá)式的原因是因?yàn)槔ㄌ?hào) ()是一個(gè)分組操作符,它的內(nèi)部只能包含表達(dá)式,我們來(lái)看幾個(gè)例子:
function foo(){} // 函數(shù)聲明 (function foo(){}); // 函數(shù)表達(dá)式:包含在分組操作符內(nèi) try { (var x = 5); // 分組操作符,只能包含表達(dá)式而不能包含語(yǔ)句:這里的var就是語(yǔ)句 } catch(err) { // SyntaxError }
你可以會(huì)想到,在使用eval對(duì)JSON進(jìn)行執(zhí)行的時(shí)候,JSON字符串通常被包含在一個(gè)圓括號(hào)里:eval("(" + json + ")"),這樣做的原因就是因?yàn)榉纸M操作符,也就是這對(duì)括號(hào),會(huì)讓解析器強(qiáng)制將JSON的花括號(hào)解析成表達(dá)式而不是代碼塊。
try { { "x": 5 }; // "{" 和 "}" 做解析成代碼塊 } catch(err) { // SyntaxError } ({ "x": 5 }); // 分組操作符強(qiáng)制將"{" 和 "}"作為對(duì)象字面量來(lái)解析
表達(dá)式和聲明存在著十分微妙的差別,首先,函數(shù)聲明會(huì)在任何表達(dá)式被解析和求值之前先被解析和求值,即使你的聲明在代碼的最后一行,它也會(huì)在同作用域內(nèi)第一個(gè)表達(dá)式之前被解析/求值,參考如下例子,函數(shù)fn是在alert之后聲明的,但是在alert執(zhí)行的時(shí)候,fn已經(jīng)有定義了:
alert(fn()); function fn() { return "Hello world!"; }
另外,還有一點(diǎn)需要提醒一下,函數(shù)聲明在條件語(yǔ)句內(nèi)雖然可以用,但是沒(méi)有被標(biāo)準(zhǔn)化,也就是說(shuō)不同的環(huán)境可能有不同的執(zhí)行結(jié)果,所以這樣情況下,最好使用函數(shù)表達(dá)式:
// 千萬(wàn)別這樣做! // 因?yàn)橛械臑g覽器會(huì)返回first的這個(gè)function,而有的瀏覽器返回的卻是第二個(gè) if (true) { function foo() { return "first"; } }else { function foo() { return "second"; } } foo(); // 相反,這樣情況,我們要用函數(shù)表達(dá)式 var foo; if (true) { foo = function() { return "first"; }; }else { foo = function() { return "second"; }; } foo();
函數(shù)聲明的實(shí)際規(guī)則如下:
函數(shù)語(yǔ)句函數(shù)聲明只能出現(xiàn)在程序或函數(shù)體內(nèi)。從句法上講,它們 不能出現(xiàn)在Block(塊)({ ... })中,例如不能出現(xiàn)在 if、while 或 for 語(yǔ)句中。因?yàn)?Block(塊) 中只能包含Statement語(yǔ)句, 而不能包含函數(shù)聲明這樣的源元素。另一方面,仔細(xì)看一看規(guī)則也會(huì)發(fā)現(xiàn),唯一可能讓表達(dá)式出現(xiàn)在Block(塊)中情形,就是讓它作為表達(dá)式語(yǔ)句的一部分。但是,規(guī)范明確規(guī)定了表達(dá)式語(yǔ)句不能以關(guān)鍵字function開(kāi)頭。而這實(shí)際上就是說(shuō),函數(shù)表達(dá)式同樣也不能出現(xiàn)在Statement語(yǔ)句或Block(塊)中(因?yàn)锽lock(塊)就是由Statement語(yǔ)句構(gòu)成的)。
在ECMAScript的語(yǔ)法擴(kuò)展中,有一個(gè)是函數(shù)語(yǔ)句,目前只有基于Gecko的瀏覽器實(shí)現(xiàn)了該擴(kuò)展,所以對(duì)于下面的例子,我們僅是抱著學(xué)習(xí)的目的來(lái)看,一般來(lái)說(shuō)不推薦使用(除非你針對(duì)Gecko瀏覽器進(jìn)行開(kāi)發(fā))。
一般語(yǔ)句能用的地方,函數(shù)語(yǔ)句也能用,當(dāng)然也包括Block塊中:
if (true) { function f(){ } }else { function f(){ } }
函數(shù)語(yǔ)句可以像其他語(yǔ)句一樣被解析,包含基于條件執(zhí)行的情形
if (true) { function foo(){ return 1; } }else { function foo(){ return 2; } } foo(); // 1 // 注:其它客戶端會(huì)將foo解析成函數(shù)聲明 // 因此,第二個(gè)foo會(huì)覆蓋第一個(gè),結(jié)果返回2,而不是1
函數(shù)語(yǔ)句不是在變量初始化期間聲明的,而是在運(yùn)行時(shí)聲明的——與函數(shù)表達(dá)式一樣。不過(guò),函數(shù)語(yǔ)句的標(biāo)識(shí)符一旦聲明能在函數(shù)的整個(gè)作用域生效了。標(biāo)識(shí)符有效性正是導(dǎo)致函數(shù)語(yǔ)句與函數(shù)表達(dá)式不同的關(guān)鍵所在(下一小節(jié)我們將會(huì)展示命名函數(shù)表達(dá)式的具體行為)。
// 此刻,foo還沒(méi)用聲明 typeof foo; // "undefined" if (true) { // 進(jìn)入這里以后,foo就被聲明在整個(gè)作用域內(nèi)了 function foo(){ return 1; } }else { // 從來(lái)不會(huì)走到這里,所以這里的foo也不會(huì)被聲明 function foo(){ return 2; } } typeof foo; // "function"
不過(guò),我們可以使用下面這樣的符合標(biāo)準(zhǔn)的代碼來(lái)模式上面例子中的函數(shù)語(yǔ)句:
var foo; if (true) { foo = function foo(){ return 1; }; }else { foo = function foo() { return 2; }; }
函數(shù)語(yǔ)句和函數(shù)聲明(或命名函數(shù)表達(dá)式)的字符串表示類似,也包括標(biāo)識(shí)符:
if (true) { function foo(){ return 1; } } String(foo); // function foo() { return 1; }
另外一個(gè),早期基于Gecko的實(shí)現(xiàn)(Firefox 3及以前版本)中存在一個(gè)bug,即函數(shù)語(yǔ)句覆蓋函數(shù)聲明的方式不正確。在這些早期的實(shí)現(xiàn)中,函數(shù)語(yǔ)句不知何故不能覆蓋函數(shù)聲明:
// 函數(shù)聲明 function foo(){ return 1; } if (true) { // 用函數(shù)語(yǔ)句重寫(xiě) function foo(){ return 2; } } foo(); // FF3以下返回1,F(xiàn)F3.5以上返回2 // 不過(guò),如果前面是函數(shù)表達(dá)式,則沒(méi)用問(wèn)題 var foo = function(){ return 1; }; if (true) { function foo(){ return 2; } } foo(); // 所有版本都返回2
再次強(qiáng)調(diào)一點(diǎn),上面這些例子只是在某些瀏覽器支持,所以推薦大家不要使用這些,除非你就在特性的瀏覽器上做開(kāi)發(fā)。
命名函數(shù)表達(dá)式函數(shù)表達(dá)式在實(shí)際應(yīng)用中還是很常見(jiàn)的,在web開(kāi)發(fā)中友個(gè)常用的模式是基于對(duì)某種特性的測(cè)試來(lái)偽裝函數(shù)定義,從而達(dá)到性能優(yōu)化的目的,但由于這種方式都是在同一作用域內(nèi),所以基本上一定要用函數(shù)表達(dá)式:
// 該代碼來(lái)自Garrett Smith的APE Javascript library庫(kù)(http://dhtmlkitchen.com/ape/) var contains = (function() { var docEl = document.documentElement; if (typeof docEl.compareDocumentPosition != "undefined") { return function(el, b) { return (el.compareDocumentPosition(b) & 16) !== 0; }; } else if (typeof docEl.contains != "undefined") { return function(el, b) { return el !== b && el.contains(b); }; } return function(el, b) { if (el === b) return false; while (el != b && (b = b.parentNode) != null); return el === b; }; })();
提到命名函數(shù)表達(dá)式,理所當(dāng)然,就是它得有名字,前面的例子var bar = function foo(){};就是一個(gè)有效的命名函數(shù)表達(dá)式,但有一點(diǎn)需要記?。哼@個(gè)名字只在新定義的函數(shù)作用域內(nèi)有效,因?yàn)橐?guī)范規(guī)定了標(biāo)示符不能在外圍的作用域內(nèi)有效:
var f = function foo(){ return typeof foo; // foo是在內(nèi)部作用域內(nèi)有效 }; // foo在外部用于是不可見(jiàn)的 typeof foo; // "undefined" f(); // "function"
既然,這么要求,那命名函數(shù)表達(dá)式到底有啥用?。繛樯兑∶??
正如我們開(kāi)頭所說(shuō):給它一個(gè)名字就是可以讓調(diào)試過(guò)程更方便,因?yàn)樵谡{(diào)試的時(shí)候,如果在調(diào)用棧中的每個(gè)項(xiàng)都有自己的名字來(lái)描述,那么調(diào)試過(guò)程就太爽了,感受不一樣嘛。
如果一個(gè)函數(shù)有名字,那調(diào)試器在調(diào)試的時(shí)候會(huì)將它的名字顯示在調(diào)用的棧上。有些調(diào)試器(Firebug)有時(shí)候還會(huì)為你們函數(shù)取名并顯示,讓他們和那些應(yīng)用該函數(shù)的便利具有相同的角色,可是通常情況下,這些調(diào)試器只安裝簡(jiǎn)單的規(guī)則來(lái)取名,所以說(shuō)沒(méi)有太大價(jià)格,我們來(lái)看一個(gè)例子:
function foo(){ return bar(); } function bar(){ return baz(); } function baz(){ debugger; } foo(); // 這里我們使用了3個(gè)帶名字的函數(shù)聲明 // 所以當(dāng)調(diào)試器走到debugger語(yǔ)句的時(shí)候,F(xiàn)irebug的調(diào)用棧上看起來(lái)非常清晰明了 // 因?yàn)楹苊靼椎仫@示了名稱 baz bar foo expr_test.html()
通過(guò)查看調(diào)用棧的信息,我們可以很明了地知道foo調(diào)用了bar, bar又調(diào)用了baz(而foo本身有在expr_test.html文檔的全局作用域內(nèi)被調(diào)用),不過(guò),還有一個(gè)比較爽地方,就是剛才說(shuō)的Firebug為匿名表達(dá)式取名的功能:
function foo(){ return bar(); } var bar = function(){ return baz(); } function baz(){ debugger; } foo(); // Call stack baz bar() //看到了么? foo expr_test.html()
然后,當(dāng)函數(shù)表達(dá)式稍微復(fù)雜一些的時(shí)候,調(diào)試器就不那么聰明了,我們只能在調(diào)用棧中看到問(wèn)號(hào):
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function(){ return baz(); }; } else if (window.attachEvent) { return function() { return baz(); }; } })(); function baz(){ debugger; } foo(); // Call stack baz (?)() // 這里可是問(wèn)號(hào)哦 foo expr_test.html()
另外,當(dāng)把函數(shù)賦值給多個(gè)變量的時(shí)候,也會(huì)出現(xiàn)令人郁悶的問(wèn)題:
function foo(){ return baz(); } var bar = function(){ debugger; }; var baz = bar; bar = function() { alert("spoofed"); }; foo(); // Call stack: bar() foo expr_test.html()
這時(shí)候,調(diào)用棧顯示的是foo調(diào)用了bar,但實(shí)際上并非如此,之所以有這種問(wèn)題,是因?yàn)閎az和另外一個(gè)包含alert("spoofed")的函數(shù)做了引用交換所導(dǎo)致的。
歸根結(jié)底,只有給函數(shù)表達(dá)式取個(gè)名字,才是最穩(wěn)妥的辦法,也就是使用命名函數(shù)表達(dá)式。我們來(lái)使用帶名字的表達(dá)式來(lái)重寫(xiě)上面的例子(注意立即調(diào)用的表達(dá)式塊里返回的2個(gè)函數(shù)的名字都是bar):
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function bar(){ return baz(); }; } else if (window.attachEvent) { return function bar() { return baz(); }; } })(); function baz(){ debugger; } foo(); // 又再次看到了清晰的調(diào)用棧信息了耶! baz bar foo expr_test.html()
OK,又學(xué)了一招吧?不過(guò)在高興之前,我們?cè)倏纯床煌瑢こ5腏Script吧。
JScript這一部分講的全都是JScript而不是Javascript這兩個(gè)真不是一種東西
netscape開(kāi)發(fā)了在Navigator中使用的LiveScript語(yǔ)言,后改名為JavaScript
Microsoft發(fā)行jscript用于internet explorer.
最初的jscript和javascript差異過(guò)大,web程序員不得不痛苦的為兩種瀏覽器編寫(xiě)兩種腳本。于是誕生了ECMAScript,是一種國(guó)際標(biāo)準(zhǔn)化的javascript版本?,F(xiàn)在的主流瀏覽器都支持這種版本。
javascript是一個(gè)通用的名稱,所有瀏覽器都認(rèn)識(shí),而jscript只有IE認(rèn)識(shí)。
其他語(yǔ)言細(xì)節(jié)上的區(qū)別,不是一兩下能說(shuō)完的。編程時(shí)最好遵循ECMAscript標(biāo)準(zhǔn)。這樣可以保證兼容性。
順便說(shuō)一下,javascript原來(lái)叫Livescript,后來(lái)Sun的java風(fēng)頭正盛的時(shí)候netscape就把名字改成javascript。
個(gè)人感覺(jué)這一段基本上可以忽略了 但為了尊重作者我還是把它整理了一下。
JScript的Bug比較惡的是,IE的ECMAScript實(shí)現(xiàn)JScript嚴(yán)重混淆了命名函數(shù)表達(dá)式,搞得現(xiàn)很多人都出來(lái)反對(duì)命名函數(shù)表達(dá)式,而且即便是最新的一版(IE8中使用的5.8版)仍然存在下列問(wèn)題。
下面我們就來(lái)看看IE在實(shí)現(xiàn)中究竟犯了那些錯(cuò)誤,俗話說(shuō)知已知彼,才能百戰(zhàn)不殆。我們來(lái)看看如下幾個(gè)例子:
例1:函數(shù)表達(dá)式的標(biāo)示符泄露到外部作用域
var f = function g(){}; typeof g; // "function"
上面我們說(shuō)過(guò),命名函數(shù)表達(dá)式的標(biāo)示符在外部作用域是無(wú)效的,但JScript明顯是違反了這一規(guī)范,上面例子中的標(biāo)示符g被解析成函數(shù)對(duì)象,這就亂了套了,很多難以發(fā)現(xiàn)的bug都是因?yàn)檫@個(gè)原因?qū)е碌摹?br>==注:IE9貌似已經(jīng)修復(fù)了這個(gè)問(wèn)題==
例2:將命名函數(shù)表達(dá)式同時(shí)當(dāng)作函數(shù)聲明和函數(shù)表達(dá)式
typeof g; // "function" var f = function g(){};
特性環(huán)境下,函數(shù)聲明會(huì)優(yōu)先于任何表達(dá)式被解析,上面的例子展示的是JScript實(shí)際上是把命名函數(shù)表達(dá)式當(dāng)成函數(shù)聲明了,因?yàn)樗趯?shí)際聲明之前就解析了g。
這個(gè)例子引出了下一個(gè)例子。
例3:命名函數(shù)表達(dá)式會(huì)創(chuàng)建兩個(gè)截然不同的函數(shù)對(duì)象!
var f = function g(){}; f === g; // false f.expando = "foo"; g.expando; // undefined
看到這里,大家會(huì)覺(jué)得問(wèn)題嚴(yán)重了,因?yàn)樾薷娜魏我粋€(gè)對(duì)象,另外一個(gè)沒(méi)有什么改變,這太惡了。通過(guò)這個(gè)例子可以發(fā)現(xiàn),創(chuàng)建2個(gè)不同的對(duì)象,也就是說(shuō)如果你想修改f的屬性中保存某個(gè)信息,然后想當(dāng)然地通過(guò)引用相同對(duì)象的g的同名屬性來(lái)使用,那問(wèn)題就大了,因?yàn)楦揪筒豢赡堋?/p>
再來(lái)看一個(gè)稍微復(fù)雜的例子:
例4:僅僅順序解析函數(shù)聲明而忽略條件語(yǔ)句塊
var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } g(); // 2
這個(gè)bug查找就難多了,但導(dǎo)致bug的原因卻非常簡(jiǎn)單。首先,g被當(dāng)作函數(shù)聲明解析,由于JScript中的函數(shù)聲明不受條件代碼塊約束,所以在這個(gè)很惡的if分支中,g被當(dāng)作另一個(gè)函數(shù)function g(){ return 2 },也就是又被聲明了一次。然后,所有“常規(guī)的”表達(dá)式被求值,而此時(shí)f被賦予了另一個(gè)新創(chuàng)建的對(duì)象的引用。由于在對(duì)表達(dá)式求值的時(shí)候,永遠(yuǎn)不會(huì)進(jìn)入“這個(gè)可惡if分支,因此f就會(huì)繼續(xù)引用第一個(gè)函數(shù)function g(){ return 1 }。分析到這里,問(wèn)題就很清楚了:假如你不夠細(xì)心,在f中調(diào)用了g,那么將會(huì)調(diào)用一個(gè)毫不相干的g函數(shù)對(duì)象。
你可能會(huì)文,將不同的對(duì)象和arguments.callee相比較時(shí),有什么樣的區(qū)別呢?我們來(lái)看看:
var f = function g(){ return [ arguments.callee == f, arguments.callee == g ]; }; f(); // [true, false] g(); // [false, true]
可以看到,arguments.callee的引用一直是被調(diào)用的函數(shù),實(shí)際上這也是好事,稍后會(huì)解釋。
還有一個(gè)有趣的例子,那就是在不包含聲明的賦值語(yǔ)句中使用命名函數(shù)表達(dá)式:
(function(){ f = function f(){}; })();
按照代碼的分析,我們?cè)臼窍雱?chuàng)建一個(gè)全局屬性f(注意不要和一般的匿名函數(shù)混淆了,里面用的是帶名字的生命),JScript在這里搗亂了一把,首先他把表達(dá)式當(dāng)成函數(shù)聲明解析了,所以左邊的f被聲明為局部變量了(和一般的匿名函數(shù)里的聲明一樣),然后在函數(shù)執(zhí)行的時(shí)候,f已經(jīng)是定義過(guò)的了,右邊的function f(){}則直接就賦值給局部變量f了,所以f根本就不是全局屬性。
了解了JScript這么{{BANNED}}以后,我們就要及時(shí)預(yù)防這些問(wèn)題了,首先防范標(biāo)識(shí)符泄漏帶外部作用域,其次,應(yīng)該永遠(yuǎn)不引用被用作函數(shù)名稱的標(biāo)識(shí)符;還記得前面例子中那個(gè)討人厭的標(biāo)識(shí)符g嗎?——如果我們能夠當(dāng)g不存在,可以避免多少不必要的麻煩哪。因此,關(guān)鍵就在于始終要通過(guò)f或者arguments.callee來(lái)引用函數(shù)。如果你使用了命名函數(shù)表達(dá)式,那么應(yīng)該只在調(diào)試的時(shí)候利用那個(gè)名字。最后,還要記住一點(diǎn),一定要把命名函數(shù)表達(dá)式聲明期間錯(cuò)誤創(chuàng)建的函數(shù)清理干凈。
對(duì)于,上面最后一點(diǎn),我們還得再解釋一下。
WebKit的displayNameWebKit團(tuán)隊(duì)在這個(gè)問(wèn)題采取了有點(diǎn)兒另類的策略。介于匿名和命名函數(shù)如此之差的表現(xiàn)力,WebKit引入了一個(gè)“特殊的”displayName屬性(本質(zhì)上是一個(gè)字符串),如果開(kāi)發(fā)人員為函數(shù)的這個(gè)屬性賦值,則該屬性的值將在調(diào)試器或性能分析器中被顯示在函數(shù)“名稱”的位置上。Francisco Tolmasky詳細(xì)地解釋了這個(gè)策略的原理和實(shí)現(xiàn)。
在ECMAScript-262第5版引入了嚴(yán)格模式(strict mode)。開(kāi)啟嚴(yán)格模式的實(shí)現(xiàn)會(huì)禁用語(yǔ)言中的那些不穩(wěn)定、不可靠和不安全的特性。據(jù)說(shuō)出于安全方面的考慮,arguments.callee屬性將在嚴(yán)格模式下被“封殺”。因此,在處于嚴(yán)格模式時(shí),訪問(wèn)arguments.callee會(huì)導(dǎo)致TypeError(參見(jiàn)ECMA-262第5版的10.6節(jié))。而我之所以在此提到嚴(yán)格模式,是因?yàn)槿绻诨诘?版標(biāo)準(zhǔn)的實(shí)現(xiàn)中無(wú)法使用arguments.callee來(lái)執(zhí)行遞歸操作,那么使用命名函數(shù)表達(dá)式的可能性就會(huì)大大增加。從這個(gè)意義上來(lái)說(shuō),理解命名函數(shù)表達(dá)式的語(yǔ)義及其bug也就顯得更加重要了。
// 此前,你可能會(huì)使用arguments.callee (function(x) { if (x <= 1) return 1; return x * arguments.callee(x - 1); })(10); // 但在嚴(yán)格模式下,有可能就要使用命名函數(shù)表達(dá)式 (function factorial(x) { if (x <= 1) return 1; return x * factorial(x - 1); })(10); // 要么就退一步,使用沒(méi)有那么靈活的函數(shù)聲明 function factorial(x) { if (x <= 1) return 1; return x * factorial(x - 1); } factorial(10);致謝
理查德· 康福德(Richard Cornford),是他率先解釋了JScript中命名函數(shù)表達(dá)式所存在的bug。理查德解釋了我在這篇文章中提及的大多數(shù)bug,所以我強(qiáng)烈建議大家去看看他的解釋。我還要感謝Yann-Erwan Perio和道格拉斯·克勞克佛德(Douglas Crockford),他們?cè)缭?003年就在comp.lang.javascript論壇中提及并討論NFE問(wèn)題了。
約翰-戴維·道爾頓(John-David Dalton)對(duì)“最終解決方案”提出了很好的建議。
托比·蘭吉的點(diǎn)子被我用在了“替代方案”中。
蓋瑞特·史密斯(Garrett Smith)和德米特里·蘇斯尼科(Dmitry Soshnikov)對(duì)本文的多方面作出了補(bǔ)充和修正。
英文原文:http://kangax.github.com/nfe/
參考譯文:連接訪問(wèn) (SpiderMonkey的怪癖之后的章節(jié)參考該文)
關(guān)于本文本文轉(zhuǎn)自TOM大叔的深入理解JavaScript系列本文有大量刪減,查看原文
【深入理解JavaScript系列】文章,包括了原創(chuàng),翻譯,轉(zhuǎn)載,整理等各類型文章,原文是TOM大叔的一個(gè)非常不錯(cuò)的專題,現(xiàn)將其重新整理發(fā)布。謝謝大叔。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78411.html
摘要:前言大家學(xué)的時(shí)候,經(jīng)常遇到自執(zhí)行匿名函數(shù)的代碼,今天我們主要就來(lái)想想說(shuō)一下自執(zhí)行。其實(shí),前面兩個(gè)例子里的變量,也可以換成,因?yàn)楹屯饷娴牟辉谝粋€(gè)作用于,所以不會(huì)出現(xiàn)問(wèn)題,這也是匿名函數(shù)閉包的威力。 前言 大家學(xué)JavaScript的時(shí)候,經(jīng)常遇到自執(zhí)行匿名函數(shù)的代碼,今天我們主要就來(lái)想想說(shuō)一下自執(zhí)行。 在詳細(xì)了解這個(gè)之前,我們來(lái)談了解一下自執(zhí)行這個(gè)叫法,本文對(duì)這個(gè)功能的叫法也不一定完全對(duì)...
摘要:關(guān)鍵詞必須是小寫(xiě)的,并且必須以與函數(shù)名稱相同的大小寫(xiě)來(lái)調(diào)用函數(shù)。當(dāng)調(diào)用函數(shù)時(shí),這些標(biāo)識(shí)符則指代傳入函數(shù)的實(shí)參。函數(shù)表達(dá)式其實(shí)是忽略函數(shù)名稱的,并且不可以使用函數(shù)名這種形式調(diào)用函數(shù)。注意構(gòu)造函數(shù)無(wú)法指定函數(shù)名稱,它創(chuàng)建的是一個(gè)匿名函數(shù)。 一、關(guān)于函數(shù) JavaScript函數(shù)是指一個(gè)特定代碼塊,可能包含多條語(yǔ)句,可以通過(guò)名字來(lái)供其他語(yǔ)句調(diào)用以執(zhí)行函數(shù)包含的代碼語(yǔ)句。 比如我們有一個(gè)特定的...
摘要:所有變量聲明由名稱和對(duì)應(yīng)值組成一個(gè)變量對(duì)象的屬性被創(chuàng)建如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會(huì)干擾已經(jīng)存在的這類屬性。 介紹 JavaScript編程的時(shí)候總避免不了聲明函數(shù)和變量,以成功構(gòu)建我們的系統(tǒng),但是解釋器是如何并且在什么地方去查找這些函數(shù)和變量呢?我們引用這些對(duì)象的時(shí)候究竟發(fā)生了什么? 原始發(fā)布:Dmitry A. Soshnikov 發(fā)布時(shí)間:2009-...
摘要:編寫(xiě)高質(zhì)量的要點(diǎn)深入理解系列一知識(shí)點(diǎn)最小全局變量全局變量命名易與第三方的腳本引起沖突所以盡可能少的使用全局變量是很重要的相關(guān)策略有命名空間模式或是函數(shù)立即自動(dòng)執(zhí)行,但是要想讓全局變量少最重要的還是始終使用來(lái)聲明變量。 Title: 編寫(xiě)高質(zhì)量Javascript的要點(diǎn)-Review深入理解Javascript系列(一)date: 2017-6-9 14:14:20 status: p...
摘要:訪問(wèn)全局對(duì)象在瀏覽器中,全局對(duì)象可以通過(guò)屬性在代碼的任何位置訪問(wèn)除非你做了些比較出格的事情,像是聲明了一個(gè)名為的局部變量。 前言 才華橫溢的Stoyan Stefanov,在他寫(xiě)的由O’Reilly初版的新書(shū)《JavaScript Patterns》(JavaScript模式)中,我想要是為我們的讀者貢獻(xiàn)其摘要,那會(huì)是件很美妙的事情。具體一點(diǎn)就是編寫(xiě)高質(zhì)量JavaScript的一些要素...
閱讀 2861·2021-09-10 10:51
閱讀 2224·2021-09-02 15:21
閱讀 3216·2019-08-30 15:44
閱讀 886·2019-08-29 18:34
閱讀 1663·2019-08-29 13:15
閱讀 3335·2019-08-26 11:37
閱讀 2707·2019-08-26 10:46
閱讀 1118·2019-08-26 10:26