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

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

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

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