摘要:如果我們把這樣的定義放在全局代碼中,解析器會把函數(shù)當作聲明,因為它以關(guān)鍵字開頭,在第一種情況中,我們會得到,因為我們?nèi)鄙俸瘮?shù)名。
原文
ECMA-262-3 in detail. Chapter 5. Functions.
簡介在這篇文章中,我們將討論一個ESCMAScript對象,函數(shù)。我們將討論不同類型的函數(shù),每個類型是如何影響環(huán)境中的變量對象(variables object)以及內(nèi)部的作用域的。我們將回答以下經(jīng)常會出現(xiàn)的問題,“下面的函數(shù)有什么不同嗎?”[譯者注:當進入一個函數(shù)時,會創(chuàng)建一個對象,里面保存了函數(shù)運行需要的各種變量]
var foo = function () { ... };
function foo() { ... }
(function () { ... })();函數(shù)類型 函數(shù)聲明
函數(shù)聲明(Funcation Declaration),縮寫FD,是一個函數(shù):
必須擁有一個名字
在源碼中的位置,要么在程序級別,要么在其他函數(shù)體的體內(nèi)
在進入上下文時創(chuàng)建
會影響變量對象
用以下方式聲明
function exampleFunc() { ... }
這個類型的函數(shù)的主要特點是它影響變量對象(它們被存在環(huán)境的變量對象中)。這導致了第二個重要的點,在代碼執(zhí)行期間,他們已經(jīng)被用了,因為當一進入環(huán)境后,函數(shù)聲明就被保存在環(huán)境的變量對象中,在代碼開始執(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ù)表達式函數(shù)表達式(Function Expression),縮寫FE,是一個函數(shù)
在源碼中可以在表達式位置被定義
可以選擇不要名字
對環(huán)境的變量對象沒有影響
在代碼執(zhí)行的階段被創(chuàng)建
常見的賦值表達式
var foo = function () { ... };
也可以給函數(shù)個名字
var foo = function _foo() { ... };
在這值得注意的是,在函數(shù)表達式的外面通過變量foo訪問函數(shù),而在函數(shù)內(nèi)部,例如遞歸調(diào)用,用_foo訪問。函數(shù)表達式總在表達式的位置,例如以下的例子都是函數(shù)表達式
// 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ù)表達式在代碼執(zhí)行的階段才會創(chuàng)建,并不會被存在環(huán)境變量對象中
// 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ù)表達式?是為了不污染環(huán)境變量對象,同時作為其他函數(shù)的參數(shù)。
function foo(callback) { callback(); } foo(function bar() { console.log("foo.bar"); }); foo(function baz() { console.log("foo.baz"); });
函數(shù)表達式被賦值給了個變量,我們可以通過該變量來訪問它
var foo = function () { console.log("foo"); }; foo();
還可以創(chuàng)建封閉的作用域,對外隱藏內(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,與此同時,x不能被外界直接訪問。這個策略經(jīng)常被用來創(chuàng)建私有變量和隱藏輔助實體。通常initialize函數(shù)表達式的名字是被忽略的。
(function () { // initializing scope })();
這有個函數(shù)表達式,根據(jù)運行情況來創(chuàng)建,且不污染環(huán)境變量對象。
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)中被使用。
括號問題根據(jù)規(guī)范,表達式聲明(expression statement),不能以{開始,這會被當作塊,也不能以關(guān)鍵字function開始,會被當作函數(shù)聲明。所以,如果我們想定義一個函數(shù),用下面的方式(以function關(guān)鍵字開頭)立馬調(diào)用。
function () { ... }(); // or even with a name function foo() { ... }();
我們處理函數(shù)聲明,同時會產(chǎn)生解析錯誤。如果我們把這樣的定義放在全局代碼中,解析器會把函數(shù)當作聲明,因為它以function關(guān)鍵字開頭,在第一種情況中,我們會得到SyntaxError,因為我們?nèi)鄙俸瘮?shù)名。在第二個情況中,我們確實有了函數(shù)名,函數(shù)聲明應該被正常的創(chuàng)建。但是我們有另一個語法錯誤,一個組操作符中沒有表達式。所以在這個場合下,括號只是函數(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
如果我們在語句中有函數(shù)聲明,也會報錯
if (true) function foo() {console.log(1)}
我們?nèi)绾蝿?chuàng)建一個函數(shù)立刻調(diào)用它?它應該是個函數(shù)表達式,創(chuàng)建函數(shù)表達式最簡單的操作就是組操作符。如此,一個函數(shù)會在執(zhí)行時創(chuàng)建,調(diào)用,移除,如果沒有引用指向它。
(function foo(x) { console.log(x); })(1); // OK, it"s a call, not a grouping operator, 1
注意在下面的例子中,括號已經(jīng)不需要了,因為函數(shù)已經(jīng)在表達式的位置了,解析器知道把它當作函數(shù)表達式,在執(zhí)行的時候被創(chuàng)建。
var foo = { bar: function (x) { return x % 2 != 0 ? "yes" : "no"; }(1) }; console.log(foo.bar); // "yes"
正如我們所見,foo.bar是一個字符串而不是函數(shù),函數(shù)在初始化屬性時就被調(diào)用了。括號是需要的,如果我們想立刻調(diào)用函數(shù)在創(chuàng)建它后,而函數(shù)并不在表達式的位置,如果函數(shù)已經(jīng)在表達式的位置,括號就不需要了。除了括號,還有其他轉(zhuǎn)換函數(shù)表達式的方法
1, function () { console.log("anonymous function is called"); }(); // or this one !function () { console.log("ECMAScript"); }(); // and any other manual // transformation
(function () {})(); (function () {}());實現(xiàn)擴展: 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)造是不正確的。因為函數(shù)聲明不能出現(xiàn)在代碼塊中,而這里有if/else的代碼塊,函數(shù)聲明只能出現(xiàn)在程序級別(program level)或其他函數(shù)體內(nèi)。然而在規(guī)范的錯誤處理中,允許了這種實現(xiàn)擴展。但是有各自不同的實現(xiàn)方式。if/else是希望我們能根據(jù)運行時的情況,來創(chuàng)建函數(shù),這暗示了把它們當作函數(shù)表達式,實際上,主要的實現(xiàn)中,在進入上下文時,就會創(chuàng)建函數(shù)聲明,因為它們同名,所以最后一個會被調(diào)用,所以打印1。然后spidermonkey(一種js引擎)以不同的方式實現(xiàn)這個。
有名字的函數(shù)表達式的特點 (NFE)如果函數(shù)表達式有名字(named function expression),縮寫NFE。由定義可知,函數(shù)表達式并不影響環(huán)境的變量對象。然后,函數(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
當解釋器在代碼執(zhí)行的過程中遇到有名字的函數(shù)表達式,在創(chuàng)建函數(shù)表達式之前,解釋器創(chuàng)建了一個輔助特殊的對象,把它加在當前的作用域鏈前面。然后它創(chuàng)建函數(shù)表達式,函數(shù)獲得[[Scope]]屬性。在那之后,有名字的函數(shù)表達式被作為一個屬性,添加到了特殊的對象上,對象的值是函數(shù)表達式的引用。最后一步是從父作用域鏈中移除特殊的對象。
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/c++)引擎如何處理有名函數(shù)表達式,不譯了]
[譯者注:講述Rhino(Mozilla java)引擎如何處理有名函數(shù)表達式,不譯了]
[譯者注:講述JScript(微軟)引擎如何處理有名函數(shù)表達式,不譯了]
通過函數(shù)構(gòu)造器創(chuàng)建函數(shù)由函數(shù)構(gòu)造器構(gòu)造的函數(shù),它的[[Scope]]只包括全局對象。
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)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.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í)行手動觸發(fā)一個,在中,用內(nèi)置函數(shù)手動觸發(fā)屬性,同時回調(diào)父組件中相應的方法,在父組件中,通過通知狀態(tài)已經(jīng)改變。翻譯自:Study Blazor .NET,轉(zhuǎn)載請注明。數(shù)據(jù)綁定單向綁定在blazor中單向綁定簡單而直接,無需UI刷新或渲染。下面示例展示了單向數(shù)據(jù)綁定://Counter.razor@page...
摘要:鍵是函數(shù)名,值是函數(shù)對象,函數(shù)名也用于生成。注冊一個視圖函數(shù),用裝飾器。獲取儲存視圖函數(shù)字典中的函數(shù)對象視圖函數(shù)類中的字典儲存了注冊的視圖函數(shù)名和視圖函數(shù)對象。輸出視圖函數(shù)視圖函數(shù)名重復修改解決 那天莫名其妙出了個錯。。就順便看了看Flask路由 在flask存儲路由函數(shù)是以函數(shù)名為鍵,函數(shù)對象為值 class Flask: def __init__(self, *args, ...
摘要:最近開始看源碼,并將源碼解讀放在了我的計劃中。將轉(zhuǎn)為數(shù)組同時去掉第一個元素之后便可以調(diào)用方法總結(jié)數(shù)組的擴展方法就解讀到這里了,相關(guān)源碼可以參考這部分。放個預告,下一篇會暫緩下,講下相關(guān)的東西,敬請期待。 Why underscore 最近開始看 underscore.js 源碼,并將 underscore.js 源碼解讀 放在了我的 2016 計劃中。 閱讀一些著名框架類庫的源碼,就好...
閱讀 1780·2021-09-23 11:34
閱讀 2500·2021-09-22 15:45
閱讀 13133·2021-09-22 15:07
閱讀 2283·2021-09-02 15:40
閱讀 4182·2021-07-29 14:48
閱讀 1111·2019-08-30 15:55
閱讀 3268·2019-08-30 15:55
閱讀 2216·2019-08-30 15:55