摘要:本文整理了我對的一些理解,試將零散的知識歸總。此文非語法整理,內容偏中高級,如有紕漏或錯誤,請予以指正。原型對象原型對象通常由內置函數對象創建,它通常是一個普通對象,但也可能是函數對象。構造器的屬性中只包含全局對象參考資料
分享一篇我在2015年底做的總結筆記。本文整理了我對 JavaScript 的一些理解,試將零散的知識歸總。此文非語法整理,內容偏中高級,如有紕漏或錯誤,請予以指正。
1. 對象模型 1.1. 數據類型在 JavaScript 的語法層面,除了 undefined 和 null 一切皆對象,字面量也是對象,而 null 的類型也是對象:
"foo".substring(1); 3.1415926.toFixed(2); typeof null; // "object"
JavaScript 語言中內置了一些對象用來輔助用戶編程,它們均是 函數對象 ,如:
Function
Object
String
Number
解析引擎中創建了諸多內建類型,它們是實現 JavaScript 各類型的數據結構。
基本類型的字面量創建方式會直接調用解析引擎來創建 JavaScript 對象,它不是內置函數對象的實例:
var foo = "foo"; console.log(foo instanceof String); // false foo = new String("foo"); console.log(foo instanceof String); // true
對象(這里指語法層面的對象)、正則、數組等的字面量創建方式會調用內置函數對象來創建實例:
var foo = {}; console.log(foo instanceof Object); // true foo = new Object(); console.log(foo instanceof Object); // true
歸納如下:
1.2. 函數對象任何JS對象均需要由函數對象創建。函數對象是在普通對象的基礎上增加了內建的屬性 [[Call]] 和 [[Construct]] ,這一過程由解釋器完成,兩個屬性均指向解釋器的內建函數:[[Call]] 用于函數調用,使用操作符 () 時執行;[[Construct]] 用于構造對象,使用操作符 new 時執行。
語法層面上,函數對象也是由其它函數對象(或自己)創建的,使用 function 關鍵字可以創建用戶自定義函數對象。最上游的對象是 Function 。
當對象被創建后,解釋器為對象增加 constructor 屬性指向創建它的函數對象。
1.3. 原型對象原型對象通常由內置函數對象 Object 創建,它通常是一個普通對象,但也可能是函數對象。
任何對象都有內建屬性 [[Prototype]] 用來指向其原型對象,有些解釋器(如V8)會將其開放為 __proto__ 屬性供用戶代碼調用。函數對象有開放屬性 prototype ,用來表示通過函數對象構建的對象的原型。
以下條件總是為 true :
函數對象.prototype === 該函數對象創建的對象.__proto__
示例如下代碼的原型關系:
function Foo(){ this.foo = "foo"; }; Foo.prototype.bar = "bar"; var f1 = new Foo(); var f2 = new Foo();
對象指向原型對象的層層鏈條構成原型鏈,對象查找屬性時沿著原型鏈向上游找。
通常情況下,Function.prototype 為解析引擎創建的空函數,Object.prototype 為解析引擎創建的空對象。
1.4. 對象的關系示例如下代碼:
function Foo(){}; var foo = new Foo();
再加上內置函數對象 String,其關系如下:
有如下規律:
所有函數對象的原型最終指向 Function.prototype ;
所有普通對象(除 Object.prototype)的原型最終指向 Object.prototype,而 Object.prototype 的原型為 null ;
所有 constructor 最終指向 Function ,包括它自己;
所有原型對象的 constructor 的 prototype 指向自己,普通對象不具備該特性。
2. 執行模型函數生命周期包括:
2.1. 執行上下文執行上下文(Execution Context) 是對可執行代碼的抽象,某特定時刻下它們是等價的。發生函數調用的時候,正在執行的上下文被中斷并將新的執行上下文壓入執行上下文棧中,調用結束后(return 或 throw Error)新的上下文從棧中彈出并繼續執行之前的上下文。棧底總是全局執行上下文:
變量對象(Variable Object)是執行上下文中的一種數據結構,用來存儲:
變量
函數聲明
形參
變量對象為抽象概念,其實現分兩種情況:
一、全局執行上下文中的變量對象使用全局對象自身實現,因此全局變量可以通過相應的變量對象訪問到:
var foo = "foo" alert(window.foo);
二、函數執行上下文中的變量對象為活動對象(Activation Object),用戶代碼無法直接訪問它。
2.2. 函數執行過程函數執行前會先為其創建執行環境:
示例以下代碼的執行過程:
function foo(foo1, foo2) { var foo3 = "foo3"; var foo4 = function () {}; this.foo5 = "foo5"; function foo6() {}; foo6(); } foo("foo1", "foo2", "more");
1) 創建執行環境
該過程重點是創建 活動對象 的命名屬性:
2) 依次執行代碼
理解了函數執行過程便可以解釋局部變量的初始化時機問題:
var foo = "global"; function bar() { alert(foo); // undefined var foo = "local"; } bar();
同時也解釋了兩種函數聲明方式的區別:
foo(); // foo bar(); // TypeError: bar is not a function. function foo() { console.log("foo"); } var bar = function () { console.log("bar"); };
根據活動對象的屬性填充順序,也可以解釋:
alert(x); // function var x = 10; alert(x); // 10 x = 20; function x() {}; alert(x); // 202.2. 作用域
示例代碼如下:
var x = 1; function foo() { var y = 2; function bar() { var z = 3; alert(x + y + z); } bar(); } foo(); // 6
其作用域相關的屬性創建過程如下:
其中函數對象的內部屬性 [[Scope]] 在某些解釋器中實現為 __parent__ 并開放給用戶代碼。執行上下文中的 Scope 屬性構成 作用域鏈,其實現未必像圖中所示使用數組,也可以使用鏈表等數據結構,ECMAScript 規范對解釋器的實現機制未做規定。
變量查找時沿著作用域鏈向上游查找。例如在函數 bar 中查找 x 時,會依次查找:1)bar的活動對象;2)foo的活動對象;3)全局對象,最終在全局對象中找到。
2.3. 閉包ECMAScript 使用靜態詞法作用域:當函數對象創建時,其上層上下文數據(變量對象)保存在內部屬性 [[Scope]] 中,即函數在創建的時候就保存了上層上下文的作用域鏈,不管函數會否被調用。因此所有的函數都是一個閉包(除了 Function 構造器創建的函數)。不過,出于優化目的,當函數不使用自由變量時,引擎實現可能并不保存上層作用域鏈。
自由變量是在函數內使用的一種變量:它既不是函數的參數,也不是其局部變量。
[[Scope]] 屬性是指向變量對象的引用,同一上下文創建的多個閉包共用該變量對象。因此,某個閉包對其變量的修改會影響到其他閉包對其變量的讀取:
var fooClosure; var barClosure; function foo() { var x = 1; fooClosure = function () { return ++x; }; barClosure = function () { return --x; }; } foo(); alert(fooClosure()); // 2 alert(barClosure()); // 1
函數執行時,變量對象的屬性變化如下:
可以解釋此常犯錯的情況:
var data = []; for (var k = 0; k < 3; k++) { data[k] = function () { alert(k); }; } data[0](); // 3, 而不是 0 data[1](); // 3, 而不是 1 data[2](); // 3, 而不是 2
通過創建多個變量對象(方式一)或使用函數對象的屬性(方式二)可以解決此問題:
// 方式一 var data = []; for (var k = 0; k < 3; k++) { data[k] = (function (x) { return function () { alert(x); }; })(k); } // 方式二 var data = []; for (var k = 0; k < 3; k++) { (data[k] = function () { alert(arguments.callee.x); }).x = k; }
從理論角度講,ECMAScript 中所有的函數都是閉包。然而實踐中,以下函數才算是閉包:
即使創建它的上下文已經銷毀,它仍然存在
代碼中引用了自由變量
3. 其它 3.1. 不使用var聲明并不能創建全局變量不使用 var 關鍵字創建的只是全局對象的屬性(全局執行上下文中的變量對象使用全局對象自身實現),它并不是一個變量。可以用如下代碼檢測區別:
alert(a); // undefined alert(b); // Can"t find variable: b b = 10; var a = 20;3.2. 三種函數類型
1) 函數聲明在程序級別或另一函數的函數體:
function foo() { // ... } function globalFD() { function innerFD() {} }
2) 函數表達式在表達式的位置:
var foo = function () { // ... }; (function foo() {}); [function foo() {}]; 1, function foo() {}; var bar = (foo % 2 == 0 ? function () { alert(0); } : function () { alert(1); } ); // bar 為函數表達式: foo(function bar() { alert("foo.bar"); });
函數表達式的作用是避免對變量對象造成污染。
3)Function構造器的 [[Scope]] 屬性中只包含全局對象:
var x = 10; function foo() { var x = 20; var y = 30; var bar = new Function("alert(x); alert(y);"); bar(); // 10, "y" is not defined }
參考資料:
closures
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90056.html
摘要:推薦高性能網站建設指南高性能網站建設進階指南理由在讀完前幾本書之后我們對前端的性能和自己的代碼的效率已經達到相當的高度了,然后我們在接觸一些前端工程師的一些精髓。 WEB前端研發工程師,在國內算是一個朝陽職業,這個領域沒有學校的正規教育,大多數人都是靠自己自學成才。本文主要介紹自己從事web開發以來(從大二至今)看過的書籍和自己的成長過程,目的是給想了解JavaScript或者是剛...
摘要:推薦高性能網站建設指南高性能網站建設進階指南理由在讀完前幾本書之后我們對前端的性能和自己的代碼的效率已經達到相當的高度了,然后我們在接觸一些前端工程師的一些精髓。 WEB前端研發工程師,在國內算是一個朝陽職業,這個領域沒有學校的正規教育,大多數人都是靠自己自學成才。本文主要介紹自己從事web開發以來(從大二至今)看過的書籍和自己的成長過程,目的是給想了解JavaScript或者是剛...
摘要:推薦高性能網站建設指南高性能網站建設進階指南理由在讀完前幾本書之后我們對前端的性能和自己的代碼的效率已經達到相當的高度了,然后我們在接觸一些前端工程師的一些精髓。 WEB前端研發工程師,在國內算是一個朝陽職業,這個領域沒有學校的正規教育,大多數人都是靠自己自學成才。本文主要介紹自己從事web開發以來(從大二至今)看過的書籍和自己的成長過程,目的是給想了解JavaScript或者是剛...
閱讀 2567·2021-11-22 13:53
閱讀 4069·2021-09-28 09:47
閱讀 858·2021-09-22 15:33
閱讀 808·2020-12-03 17:17
閱讀 3315·2019-08-30 13:13
閱讀 2121·2019-08-29 16:09
閱讀 1176·2019-08-29 12:24
閱讀 2452·2019-08-28 18:14