JavaScript 沒有類的概念,因此它的對象與基於類的語言中的對象有所不同。筆者主要參考《JS 進階程式設計》、《JS 權威指南》和《JS 精粹》 本文由淺入深的講解了對象的概念,特性,和使用,由於筆者水平的確有限,有些觀點也是邊理解,邊查證,邊分享。 希望大家都能感受到分享的樂趣,祝我們共同進步,請大家不吝交流。 目錄對象是什嗎?對象有什麼特性?對象有什麼用?如何建立對象?對象直接量Factory 方法建立對象通過 new 建立對象對象屬性的查詢與設定(檢索與更新)刪除對象的屬性反射枚舉屬性對象的三個屬性序列化對象寫在後面的話對象是什嗎?ECMA-262(JS的標準)把對象定義為“無序屬性的集合,其屬性包含基本值、對象和函數”。 JavaScript中的對象是可變的鍵控集合(keyed collections)(注意,是可變的喲)。 對象是屬性和方法的容器(貌似在任何一門支援對象的語言中都是),每個屬性都擁有名字和值。 屬性的名字(key)可以包括Null 字元串在內的任一字元串,屬性值可以是除undefined值之外的任何值。 對象有什麼特性?動態性:可以新增也可以刪除屬性,但常用來類比靜態對象和靜態類型語言中的結構體,有時還用做字串的集合(忽略名值對的值)。無序性:在枚舉對象的屬性時,屬性是沒有順序的。無類型:對象適合用於彙集和管理資料。對象可以包含其他對象,所以它們很容易表示樹形結構或圖形結構。原型鏈特徵:允許對象繼承另一個對象的屬性。正確地使用它能減少對象初始化時消耗的時間和記憶體。對象有什麼用?對象最常用的用法就是建立、設定、尋找、刪除、檢測和枚舉它的屬性。另外還有一些進階操作。 如何建立對象?可以通過對象直接量、Factory 方法、new 關鍵字來建立對象。(另外《權威指南》 上說Object.create() 函數也可以建立自訂對象) 對象直接量對象直接量是由若干名值對組成的映射表,名值對中間用冒號隔開,名值對之間用逗號分隔,整個映射表用花括弧括起來。 屬性名稱可以是JavaScript 標識符或者字串直接量(包括Null 字元串),屬性的值可以是任意類型的JavaScript 運算式,運算式的值就是這個屬性的值。 對象直接量是一個運算式,該運算式每次運算時都建立並初始化一個新的對象,也都會計算它每個屬性的值(注意在迴圈體中使用對象直接量)。 複製代碼var tony = { name: 'tony', age: 23, contacts: { tel: '12345566', QQ: '55555' }};複製代碼其中 ECMAScript 5 中 字面量最後一個名值對後面的逗號將被忽略,但是IE中會報錯。 Factory 方法建立對象用函數來封裝以特定介面建立對象的細節。 複製代碼function createPerson (name, age, job) { var o = new Object (); o.name = name; o.age = age; o.job = job; o.sayHello = function () { alert("hello, I'm "+this.name); }; return o;} var tony = createPerson('tony', 23, 'engneer');複製代碼Factory 方法雖然解決了建立多個相似對象的問題,但是卻沒有解決對象識別的問題,不知道一個對象屬於那種類型,沒有對象的類型資訊。 通過 new 建立對象new 運算子 建立並初始化一個對象,關鍵字 new 後面跟一個函數調用(該函數被稱為建構函式,建構函式用來初始化一個新對象)。JavaScript語言核心中的原始類型都包含內建建構函式。 var o = new Object(); // 建立一個Null 物件 {} 還有 Array() Date() RegExp()除了內建建構函式外,還可以自訂建構函式,例如: 複製代碼function Person /*首字母大寫主要是區別普通函數*/ (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayHello = function () { alert("Hello I'm "+ this.name); };}var tony = new Person ('tony', 23, 'engneer');var googny = new Person ('googny', 23, 'manager');googny.sayHello == tony.sayHello; // false複製代碼要建立 Person 的新執行個體,必須使用 new 操作符,這種方式調用建構函式實際上經曆四個步驟: 建立一個新對象;將建構函式的範圍賦給新對象(this 就指向這個新對象)執行建構函式中的代碼返回新對象OK,上面的例子的確可以建立並初始化新的對象,有心人可能會注意到我最後三行代碼,googny.sayHello 和 tony.sayHello 不相等。 也就是說該函數在每個對象中都有一份拷貝,熟悉類C語言的童鞋都知道,類中的方法方法只有一份拷貝,各個對象共用這份拷貝,調用過程中將 this 隱式的傳遞給該方法。 這種情況可以在 JavaScript 中實現嗎? 是的,可以 複製代碼var sayHello = function () { alert("Hello I'm "+ this.name); }; function Person /*首字母大寫主要是區別普通函數*/ (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayHello = sayHello;}var tony = new Person ('tony', 23, 'engneer');var googny = new Person ('googny', 23, 'manager');googny.sayHello == tony.sayHello; // true複製代碼從返回結果可以看出,兩個對象共用一段代碼,但是這樣看起來封裝性不是特別的好,用起來也憋手蹩腳的,《悟透 JavaScript》 中用“不優雅”來形容。 JavaScript 的設計者也覺得上面的兩種做法是浪費“糧食”(記憶體),而且醜陋,於是“原型” 應運而生。 什麼是原型? 我們建立的每個函數都有一個 prototype (原型) 屬性, 這個屬性是一個指標,指向一個對象,而這個對象的用途就是用來包含可以有特定類型的所有執行個體共用的屬性和方法。 使用原型對象的好處就是可以讓所有執行個體共用它所包含的屬性和方法。 複製代碼function Person /*首字母大寫主要是區別普通函數*/ (name, age, job) { this.name = name; this.age = age; this.job = job;} Person.prototype.sayHello = function () { alert("Hello I'm "+ this.name);};var tony = new Person ('tony', 23, 'engneer');var googny = new Person ('googny', 23, 'manager');googny.sayHello == tony.sayHello; // true複製代碼在預設情況下,所有原型對象都會自動擷取一個constructor(建構函式)屬性,這個屬性包含一個指向 prototype 屬性所在函數的指標。 很繞是吧,看一張清爽無比的高清大圖 // 為了說明Prototype 中 constructor 指向Person函數var some = new Person.prototype.constructor('somebody',25,'boss');console.log(some); 當然,上述小代碼只是為了說明問題,該屬性主要是用來判斷對象的類型。 tony.constructor == Person; //constructor先從對象的屬性中找,沒有的話,再從tony 的原型中找。tony instanceof Person;組合使用建構函式和原型模式來建立新對象 複製代碼function Person (name, age, job) { this.name = name; this.age = age; this.job = job;} Person.prototype = { constructor : Person, //以對象形式給prototype 賦值,要想有類型判斷,則必須指定constructor sayHello : function () { alert("hello, my name is "+this.name); }}; var tony = new Person ('tony', 23, 'student'); tony instanceof Person;複製代碼該方式是目前使用最廣泛,認同度最高的一種建立自訂類型的方法。 對象屬性的查詢與設定(檢索與更新)要檢索對象裡包含的值,可以採用在[]尾碼中括住一個字串運算式的方法。如果字串運算式是一個字串字面量,而且它是一個合法的JavaScript 標識符而不是保留字,那麼也可以用 . 標記法代替。 複製代碼var tony = { "first-name" : 'tony tong', departure : 'xidian university', contacts : { tel : '123456767', QQ : '55555' }}; tony ['first-name'] ; // '-'標識符中不合法喲tony.contacts.tel ; 複製代碼如果你嘗試查詢一個並不存在的成員屬性的值,將返回 undefined || 運算子可以用來填充預設值: var middle = tony["middle-name"] || "(none)"嘗試從undefined 的成員屬性中取值會導致TypeError 異常,這時可以通過 && 運算子來避免錯誤。 tony.age; // undefined tony.age.birth // TypeError 異常tony.age && tony.age.birth //undefined對象裡的值可以通過指派陳述式來更新,如果屬性名稱已經存在於對象裡,那麼這幾個屬性的值就會被替換。 如果之前對象中沒有那個屬性,那麼該屬性就被擴充到對象中。 刪除對象的屬性delete 運算子可以用來刪除對象的屬性。如果對象包含該屬性,那麼該屬性就會被移除,它不會觸及原型鏈中的任何對象。 刪除對象的屬性可能會讓來自原型鏈中的屬性顯現出來。 複製代碼function Person (name, age, job) { this.name = name; this.age = age; this.job = job; this.sayHello = function () { alert("hello, I'm a person!"); };} Person.prototype = { constructor : Person, sayHello : function () { alert("hello, my name is "+this.name); }}; var tony = new Person ('tony', 23, 'student'); tony.sayHello(); // hello, I'm a person!delete tony.sayHello;tony.sayHello(); // hello, my name is tony複製代碼反射反射指的是程式在運行時能夠擷取自身的資訊。例如一個對象能夠在運行時知道自己有哪些方法和屬性。 檢查對象並確定對象有什麼屬性是很容易的事兒,只要試著去檢索該屬性並驗證取得的值。 typeof 操作符對確定屬性的類型很有協助; typeof tony.sayHello // 'function'typeof tony.contacts // 'object'使用 hasOwnProperty 方法來檢查對象是否擁有該屬性, 如果對象擁有專屬的屬性, 它將返回true, 該方法不會檢查原型鏈 枚舉屬性for in 語句用來遍曆一個對象中的所有屬性名稱。該枚舉過程將會列出所有的屬性—— 包括函數和可能不關心的原型中的屬性——所以有必要過濾掉那些不想要的屬性, 最為常用的過濾器是 hasOwnProperty 方法,以及使用typeof 來排除值為函數的屬性 var property;for (property in tony) { if ( tony.hasOwnProperty(property) && typeof tony[property] != 'function') { console.log(property); }}屬性名稱出現的順序是不確定的。 對象的三個屬性每一個對象都有與之相關的原型(prototype)、類(class)、和可擴充性(extensible attribute) 原型屬性是一個指向原型對象的指標,主要用來在同一類對象中共用屬性和方法,還可以用來繼承屬性。 對象的類屬性是一個字串,用以表示對象的類型資訊,只有一種間接的方法來查詢它。 預設的toString()方法,返回如下格式的字串: [object class]因此要想獲得對象的類,調用對象的toString 方法,然後提取字串 從第8個字元到倒數第二個字元之間的字串。 不過讓人棘手的是,很多個物件的toString() 方法都被重寫了,為了能調用正確的toString() 版本,必須使用函數的間接調用模式(參考深入淺出 JavaScript 函數 中的間接調用模式) 例如: function classOf (o) { if (o == null) return 'Null'; if (o == undefined) return 'Undefined'; return Object.prototype.toString.call(o).slice(8,-1);}該函數可以接受任何類型的參數。 對象的可擴充性用以表示對象是否可以給對象添加新屬性。所有內建對象和自訂對象都是顯式可擴充性的。 宿主對象的可擴充性是由JavaScript 引擎定義的,在ECMAScript 5 中,所有的內建對象和自訂對象預設都是可擴充的,除非將它們轉換為不可擴充的。 通過將對象傳入 Object.esExtensible(),來判斷該對象是否是可擴充的。 如果想將對象轉換成不可擴充的,則需要調用Object.preventExtensions(),將待轉換的對象作為參數傳進去。(此過程是無法復原的) 如果想將對象所有的自有屬性設定為不可配置的,也就是說不能增加新屬性,刪除、設定它的已有屬性,則需要調用Object.seal()。不過它已有的可寫屬性依然可以設定。 Object.freeze() 將對象所有屬性設定為唯讀。 Object.isSealed() 檢測是否封閉,Object.isFrozen() 來檢測對象是否被凍結。 序列化對象對象序列化,是指將對象的狀態轉換為字串,也可將字串還原成對象。 ECMAScript 5 提供了內建函數JSON.stringify() 和 JSON.prase()用來序列化和還原序列化JavaScript 對象。 複製代碼var o = { x: 1, y: { z: [false, null, ""] }}; //定義一個測試對象 var s = JSON.stringify (o); //s 是一個字串console.log(s);var p = JSON.parse(s); //p是o的深拷貝console.log(p);複製代碼寫在後面的話其實關於JavaScript 對象的話題遠遠不止這些,比如一些比較進階的話題:對象屬性的特性,對象屬性getter和setter等 有興趣的讀者可以找相關資料查閱。 謝謝大家,看著覺的說得過去的話,點個推薦吧。