JavaScript之對象屬性

JavaScript之對象屬性

Object.create()繼承

ECMAScript 5定義了一個名爲Object.create()的方法,它建立一個新對象, 其中第一個參數是這個對象的原型。Object.create()提供第二個可選參數,用以對對象的屬性進行進一步描述。
Object.create()是一個靜態函數,而不是提供給某個對象調用的方法。使用它的方法很簡單,只須傳入所需的原型對象便可:javascript

var o1 = object.create({x:1, y:2}); // o1繼承了屬性x和y

inherit()函數繼承

經過原型繼承建立一個新對象前端

// inherit() 返回了一個繼承自原型對象p的屬性的新對象
//這裏使用ECMAScript 5中的0bject. create()函數(若是存在的話)
//若是不存在0bject . create(),則退化使用其餘方法
function inherit(p) {
    if (p == nul1) throw TypeError();     // p是一個對象,但不能是null
    if (Object.create)                // 若是bject. create()存在
        return object.create(p);        // 直接使用它
    var t = typeof p;                    // 不然進行進- -步檢測
    if (t !== "object" & t !== "function") throw TypeError();
    function f() {};                    // 定義一個空構造函數
    f.prototype = p;                    // 將其原型屬性設置爲p
    return new f();                        // 使用f()建立p的繼承對象
}

對象繼承後屬性的建立、訪問和修改

原型鏈:

假設要查詢對象o的屬性x,若是o中不存在x,那麼將會繼續在o的原型對象中查詢屬性x。若是原型對象中也沒有x,但這個原型對象也有原型,那麼繼續在這個原型對象的原型上執行查詢,直到找到x或者查找到一個原型是nul1的對象爲止。能夠看到,對象的原型屬性構成了一個「鏈」,經過這個「鏈」能夠實現屬性的繼承。
實例java

var o = {};
o.x = 1;
var p = inherit(o);
p.y = 2;
var q = inherit(p);
q.y = 3;
var s = q.toString();
q.x + q.y                // =>3

繼承對象的屬性賦值:

假設給對象o的屬性x賦值:web

  1. 屬性賦值首先會檢查o中是否已有x屬性;
  2. 若是o中已有x屬性,則需先斷定x屬性是o繼承的屬性仍是自有屬性,從而進一步斷定屬性x是否爲只讀屬性,若是o的原型鏈中存在該屬性但不容許修改則會致使屬性賦值失敗;segmentfault

    1. 若是o中已有屬性x,但這個屬性不是繼承來的,那麼這個賦值操做只是簡單改變o的這個已有屬性x的值,賦值成功。
    2. 若是o中已有屬性x,但這個屬性繼承而來的,屬性x容許賦值操做,那麼這個賦值操做只改變這個o的屬性x的值,而不會去修改原型鏈,賦值成功。
  3. 若是o中不存在屬性x(原型鏈中也沒有已定義的屬性x),那麼賦值操做會直接爲o建立一個新的屬性x,賦值成功。

總結:屬性賦值要麼失敗,要麼建立一個屬性,要麼在原始對象中設置屬性,不會影響到原型鏈。但有一個例外,若是o繼承自屬性x,而這個屬性是一個具備setter方法的accessor屬性。數組

屬性訪問錯誤

屬性訪問並不老是返回或設置一個值。查詢一個不存在的屬性並不會報錯,若是在對象o自身的屬性或繼承的屬性中均未找到屬性x,屬性訪問表達式o.x返回undefined。框架

這裏假設book對象有屬性"sub-title",而沒有屬性"subtitle"dom

book.subtitle; // => undefined: 屬性不存在

可是,若是對象不存在,那麼試圖查詢這個不存在的對象的屬性就會報錯。null和undefined值都沒有屬性,所以查詢這些值的屬性會報錯,接上例:函數

//拋出一個類型錯誤異常,undefined沒有length屬性
var len = book.subtitle.length;

