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