摘要:如果我們把這樣的定義放在全局代碼中,解析器會(huì)把函數(shù)當(dāng)作聲明,因?yàn)樗躁P(guān)鍵字開頭,在第一種情況中,我們會(huì)得到,因?yàn)槲覀內(nèi)鄙俸瘮?shù)名。
原文
ECMA-262-3 in detail. Chapter 5. Functions.
簡介在這篇文章中,我們將討論一個(gè)ESCMAScript對(duì)象,函數(shù)。我們將討論不同類型的函數(shù),每個(gè)類型是如何影響環(huán)境中的變量對(duì)象(variables object)以及內(nèi)部的作用域的。我們將回答以下經(jīng)常會(huì)出現(xiàn)的問題,“下面的函數(shù)有什么不同嗎?”[譯者注:當(dāng)進(jìn)入一個(gè)函數(shù)時(shí),會(huì)創(chuàng)建一個(gè)對(duì)象,里面保存了函數(shù)運(yùn)行需要的各種變量]
var foo = function () { ... };
function foo() { ... }
(function () { ... })();函數(shù)類型 函數(shù)聲明
函數(shù)聲明(Funcation Declaration),縮寫FD,是一個(gè)函數(shù):
必須擁有一個(gè)名字
在源碼中的位置,要么在程序級(jí)別,要么在其他函數(shù)體的體內(nèi)
在進(jìn)入上下文時(shí)創(chuàng)建
會(huì)影響變量對(duì)象
用以下方式聲明
function exampleFunc() { ... }
這個(gè)類型的函數(shù)的主要特點(diǎn)是它影響變量對(duì)象(它們被存在環(huán)境的變量對(duì)象中)。這導(dǎo)致了第二個(gè)重要的點(diǎn),在代碼執(zhí)行期間,他們已經(jīng)被用了,因?yàn)楫?dāng)一進(jìn)入環(huán)境后,函數(shù)聲明就被保存在環(huán)境的變量對(duì)象中,在代碼開始執(zhí)行之前。例如函數(shù)可以被調(diào)用在它被聲明之前,從源碼的角度看過去
foo(); function foo() { console.log("foo"); }
// function can be declared: // 1) directly in the global context function globalFD() { // 2) or inside the body // of another function function innerFD() {} }
函數(shù)聲明要么出現(xiàn)在全局環(huán)境中,要么出現(xiàn)在其他函數(shù)體內(nèi)。
函數(shù)表達(dá)式函數(shù)表達(dá)式(Function Expression),縮寫FE,是一個(gè)函數(shù)
在源碼中可以在表達(dá)式位置被定義
可以選擇不要名字
對(duì)環(huán)境的變量對(duì)象沒有影響
在代碼執(zhí)行的階段被創(chuàng)建
常見的賦值表達(dá)式
var foo = function () { ... };
也可以給函數(shù)個(gè)名字
var foo = function _foo() { ... };
在這值得注意的是,在函數(shù)表達(dá)式的外面通過變量foo訪問函數(shù),而在函數(shù)內(nèi)部,例如遞歸調(diào)用,用_foo訪問。函數(shù)表達(dá)式總在表達(dá)式的位置,例如以下的例子都是函數(shù)表達(dá)式
// in parentheses (grouping operator) can be only an expression (function foo() {}); // in the array initialiser – also only expressions [function bar() {}]; // comma also operates with expressions 1, function baz() {};
函數(shù)表達(dá)式在代碼執(zhí)行的階段才會(huì)創(chuàng)建,并不會(huì)被存在環(huán)境變量對(duì)象中
// FE is not available neither before the definition // (because it is created at code execution phase), console.log(foo); // "foo" is not defined (function foo() {}); // nor after, because it is not in the VO console.log(foo); // "foo" is not defined
為什么需要函數(shù)表達(dá)式?是為了不污染環(huán)境變量對(duì)象,同時(shí)作為其他函數(shù)的參數(shù)。
function foo(callback) { callback(); } foo(function bar() { console.log("foo.bar"); }); foo(function baz() { console.log("foo.baz"); });
函數(shù)表達(dá)式被賦值給了個(gè)變量,我們可以通過該變量來訪問它
var foo = function () { console.log("foo"); }; foo();
還可以創(chuàng)建封閉的作用域,對(duì)外隱藏內(nèi)部數(shù)據(jù)。
var foo = {}; (function initialize() { var x = 10; foo.bar = function () { console.log(x); }; })(); foo.bar(); // 10; console.log(x); // "x" is not defined
我們可以看到foo.bar通過[[Scope]]可以訪問函數(shù)initialize內(nèi)部變量x,與此同時(shí),x不能被外界直接訪問。這個(gè)策略經(jīng)常被用來創(chuàng)建私有變量和隱藏輔助實(shí)體。通常initialize函數(shù)表達(dá)式的名字是被忽略的。
(function () { // initializing scope })();
這有個(gè)函數(shù)表達(dá)式,根據(jù)運(yùn)行情況來創(chuàng)建,且不污染環(huán)境變量對(duì)象。
var foo = 10; var bar = (foo % 2 == 0 ? function () { console.log(0); } : function () { console.log(1); } ); bar(); // 0
注意,ES5中有bind函數(shù),鎖定this的值。
var boundFn = function () { return this.x; }.bind({x: 10}); boundFn(); // 10 boundFn.call({x: 20}); // still 10
這通常在事件監(jiān)聽,或延遲函數(shù)(setTimeout)中被使用。
括號(hào)問題根據(jù)規(guī)范,表達(dá)式聲明(expression statement),不能以{開始,這會(huì)被當(dāng)作塊,也不能以關(guān)鍵字function開始,會(huì)被當(dāng)作函數(shù)聲明。所以,如果我們想定義一個(gè)函數(shù),用下面的方式(以function關(guān)鍵字開頭)立馬調(diào)用。
function () { ... }(); // or even with a name function foo() { ... }();
我們處理函數(shù)聲明,同時(shí)會(huì)產(chǎn)生解析錯(cuò)誤。如果我們把這樣的定義放在全局代碼中,解析器會(huì)把函數(shù)當(dāng)作聲明,因?yàn)樗?b>function關(guān)鍵字開頭,在第一種情況中,我們會(huì)得到SyntaxError,因?yàn)槲覀內(nèi)鄙俸瘮?shù)名。在第二個(gè)情況中,我們確實(shí)有了函數(shù)名,函數(shù)聲明應(yīng)該被正常的創(chuàng)建。但是我們有另一個(gè)語法錯(cuò)誤,一個(gè)組操作符中沒有表達(dá)式。所以在這個(gè)場合下,括號(hào)只是函數(shù)聲明后面的組操作符,而不是函數(shù)調(diào)用。
// "foo" is a function declaration // and is created on entering the context console.log(foo); // function function foo(x) { console.log(x); }(1); // and this is just a grouping operator, not a call! foo(10); // and this is already a call, 10
// function declaration function foo(x) { console.log(x); } // a grouping operator // with the expression (1); // another grouping operator with // another (function) expression (function () {}); // also - the expression inside ("foo"); // etc
如果我們?cè)谡Z句中有函數(shù)聲明,也會(huì)報(bào)錯(cuò)
if (true) function foo() {console.log(1)}
我們?nèi)绾蝿?chuàng)建一個(gè)函數(shù)立刻調(diào)用它?它應(yīng)該是個(gè)函數(shù)表達(dá)式,創(chuàng)建函數(shù)表達(dá)式最簡單的操作就是組操作符。如此,一個(gè)函數(shù)會(huì)在執(zhí)行時(shí)創(chuàng)建,調(diào)用,移除,如果沒有引用指向它。
(function foo(x) { console.log(x); })(1); // OK, it"s a call, not a grouping operator, 1
注意在下面的例子中,括號(hào)已經(jīng)不需要了,因?yàn)楹瘮?shù)已經(jīng)在表達(dá)式的位置了,解析器知道把它當(dāng)作函數(shù)表達(dá)式,在執(zhí)行的時(shí)候被創(chuàng)建。
var foo = { bar: function (x) { return x % 2 != 0 ? "yes" : "no"; }(1) }; console.log(foo.bar); // "yes"
正如我們所見,foo.bar是一個(gè)字符串而不是函數(shù),函數(shù)在初始化屬性時(shí)就被調(diào)用了。括號(hào)是需要的,如果我們想立刻調(diào)用函數(shù)在創(chuàng)建它后,而函數(shù)并不在表達(dá)式的位置,如果函數(shù)已經(jīng)在表達(dá)式的位置,括號(hào)就不需要了。除了括號(hào),還有其他轉(zhuǎn)換函數(shù)表達(dá)式的方法
1, function () { console.log("anonymous function is called"); }(); // or this one !function () { console.log("ECMAScript"); }(); // and any other manual // transformation
(function () {})(); (function () {}());實(shí)現(xiàn)擴(kuò)展: Function Statement
if (true) { function foo() { console.log(0); } } else { function foo() { console.log(1); } } foo(); // 1 or 0 ? test in different implementations
這里要說的是,根據(jù)規(guī)范,這種語法構(gòu)造是不正確的。因?yàn)楹瘮?shù)聲明不能出現(xiàn)在代碼塊中,而這里有if/else的代碼塊,函數(shù)聲明只能出現(xiàn)在程序級(jí)別(program level)或其他函數(shù)體內(nèi)。然而在規(guī)范的錯(cuò)誤處理中,允許了這種實(shí)現(xiàn)擴(kuò)展。但是有各自不同的實(shí)現(xiàn)方式。if/else是希望我們能根據(jù)運(yùn)行時(shí)的情況,來創(chuàng)建函數(shù),這暗示了把它們當(dāng)作函數(shù)表達(dá)式,實(shí)際上,主要的實(shí)現(xiàn)中,在進(jìn)入上下文時(shí),就會(huì)創(chuàng)建函數(shù)聲明,因?yàn)樗鼈兺?,所以最后一個(gè)會(huì)被調(diào)用,所以打印1。然后spidermonkey(一種js引擎)以不同的方式實(shí)現(xiàn)這個(gè)。
有名字的函數(shù)表達(dá)式的特點(diǎn) (NFE)如果函數(shù)表達(dá)式有名字(named function expression),縮寫NFE。由定義可知,函數(shù)表達(dá)式并不影響環(huán)境的變量對(duì)象。然后,函數(shù)表達(dá)式有時(shí)候需要在遞歸中調(diào)用自己。
(function foo(bar) { if (bar) { return; } foo(true); // "foo" name is available })(); // but from the outside, correctly, is not foo(); // "foo" is not defined
當(dāng)解釋器在代碼執(zhí)行的過程中遇到有名字的函數(shù)表達(dá)式,在創(chuàng)建函數(shù)表達(dá)式之前,解釋器創(chuàng)建了一個(gè)輔助特殊的對(duì)象,把它加在當(dāng)前的作用域鏈前面。然后它創(chuàng)建函數(shù)表達(dá)式,函數(shù)獲得[[Scope]]屬性。在那之后,有名字的函數(shù)表達(dá)式被作為一個(gè)屬性,添加到了特殊的對(duì)象上,對(duì)象的值是函數(shù)表達(dá)式的引用。最后一步是從父作用域鏈中移除特殊的對(duì)象。
specialObject = {}; Scope = specialObject + Scope; foo = new FunctionExpression; foo.[[Scope]] = Scope; specialObject.foo = foo; // {DontDelete}, {ReadOnly} delete Scope[0]; // remove specialObject from the front of scope chainNFE and SpiderMonkey
[譯者注:講述SpiderMonkey(Mozilla火狐c(diǎn)/c++)引擎如何處理有名函數(shù)表達(dá)式,不譯了]
[譯者注:講述Rhino(Mozilla java)引擎如何處理有名函數(shù)表達(dá)式,不譯了]
[譯者注:講述JScript(微軟)引擎如何處理有名函數(shù)表達(dá)式,不譯了]
通過函數(shù)構(gòu)造器創(chuàng)建函數(shù)由函數(shù)構(gòu)造器構(gòu)造的函數(shù),它的[[Scope]]只包括全局對(duì)象。
var x = 10; function foo() { var x = 20; var y = 30; var bar = new Function("console.log(x); console.log(y);"); bar(); // 10, "y" is not defined }函數(shù)創(chuàng)建算法
F = new NativeObject(); // property [[Class]] is "Function" F.[[Class]] = "Function" // a prototype of a function object F.[[Prototype]] = Function.prototype // reference to function itself // [[Call]] is activated by call expression F() // and creates a new execution context F.[[Call]] =// built in general constructor of objects // [[Construct]] is activated via "new" keyword // and it is the one who allocates memory for new // objects; then it calls F.[[Call]] // to initialize created objects passing as // "this" value newly created object F.[[Construct]] = internalConstructor // scope chain of the current context // i.e. context which creates function F F.[[Scope]] = activeContext.Scope // if this functions is created // via new Function(...), then F.[[Scope]] = globalContext.Scope // number of formal parameters F.length = countParameters // a prototype of created by F objects __objectPrototype = new Object(); __objectPrototype.constructor = F // {DontEnum}, is not enumerable in loops F.prototype = __objectPrototype return F
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/89625.html
Javascript anonymous functions Anonymous functions are functions that are dynamically declared at runtime. They’re called anonymous functions because they aren’t given a name in the same way as no...
摘要:下面是支持的事件參數(shù)方法方法這里是組件間雙向綁定的另一種方法,我們可以基于任何事件方法的執(zhí)行手動(dòng)觸發(fā)一個(gè),在中,用內(nèi)置函數(shù)手動(dòng)觸發(fā)屬性,同時(shí)回調(diào)父組件中相應(yīng)的方法,在父組件中,通過通知狀態(tài)已經(jīng)改變。翻譯自:Study Blazor .NET,轉(zhuǎn)載請(qǐng)注明。數(shù)據(jù)綁定單向綁定在blazor中單向綁定簡單而直接,無需UI刷新或渲染。下面示例展示了單向數(shù)據(jù)綁定://Counter.razor@page...
摘要:鍵是函數(shù)名,值是函數(shù)對(duì)象,函數(shù)名也用于生成。注冊(cè)一個(gè)視圖函數(shù),用裝飾器。獲取儲(chǔ)存視圖函數(shù)字典中的函數(shù)對(duì)象視圖函數(shù)類中的字典儲(chǔ)存了注冊(cè)的視圖函數(shù)名和視圖函數(shù)對(duì)象。輸出視圖函數(shù)視圖函數(shù)名重復(fù)修改解決 那天莫名其妙出了個(gè)錯(cuò)。。就順便看了看Flask路由 在flask存儲(chǔ)路由函數(shù)是以函數(shù)名為鍵,函數(shù)對(duì)象為值 class Flask: def __init__(self, *args, ...
摘要:最近開始看源碼,并將源碼解讀放在了我的計(jì)劃中。將轉(zhuǎn)為數(shù)組同時(shí)去掉第一個(gè)元素之后便可以調(diào)用方法總結(jié)數(shù)組的擴(kuò)展方法就解讀到這里了,相關(guān)源碼可以參考這部分。放個(gè)預(yù)告,下一篇會(huì)暫緩下,講下相關(guān)的東西,敬請(qǐng)期待。 Why underscore 最近開始看 underscore.js 源碼,并將 underscore.js 源碼解讀 放在了我的 2016 計(jì)劃中。 閱讀一些著名框架類庫的源碼,就好...
閱讀 1806·2021-09-23 11:34
閱讀 2525·2021-09-22 15:45
閱讀 13329·2021-09-22 15:07
閱讀 2356·2021-09-02 15:40
閱讀 4248·2021-07-29 14:48
閱讀 1149·2019-08-30 15:55
閱讀 3306·2019-08-30 15:55
閱讀 2250·2019-08-30 15:55