除非肯定book和book.subtitle都是(或在行爲上)對象,不然不能這樣寫表達式book.subtitle.length,由於這樣會報錯,下面提供了兩種避免出錯的方法:工具

方法一

//一種冗餘但很易懂的方法
var len = undefined;
if (book) {
if (book. subtitle) len = book.subtitle.length;
}

方法二

//一種更簡練的經常使用方法,獲取subtitle的length屬性或undefined
var len = book && book.subtitle && book.subtitle.length;

這裏利用了&&操做符的短路特色。

刪除屬性

delete運算符能夠刪除對象的屬性。它的操做數應當是一個屬性訪問表達式。讓人感到意外的是,delete 只是斷開屬性和宿主對象的聯繫,而不會去操做屬性中的屬性。

delete book.author;            // book再也不有屬性author
delete book["main title"];     // book 也再也不有屬性"main title"

delete運算符只能刪除自有屬性,不能刪除繼承屬性(要刪除繼承屬性必須從定義這個屬性的原型對象.上刪除它,並且這會影響到全部繼承自這個原型的對象)。
舉例:

a = { p: { x: 1 } }; 
b = a.p; 
delete a.p;
b.x         //=>1,執行這段代碼以後b.x的值依然是1

因爲已經刪除的屬性的引用依然存在,所以在JavaScript的某些實現中,可能由於這種不嚴謹的代碼而形成內存泄漏。因此在銷燬對象的時候,要遍歷屬性中的屬性,依次刪除。

當delete表達式刪除成功或沒有任何反作用(好比刪除不存在的屬性)時,它返回true。若是delete後不是一個屬性訪問表達式,delete一樣返回true:

delete不能刪除那些可配置性爲false的屬性(儘管能夠刪除不可擴展對象的可配置屬性)。某些內置對象的屬性是不可配置的,好比經過變量聲明和函數聲明建立的全局對象的屬性。在嚴格模式中,刪除一個不可配置屬性會報一個類型錯誤。

屬性檢測

1. isPrototypeOf()方法

檢測一個對象是不是另外一個對象的原型。或者說一個對象是否被包含在另外一個對象的原型鏈中

實例一:

var p = {x:1};                        //定義一個原型對象
var o = Object.create(p);                //使用這個原型建立一個對象
p.isPrototypeOf(o);                    //=>true:o繼承p
Object.prototype.isPrototypeOf(p);    //=> true, p繼承自Object.prototype

實例二:

function Animal(){
    this.species = "動物";
};
var eh = new Animal();
Animal.prototype.isPrototypeOf(eh)    //=>true

綜合上面的兩個例子,咱們發如今調用isPrototypeOf()的時候有三種方式

p.isPrototypeOf(o);                    //=>true
Object.prototype.isPrototypeOf(p);
Animal.prototype.isPrototypeOf(eh)    //=>true

總結一下就是:

經過Object.create()建立的對象使用第一個參數做爲原型
經過對象直接量的對象使用Object.prototype做爲原型
經過new建立的對象使用構造函數的prototype屬性做爲原型

2. instanceof 運算符

Instanceof運算符但願左操做數是一個對象,右操做數標識對象的類。若是左側對象是右側類的實例,則表達式返回爲true,不然返回false。

javascript
    var d = new Date();
    d instanceof Date;                //=>true  d是Date的實例
    d instanceof Object;              //=>true 全部對象都是Object的實

當經過instanceof判斷一個對象是不是一個類的實例的時候,這個判斷也會包含對父類的檢測。儘管instanceof的右操做數是構造函數,但計算過程實際是檢測了對象的繼承關係。
instanceOf與isPrototypeOf簡單總結

var d = new Date();
Date.prototype.isPrototypeOf(d);    //=>true
d instanceof Date;                  //=>true
* 若是Date.prototype是d的原型,那麼d必定是Date的實例。
* 缺點爲沒法同對象來得到類型,只能檢測對象是否屬於類名
* 在多窗口和多框架的子頁面的web應用中兼容性不佳。其中一個典型表明就是instanceof操做符不能視爲一個可靠的數組檢測方法。

