之前在《JavaScript的數(shù)據(jù)類型》這篇文章里說過,Object對于JavaScript的語言結(jié)構(gòu)來說意義不一般。為什么這么說呢?你肯定在不少地方看到過這句話:JavaScript中一切皆對象。這句話沒什么錯(cuò)。你知道,JS連函數(shù)都有其屬性和方法;當(dāng)你定義一個(gè)基本類型值的字符串,可是你卻可以將這個(gè)作為基本類型值的字符串當(dāng)作對象一樣去調(diào)用substr()方法。你有想過其中的原因嗎?一切的答案都在JavaScript對象系統(tǒng)(聲明一下,JavaScript對象系統(tǒng)這個(gè)詞并不是什么統(tǒng)一定義,只是感覺在這里這么叫比較合適。)中。
請看下面這張圖。

這張結(jié)構(gòu)圖其實(shí)不規(guī)范,也并不完善,但是用來整體性地理解JS的Obejct是沒問題的。下面就結(jié)合圖來分析一下JS對象系統(tǒng)的結(jié)構(gòu)。
- 本地對象(native object)
圖中的“本地對象”這個(gè)叫法并不唯一,也可以叫做“原生對象”、“內(nèi)置對象”、“內(nèi)建對象”,但其實(shí)指的都是 native object。不必糾結(jié)這個(gè),只是翻譯不一導(dǎo)致叫法上的差異 - -,其實(shí)想表達(dá)的意思都是一致的。ECMA-262對于native object的定義為:
獨(dú)立于宿主環(huán)境的 ECMAScript 實(shí)現(xiàn)提供的對象
我們知道,“宿主”一般指瀏覽器,而“獨(dú)立于宿主環(huán)境”,也就是說與瀏覽器環(huán)境不相關(guān),這就是說:native object與瀏覽器不相關(guān),只要這個(gè)瀏覽器按照 ECMAScript 規(guī)范實(shí)現(xiàn)了JS,那就必然實(shí)現(xiàn)了規(guī)范中的所有native object。換句話說就是:本地對象(native object)是語言本身實(shí)現(xiàn)和提供的對象,和語言運(yùn)行在哪個(gè)環(huán)境無關(guān)。也就是說,不管你的JS代碼在哪里跑,你都可以new出 native object 并使用它。照此理解,我覺得其實(shí)把 native object 翻譯成“原生對象”最合意。
Object
到目前為止,我們看到的大多數(shù)引用類型值都是 Object 類型的實(shí)例;而且,Object 也是 ECMAScript 中使用最多的一個(gè)類型。雖然 Object 的實(shí)例不具備多少功能,但對于在應(yīng)用程序中存儲(chǔ)和傳輸數(shù)據(jù)而言,它們確實(shí)是非常理想的選擇?!禞avaScript高級程序設(shè)計(jì)(第3版)》
所有其他對象類型的基礎(chǔ)類型。提供了toString()等基礎(chǔ)方法。
Array
ECMAScript定義的數(shù)組類型。
Date
ECMAScript定義的日期類型。
RegExp
ECMAScript定義的正則類型。
Function
說起來 ECMAScript 中什么最有意思,我想那莫過于函數(shù)了——而有意思的根源,則在于函數(shù)實(shí)際上是對象。每個(gè)函數(shù)都是 Function 類型的實(shí)例,而且都與其他引用類型一樣具有屬性和方法?!禞3》
沒錯(cuò),F(xiàn)unction 是一種對象類型,function 是一個(gè)對象實(shí)例。見如下代碼:
console.log(Function() instanceof Function); //true(構(gòu)造函數(shù)Function()是Function類型的實(shí)例)
console.log(Function() instanceof Object); //true(構(gòu)造函數(shù)Function()當(dāng)然也是Object類型的實(shí)例)
事實(shí)上,任何一個(gè)函數(shù)都是函數(shù)對象的實(shí)例,而函數(shù)類型本身就是一種對象類型,所以:函數(shù)也是對象。
- Error 等各種錯(cuò)誤類對象
Error、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError 等錯(cuò)誤類型的對象。
-
StringNumberBoolean
為了方便操作對應(yīng)的3種基本類型值而定義的3種對象類型,使得可以像使用對象一般操作字符串、數(shù)值、布爾值。請理解一下下面的代碼:
var str = new String();
str.detail = '我是字符串,但實(shí)際上是一個(gè)對象,否則我怎么可以設(shè)置屬性?';
console.log(str); //String {detail: "我是字符串,但實(shí)際上是一個(gè)對象,否則我怎么可以設(shè)置屬性?", length: 0, [[PrimitiveValue]]: ""}
console.log(str.detail); //'我是字符串,但實(shí)際上是一個(gè)對象,否則我怎么可以設(shè)置屬性?'
前面說到過,你聲明并初始化一個(gè)字符串后,通常會(huì)理所應(yīng)當(dāng)?shù)貙⑵湟暈橐粋€(gè)對象直接使用:
var str = 'Hello World';
str = str.substr(0,5); //調(diào)用substr()方法截取字符串
console.log(str); //'Hello'
代碼沒有任何問題,我們也經(jīng)常這么寫。但稍微思考一下你就會(huì)發(fā)現(xiàn),str本身作為一個(gè)字符串,它只是一個(gè)基本數(shù)據(jù)類型的值,憑什么可以像一個(gè)對象一樣直接調(diào)用方法?講道理,str僅僅是一個(gè)值,哪里來的什么substr()方法?原因就在對象系統(tǒng)中的那個(gè)基本包裝類型對象。
實(shí)際上,每當(dāng)讀取一個(gè)基本類型值的時(shí)候,后臺(tái)就會(huì)創(chuàng)建一個(gè)對應(yīng)的基本包裝類型的對象,從而讓我們能夠調(diào)用一些方法來操作這些數(shù)據(jù) ——《J3》
書上一語道明。注意一下這句話中的“讀取”和“對應(yīng)”兩個(gè)詞?!白x取”是指進(jìn)入讀取模式,即從內(nèi)存中去讀取這個(gè)字符串值的時(shí)候;“對應(yīng)”指的是基本類型值是哪種類型,后臺(tái)自動(dòng)給你創(chuàng)建的基本包裝類型就是哪種對應(yīng)的類型,比如這里的基本類型值是String類型,那后臺(tái)創(chuàng)建的基本包裝類型也就是String包裝類型。
因?yàn)檫@一機(jī)制,上面代碼的實(shí)際執(zhí)行情況其實(shí)是這樣的:
var str = 'Hello World';
//str = str.substr(0,5); //(str進(jìn)入讀取模式,準(zhǔn)備創(chuàng)建基本包裝類型對象)
var _str = new String('Hello World');
str = _str.substr(0, 5);
_str = null; //使用完后臺(tái)會(huì)立即將該包裝對象銷毀
console.log(str); //'Hello'
這里分析的是String包裝類型,還有Number包裝類型和Boolean包裝類型也是完全一樣的原理。
- 內(nèi)置對象(單體內(nèi)置對象)(built-in object)
又來一個(gè)內(nèi)置對象?別混淆,這個(gè)是 built-in object ,上面說過的那個(gè)是 native object 。要怪就怪漢語博大精深,這兩個(gè)詞你來給我翻一下 - - ?!禞3》這本書上又將 built-in object 定義為“單體內(nèi)置對象”,我也搞不懂這個(gè)“單體”又是什么意思。。
ECMA-262對內(nèi)置對象的定義是:“由ECMAScript實(shí)現(xiàn)提供的、不依賴于宿主環(huán)境的對象,這些對象在ECMAScript程序執(zhí)行之前就已經(jīng)存在了。”意思就是說,開發(fā)人員不必顯式地實(shí)例化內(nèi)置對象,因?yàn)樗鼈円呀?jīng)實(shí)例化了?!禞3》
這是ECMA-262對于 built-in object 的定義。對比 native object 的定義:“獨(dú)立于宿主環(huán)境的 ECMAScript 實(shí)現(xiàn)提供的對象”,可以看出,built-in object 是更加特殊的 native object,built-in object 屬于 native object。
ECMA-262定義了兩種 built-in object:Global對象、Math對象。
Global對象
事實(shí)上,js中并不存在所謂的全局變量以及完全獨(dú)立的函數(shù)。沒錯(cuò),你定義的所有全局變量其實(shí)都是Global對象的屬性,你在全局作用域下定義的所有函數(shù)其實(shí)都是Global對象的方法。
ECMAScript中的 Global 對象在某種意義上是作為一個(gè)終極的“兜底兒對象” 來定義的。換句話說,不屬于任何其他對象的屬性和方法,終都是它的屬性和方法。事實(shí)上,沒有全局變量或全局函數(shù);所有在全局作用域中定義的屬性和函數(shù),都是 Global 對象的屬性。——《J3》
parseInt()、eval()、encodeURIComponent()這些函數(shù)實(shí)際上都是Global對象的方法,在使用這些方法的時(shí)候無需通過對象去調(diào)用,直接就可以使用。引文說過:“這些對象在ECMAScript程序執(zhí)行之前就已經(jīng)存在了?!边@也算 build-in object 的一個(gè)特別之處,《J3》將 build-in object 定義為“單體內(nèi)置對象”,這個(gè)“單體”是否就是指無需手動(dòng)實(shí)例化這一點(diǎn)?
Global對象的所有屬性如下表:

