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