3. hasOwnProperty()方法

對象的hasOwnProperty()方法用來檢測給定的名字是不是對象的自有屬性。對於繼承屬性它將返回false:

var o = { x: 1 }
o.hasOwnProperty("x");            // true: o有一個自有屬性x
o.hasOwnProperty("y");            // false: o中不存在屬性y
o.hasOwnProperty("toString");        // false: toString是繼承屬性

4. in操做符

in運算符左側是屬性名,右側是對象,若是對象的自有屬性或者繼承屬性中包含這個屬性則返回true。

var o = { x = 1 }
"x" in o;                            // =>true
"y" in o;                            // =>false
"toString" in o;                     // =>true: toString是繼承屬性

5. propertyIsEnumberable()方法

只有檢測到是自有屬性且這個屬性可枚舉(enumberable attribute)爲true時它才返回true。某些內置屬性是不能枚舉的。

var o = inherit({ y: 2 });
o.x = 1;
o.propertyIsEnumerable("x");                         // true: o有一個可枚舉的自有屬性x
o.propertyIsEnumerable("y");                         // false: y是繼承來的
Object.prototype.propertyIsEnumerable("toString");   // false: 不可枚舉

枚舉屬性

1. for/in循環

能夠在循環體中遍歷對象中全部可枚舉的屬性(包括自有屬性和繼承的屬性),把屬性名稱賦值給循環變量。對象繼承的內置方法不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的(除非用下文中提到的一個方法將它們轉換爲不可枚舉的)。例如:

var o = {x:1, y:2, z:3};                //三個可枚舉的自有屬性
o.propertyIsEnumerable("toString")        // =>false, 不可枚舉
for(p in o)                                //遍歷屬性
    console.log(p);                        //輸出x、y和z,不會輸出toString

有許多實用工具庫給0bject.prototype添加了新的方法或屬性,這些方法和屬性能夠被全部對象繼承並使用。然而在ECMAScript 5標準以前,這些新添加的方法是不能定義爲不可枚舉的,所以它們均可以在for/i n循環中枚舉出來。爲了不這種狀況,須要過濾for/in循環返回的屬性,下面兩種方式是最多見的:

for(p in o) {
    if (!o. hasOwnProperty(p)) continue;        // 跳過繼承的屬性
}
for(p in o) {
    if (typeof o[p] === "function") continue;    // 跳過方法
}

2. Object.getOwnPropertyNames()方法

返回一個由指定對象的全部自身屬性的屬性名(包括不可枚舉屬性但不包括Symbol值做爲名稱的屬性)組成的數組。

var arr = ["a", "b", "c"];
console.log(Object.getOwnPropertyNames(arr).sort()); // ["0", "1", "2", "length"]

// 類數組對象
var obj = { 0: "a", 1: "b", 2: "c"};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]

// 使用Array.forEach輸出屬性名和屬性值
Object.getOwnPropertyNames(obj).forEach(function(val, idx, array) {
  console.log(val + " -> " + obj[val]);
});
// 輸出
// 0 -> a
// 1 -> b
// 2 -> c

//不可枚舉屬性
var my_obj = Object.create({}, {
  getFoo: {
    value: function() { return this.foo; },
    enumerable: false
  }
});
my_obj.foo = 1;

console.log(Object.getOwnPropertyNames(my_obj).sort()); // ["foo", "getFoo"]

3. Object.keys()方法

返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用for…in循環遍歷該對象時返回的順序一致 。若是對象的鍵-gs值都不可枚舉,那麼將返回由鍵組成的數組。

// simple array
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']

// array like object
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']

// array like object with random key ordering
var anObj = { 100: 'a', 2: 'b', 7: 'c' };
console.log(Object.keys(anObj)); // console: ['2', '7', '100']