好吧,特殊值undefined居然是Global對象的一個(gè)屬性值 - -。乍看之下可能會(huì)有點(diǎn)詫異,不過仔細(xì)想想,我們之所以說JavaScript中一切皆對象是有原因的,你看,就連undefined都是對象的屬性值。從表中還可以看到,所有原生引用類型的構(gòu)造函數(shù)也都是Global對象的屬性。這怎么理解?其實(shí)也很好理解,比如說當(dāng)你想要?jiǎng)?chuàng)建一個(gè)日期類型的變量時(shí),直接上構(gòu)造函數(shù)var date = new Date();就可以了,可是你想過沒有,Date()構(gòu)造函數(shù)本身是一個(gè)函數(shù),而前面我們說過,JS并不存在完全獨(dú)立的函數(shù),任何函數(shù)其實(shí)都是掛在某個(gè)對象下的方法,哪怕它是一個(gè)構(gòu)造函數(shù)。而這些原生構(gòu)造函數(shù),其“掛載”的對象正是Global對象。
介紹了這么多關(guān)于Global對象的內(nèi)容,是不是還是不清楚它是個(gè)什么玩意兒?這是因?yàn)槲覀冊跒g覽器中并不能直接訪問這個(gè)對象。好在瀏覽器為我們實(shí)現(xiàn)了一個(gè)包含了Global對象的window對象。
ECMAScript 雖然沒有指出如何直接訪問 Global 對象,但 Web 瀏覽器都是將這個(gè)全局對象作為 window 對象的一部分加以實(shí)現(xiàn)的。因此,在全局作用域中聲明的所有變量和函數(shù),就都成為了 window 對象的屬性?!禞3》
window對象包含了Global對象的所有內(nèi)容,同時(shí)擴(kuò)展了大量自身需要的屬性和方法(比如常用的alert())。因此,在瀏覽器中我們可以通過window對象隨意訪問上述的屬性和方法。請看下面代碼并加以理解:
var date = new window.Date(); //window是對Global對象的擴(kuò)展實(shí)現(xiàn),構(gòu)造函數(shù)Date()確實(shí)是掛在window上面的
console.log(date); //Thu Jul 21 2016 16:52:08 GMT+0800 (中國標(biāo)準(zhǔn)時(shí)間)
你可以自己再做下測試,控制臺(tái)打印一下window,可以發(fā)現(xiàn)它確確實(shí)實(shí)包含了上述的所有屬性和構(gòu)造函數(shù)。
Math對象
Math對象是一個(gè)保存著必要的數(shù)學(xué)操作的 build-in object。里面保存著常量e、圓周率π等的值作為屬性,同時(shí)提供了取整Math.floor()、取隨機(jī)數(shù)Math.random()、求平方根等等數(shù)學(xué)方法。和Global對象一樣,這些屬性和方法都可以在任何時(shí)候任何地方直接訪問和調(diào)用,只不過其形式統(tǒng)一為:Math.***。
宿主對象(host object)
宿主對象是指宿主環(huán)境所實(shí)現(xiàn)和提供的對象。所有非本地對象(native object)都是宿主對象。我之所以在結(jié)構(gòu)圖中把自定義對象掛到了宿主對象下正是基于此,不過這并非定死的,你若將自定義對象拎出來和本地對象、宿主對象并列放也并非不可。
所謂宿主,就是指JS代碼所在的運(yùn)行環(huán)境。對于瀏覽器環(huán)境而言,我們顯示一個(gè)頁面需要HTML,所以瀏覽器實(shí)現(xiàn)了DOM對象 —— window.document;我們還需要瀏覽器本身給我們提供一些必要的東西,比如URL地址相關(guān)的location、設(shè)備屏幕相關(guān)的screen等,所以瀏覽器又為我們提供了BOM對象 —— window。這些對象,就是host object。
等等,怎么window對象又出場了?上面不是說過了,window對象是瀏覽器對Global對象的擴(kuò)展實(shí)現(xiàn),是Global對象的超集,那這玩意兒究竟該掛到build-in object上,還是宿主對象下的BOM上?答案當(dāng)然是后者??梢悦鞔_的是,window對象本就是瀏覽器所實(shí)現(xiàn)的,那它當(dāng)然屬于瀏覽器對象模型(BOM)了!只不過,window把ECMAScript規(guī)定的Global對象也給一并實(shí)現(xiàn)了而已。還有,DOM對象即是window.document,而window.document就是DOM的根節(jié)點(diǎn),從這點(diǎn)來講,我們可以理解為BOM包含了DOM 。
如此,我們終于找到最終對象了 - -,它正是 BOM —— window。你幾乎可以在這個(gè)對象中找到一切。全局變量、自定義對象、JSON對象(ECMA262-5引入為規(guī)范)、Math對象、原生構(gòu)造函數(shù)、Global的東西。。。
萬物合一,世界清靜了。
最后,可以把最開始給的圖修正更新一下了:

(注:ECMA262-5已將JSON對象納入native object)