JavaScript權威指南 - 對象

JavaScript對象能夠看做是屬性的無序集合,每一個屬性就是一個鍵值對,可增可刪。
JavaScript中的全部事物都是對象:字符串、數字、數組、日期,等等。
JavaScript對象除了能夠保持自有的屬性外,還能夠從一個稱爲原型的對象繼承屬性。對象的方法一般是繼承的屬性。這種「原型式集成」是JavaScript的的核心特徵。javascript

建立對象

第一種:對象直接量表示法建立對象。
這是最簡單的對象建立方式,對象直接量由若干key:value鍵值對屬性組成,屬性之間用逗號分隔,整個對象用花括號括起來。java

var empty = {}; //不包含任何屬性的對象
var point = { x: 3, y: 5 }; //包含兩個屬性的對象
var point2 = { x: point.x + 1, y: point.y + 1 }; //屬性值能夠是表達式
var book = {
    "main title": "JavaScript", //屬性名有空格,必須用字符串表示
    "sub-title": "The Defintive Guide", //屬性名有連字符,必須用字符串表示
    "for": "all audiences", //屬性名是保留字,必須用字符串表示
    author: { //這個屬性的值是一個對象
        firstname: "David",
        surname: "Flanagan"
    }

ECMAScript 5版本中,使用保留字屬性名能夠不用引號引發來。對象直接量最後一個屬性後的逗號自動忽略。正則表達式

第二種:經過關鍵字建立對象。
關鍵字new用來建立並初始化對象,後面跟一個構造函數。JavaScript語言核心中原始類型都包含內置構造函數,下面是內置對象建立演示。編程

var o = new Object(); //建立一個空對象,等價於 0={}
var a = new Array(); //建立一個空數組
var d = new Date(); //建立一個表明當前時間的Date對象
var r = new RegExp("js"); //建立一個正則表達式對象

除了這些內置構造函數,使用自定義構造函數來初始化新對象也很常見。數組

介紹第三種方法以前須要先簡單瞭解「原型」的概念。每個JavaScript對象(null除外)都有一個關聯對象,而且能夠從關聯對象繼承屬性。這個關聯對象就是所謂的「原型」,相似於C#中的基類。
全部經過對象直接量和構造函數建立的對象均可以經過Object.prototype得到原型對象的引用。沒有原型的對象爲數很少,Object.prototype就是其中之一。
普通對象都有原型,好比Array數組對象的原型是Array.prototype。同時,內置構造函數都具備一個繼承Object.prototype的原型。所以,經過new Array()建立的數組對象的屬性同時繼承至Array.prototype和Object.prototype,當對象出現多繼承關係時,那麼這一系列連接的原型對象就被稱做「原型鏈」。瀏覽器

第三種:使用Object.create()函數建立對象。
Object.create(Object[,Properties])是ECMAScript 5版本出現的一個靜態函數,用來建立對象。它接收兩個參數:第一個是要建立對象的原型;第二個是可選參數,用來描述對象屬性。dom

使用它建立對象,只需傳入所需原型對象便可:編程語言

var a = Object.create({ 'isLock': true });  //爲對象a指定一個原型
console.log(a.isLock); //=> true  o繼承原型對象屬性isLock
console.log(a.hasOwnProperty('isLock')); //=> false  驗證isLock並不是o的自有屬性

建立一個普通的空對象,須要傳入參數Object.prototypeide

var b = Object.create(Object.prototype);

能夠經過傳入參數null來建立沒有原型的對象,該類對象不會繼承任何東西:函數

var b = Object.create(null); //該對象不包括任何對象的基礎方法

經過原型建立對象,可使任意對象可繼承,這是一個強大的特性。好比能夠防止程序無心修改不受控制的對象。程序不直接操做對象,而是操做經過Object.create()建立的繼承對象。

查詢和設置屬性

對象屬性值能夠經過點.和方括號[]運算符來查詢或設置。

var book = { 'author': 'Tom', 'main title': 'Hello JavaScript' };
var author = book.author; //1.獲取book的「author」屬性值
var title = book["main title"]; //2.獲取book的「main title」屬性值
book.edition = 6; //3.給book建立一個「edition」屬性
book["main title"] = "ECMAScript"; //4.修改"main title"屬性值

ES3版本中,若是屬性名是關鍵字必須經過方括號的形式訪問。ES5版本放寬了要求,能夠直接在點運算符後面直接使用保留字。

關聯數組對象
上面提到能夠經過object["property"]操做對象屬性,這種語法看起來更像數組,只是這個數組元素是經過字符串索引而不是數字索引,這類數組被稱爲關聯數組。JavaScript對象都是關聯數組,經過[]訪問對象屬性時,在程序運行時能夠建立或修改它們,更有靈活性。

繼承
JavaScript對象的屬性分兩種,一種是本身定義的,被稱爲「自有屬性」。也有一些屬性是從原型對象繼承過來的。對象屬性的多繼承關係構成了原型鏈。

對象屬性在賦值前會先檢查原型鏈,以此判斷是否容許賦值操做。例如,若是對象o繼承自一個只讀屬性x,那麼對x屬性賦值是不容許的。若是容許屬性賦值,也只是在原始對象上建立或對已有的屬性賦值,而不會修改原型鏈。

JavaScript中,通常只有在查詢屬性的時候才能體會到繼承的存在,而設置屬性和繼承無關。經過這個特性能夠有選擇的覆蓋繼承的屬性。

屬性訪問錯誤
查詢一個不存在的屬性不會報錯。若是在對象自身屬性和繼承的屬性中沒有找到指定屬性,則返回undefined。經過下面一小段代碼驗證下:

var a = { name: 'admin' }; //定義一個原型對象a
var b = Object.create(a);  //定義一個對象b繼承至對象a
console.log(b.name); //=> admin  b繼承a的name屬性,正常輸出
console.log(b.age);  //=>undefined b自己和繼承對象都沒有age屬性,故輸出undefined

但有一種狀況:假如對象不存在,試圖訪問這個不存在對象的屬性時則會拋異常。例如:

console.log(c.name); //Uncaught ReferenceError: c is not defined
var d = null;
console.log(d.name); //Uncaught TypeError: Cannot read property 'name' of null

因此,這就要求咱們在訪問不肯定對象屬性時須要驗證一下。

var book = { "length": 21 };
var len = book && book.length; //這裏用&&的第三種用法代替if。
console.log(len); //=>21

刪除屬性

delete運算符能夠刪除對象的屬性,刪除成功返回true。可是delete不能刪除那些可配置型爲false的屬性。只能刪除自身屬性,不能刪除繼承屬性。

delete book.author // 返回true

刪除全局屬性時,能夠直接省略全局對象,delete後面跟上要刪除的屬性便可。

this.x=1; //建立一個全局屬性
console.log(delete x); //=>true

檢測屬性

所謂檢測屬性就是判斷某個屬性時候存在與某個對象中。通常能夠經過in運算符、hasOwnProperty()propertyIsEnumerable()方法來完成驗證工做。
in運算符判斷,若是對象自有屬性或繼承屬性包含這個屬性則返回true

var o = { "x": 5 };
console.log("x" in o); //=>true  對象o有屬性x
console.log("y" in o); //=>false 對象o沒有屬性x
console.log("toString" in o); //=>true  對象o繼承屬性toString

hasOwnProperty()方法用來檢測給定屬性是否爲對象的自有屬性,對於繼承屬性返回false

var o = { "x": 5 };
console.log(o.hasOwnProperty("x")); //=>true
console.log(o.hasOwnProperty("toString")); //=>false

propertyIsEnumerable()方法是hasOwnProperty()的加強版。只有檢測到屬性爲對象的自有屬性而且這個屬性可枚舉性時才返回true

var o = Object.create({ "y": 5 });
o.x = 6;
console.log(o.propertyIsEnumerable("x")); //=>true x爲自有屬性
console.log(o.propertyIsEnumerable("y")); //=>false y是繼承屬性
console.log(Object.prototype.propertyIsEnumerable("toString")); //=>false  toString不可枚舉

屬性存取器

ECMAScript 5版本中,對象能夠用getset關鍵字定義像C#、Java等高級語言同樣的保護屬性。這種屬性被稱爲「存取器屬性」,它是能夠繼承的。

var obj = {
    //數據屬性(可當作字段)
    data: null,
    //存取器屬性(保護屬性)
    get Data() { return this.data; },
    set Data(value) { this.data = value; }
};
obj.Data = "admin";
console.log(obj.data); //=>admin

怎麼樣,有沒有感受和JAVA中的保護屬性寫法很像。由於JavaScript自己就是一種面向對象的編程語言。

若是對象屬性同時具備getset方法,那麼它是一個可讀/寫的屬性。若是屬性只有一個get方法,那麼它是一個只讀屬性。若是屬性只有一個set方法,那麼它是一個只寫屬性,讀取只寫屬性老是返回undefined

屬性的特性

ECMAScript 3版本下對象的屬性都是否可寫、可配置和可枚舉的,可是到ECMAScript 5版本下是屬性是能夠經過一些API來標識是否爲可寫、可配置和可枚舉的。這API也就是所謂的屬性的特性。

  • 普通數據屬性的4個特性:value(值)、writable(可寫性)、enumerable(可枚舉性)、configurable(可配置性)。
  • 存儲器屬性的4個特性:get(讀取)、set(寫入)、enumerable(可枚舉性)、configurable(可配置性)。

ECMAScript 5中定義了一個Object.getOwnPropertyDescriptor()方法用來查詢對象特定屬性的特性,返回一個「屬性描述符」對象,該對象就表明對象屬性的4個特性。

var descriptor = Object.getOwnPropertyDescriptor({ length: 50 }, "length");
console.log(descriptor);  
//=> descriptor = { value: 50, writable: true, enumerable: true, configurable: true }
//------------------------------------------------------------------
var random = {
    //只讀屬性:返回一個0-255之間的隨機數
    get octet() { return Math.floor(Math.random() * 256); }
};
var descriptor1= Object.getOwnPropertyDescriptor(random,"octet");
console.log(descriptor1); 
//=> descriptor1 = Object {set: undefined, enumerable: true, configurable: true}

從名字能夠看出該方法只能獲得對象自有屬性的描述符,因此對於繼承屬性和不存在的屬性,返回undefined。要得到繼承屬性的特性,須要遍歷原型鏈。

要想設置屬性或讓新建立屬性具備某種特性,則須要調用Object.defineProperty()方法,第一個參數是要修改的對象;第二個參數是要修改的屬性;第三個是屬性描述符對象。返回值爲修改後的對象副本。

var o = {};             //建立一個空對象
Object.defineProperty(o, "x", {
    value: 1,           //定義一個x屬性,賦值爲1
    writable: true,     //可寫
    enumerable: false,  //不可枚舉
    configurable: true  //可配置
});
if (o.x) console.log(Object.keys(o)); //=> props = [] 屬性存在,可是不能枚舉
Object.defineProperty(o, "x", { writable: false }); //讓屬性x變爲只讀
o.x = 2; //試圖修改屬性x的值失敗,但不報錯
console.log(o.x); //=>1 
Object.defineProperty(o, "x", { value: 2 }); //但屬性x依然爲可配置,能夠直接修改value值特性。
console.log(o.x); //=>2
Object.defineProperty(o, "x", {  //將數據屬性修改成存取器屬性
    get: function () {
        return 0;
    }
});
console.log(o.x); //=>0

該方法一樣不能設置繼承屬性的特性。若是須要同時修改多個自有屬性的特性可使用Object.defineProperties()方法。第一個參數是要修改的對象;第二參數是一個映射表對象,它包含屬性名稱和對應屬性的描述符對象。

var p = Object.defineProperties({}, {
    x: { value: 3, writable: true, enumerable: true, configurable: true },
    y: { value: 4, writable: true, enumerable: true, configurable: true },
    r: {
        get: function () {
            return Math.sqrt(this.x * this.x + this.y * this.y);
        },
        enumerable: true,
        configurable: true
    }
});
console.log(p.r); //=>5

對象的三個屬性

原型屬性
對象的原型是用來繼承屬性的,這個屬性很是重要,以致於常常把「o的原型屬性」直接叫作「o的原型」。
原型屬性是在對象建立之初就設置好的。前面已對原型作過介紹,但這裏仍是要補充補充。

  • 經過對象直接量建立的對象使用Object.prototype做爲原型;
  • 經過new關鍵字建立的對象使用構造函數的prototype做爲原型;
  • 經過Object.create()建立的對象使用第一個參數做爲原型。

在ES5版本中,將對象傳入Object.getPrototypeOf()方法能夠查詢它的原型對象。

想要檢測一個對象是不是另外一個對象的原型可使用isPrototypeOf()方法。

var a = { x: 2 };
var b = Object.create(a);
console.log(a.isPrototypeOf(b)); //=> true
console.log(Object.prototype.isPrototypeOf(b));//=> true

類屬性
對象的類屬性是一個字符串,用來表示對象的類型信息。可是JS中沒有提供直接查詢方法,只能用一種間接的方法查詢,能夠調用對象的toString()方法,而後提取返回字符串的第8個字符至倒數第二個位置之間的字符。若是對象繼承的toString()方法重寫了,這時必須間接經過Function.call()方法調用。

function classof(o) {
    if (o === null) return "Null";
    if (o === undefined) return "Undefined";
    return Object.prototype.toString.call(o).slice(8,-1);
}

classof()能夠接收任何類型的參數,而且該函數包含了對nullundefined的特殊處理。

console.log(classof(null));   //=> "Null"
console.log(classof(1));      //=> "Number"
console.log(classof(""));     //=> "String"
console.log(classof(false));  //=> "Boolen"
console.log(classof({}));     //=> "Object"
console.log(classof(/./));    //=> "Regexp"
console.log(classof(window)); //=> "Window"(瀏覽器宿主對象類)

可擴展性
對象的可擴展行用來表示是否能夠給對象添加新屬性。ECMAScript 5版本中,全部自定義對象、內置對象和宿主對象默認支持可擴展性。下面介紹幾個檢測和設置對象可擴展性的方法以及它們之間的區別。

Object.preventExtensions()方法能將傳入對象設置爲不可擴展的。須要注意的兩點是:1.一旦對象轉爲不可擴展的,就沒法再將其轉換成可擴展的;2.若是給一個不可擴展的對象的原型添加屬性,這個不可擴展的對象一樣會繼承這些新屬性。Object.isExtensible()方法能夠檢測傳入對象的可擴展性。

Object.seal()方法能將傳入對象設置爲不可擴展的,而且將對象全部自有屬性都設置爲不可配置的。也就是說不能給這個對象添加新屬性,並且也不能刪除或配置已有屬性。對於已經密封的對象一樣不能解封,可使用Object.isSealed()方法檢測對象是否封閉。

Object.freeze()方法更「狠」,它會直接將對象凍結。除了將對象設置爲不可擴展和其屬性設置爲不可配置以外,還將對象自有屬性的全部數據屬性設置爲只讀屬性。可使用Object.isFrozen()方法檢測對象是否被凍結。

Object.preventExtensions()Object.seal()Object.freeze()三個方法都返回傳入的對象。

序列化對象

相信你們對JSON都不陌生,其實該小節就是介紹JSON序列化。所謂序列化就是JS對象和字符串之間的互相轉換,JSON做爲數據交換格式。ECMAScript 5 中提供兩個內置函數JSON.stringify()JSON.parse()用來序列化和還原JS對象。

var obj = { x: 3, y: 5 };      //定義一個測試對象
var str = JSON.stringify(obj); //str = "{"x":3,"y":5}"
obj = JSON.parse(str);         //obj = Object {x: 3, y: 5}

JSON的全稱是「JavaScript Object Notation」---JavaScript對象表示法。JSON的語法並不能表示JavaScript裏全部的全部值。支持序列化和還原的有對象、NaN、數組、字符串、無窮大數字、true\false和null。函數、RegExp、Error對象和undefined值不能序列化和還原。JSON.stringify()函數只能序列化對象可枚舉的自有屬性。日期對象序列化的結果是ISO格式的日期字符串。

參考與擴展

本篇內容源自我對《JavaScript權威指南》第6章-對象 章節的閱讀總結和代碼實踐。總結的比較粗糙,你也可經過原著或MDN更深刻了解對象。

[1] David Flanagan,JavaScript權威指南(第6版)
[2] MDN,JavaScript 參考文檔 - Array - JavaScript | MDN

相關文章
相關標籤/搜索