// getFoo is a property which isn't enumerable
var myObj = Object.create({}, {
  getFoo: {
    value: function () { return this.foo; }
  } 
});
myObj.foo = 1;
console.log(Object.keys(myObj)); // console: ['foo']

屬性的特性

咱們將存取器屬性的getter和setter方法當作是屬性的特性。按照這個邏輯, 咱們也能夠把數據屬性的值一樣看作屬性的特性。所以,能夠認爲一個屬性包含一個名字和4個特性。

數據屬性的4個特性分別是它的值(value)可寫性(writable)可枚舉性(enumerable)可配置性(configurable)

存取器屬性不具備值(value) 特性和可寫性,它們的可寫性是由setter方法存在與否決定的。所以存取器屬性的4個特性是讀取(get)、寫入(set)、可枚舉性和可配置性。

爲了實現屬性特性的查詢和設置操做,ECMAScript 5中定義了一個名爲「屬性描述符」(property descriptor)的對象,這個對象表明那4個特性。描述符對象的屬性和它們所描述的屬性特性是同名的。

所以,數據屬性的描述符對象的屬性有value、writable.enumerable和configurable。存取器屬性的描述符對象則用get屬性和set屬性代替value和writable。其中writable、 enumerable和configurable都是布爾值,固然,get屬性和set屬性是函數值。

對象的三個屬性

每個對象都有與之相關的原型(prototype) 、類(class) 和可擴展性(extensible attribute)。

  1. 原型屬性
    對象的原型屬性是用來繼承屬性的,這個屬性如此重要,以致於咱們常常把「o的原型屬性」直接叫作「o的原型」。
    原型屬性是在實例對象建立之初就設置好的,經過對象直接量建立的對象使用Object. prototype做爲它們的原型。經過new建立的對象使用構造函數的prototype屬性做爲它們的原型。經過Object.create() 建立的對象使用第一-個參數(也能夠是null)做爲它們的原型。
  2. 類屬性
    對象的類屬性(class attribute) 是-一個字符串,用以表示對象的類型信息。ECMAScript3和ECMAScript 5都未提供設置這個屬性的方法,並只有一種間接的方法能夠查詢它。默認的toString()方法(繼承自Object.prototype)返回了以下這種格式的字符串:[object class]
    所以,要想得到對象的類,能夠調用對象的toString()方法,而後提取已返回字符串的第8個到倒數第二個位置之間的字符。
  3. 可拓展性
    對象的可擴展性用以表示是否能夠給對象添加新屬性。全部內置對象和自定義對象都是顯式可擴展的,宿主對象的可擴展性是由JavaScript引擎定義的。在ECMAScript 5中,全部的內置對象和自定義對象都是可擴展的,除非將它們轉換爲不可擴展的,一樣,宿主對象的可擴展性也是由實現ECMAScript 5的JavaScript引擎定義的。

序列化對象

對象序列化(serialization) 是指將對象的狀態轉換爲字符串,也可將字符串還原爲對象。ECMAScript 5提供了內置函數JSON.stringify()和JSON.parse()用來序列化和還原JavaScript對象。這些方法都使用JSON做爲數據交換格式,JSON的全稱是「JavaScript Object Notation" 一JavaScript對象表示法,它的語法和JavaScript對象與數組直接量的語法很是相近:

o = {x:1, y:{z:[false, null, ""]}};    //定義一個測試對象
s = JSON.stringify(o);                // s是'{"x":1,"y":{"z" :[false, null, ""]}}'
p = JSON.parse(s);                    // p是o的深拷貝

參考:

* 《JavaScript權威指南》第六版
* [MDN Web 文檔](https://developer.mozilla.org/zh-CN/)

推薦閱讀:
【專題:JavaScript進階之路】
JavaScript之「use strict」
JavaScript之new運算符
JavaScript之call()理解


我是Cloudy,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!
相關文章
相關標籤/搜索