ECMAScript 5定義了一個名爲Object.create()的方法,它建立一個新對象, 其中第一個參數是這個對象的原型。Object.create()提供第二個可選參數,用以對對象的屬性進行進一步描述。
Object.create()是一個靜態函數,而不是提供給某個對象調用的方法。使用它的方法很簡單,只須傳入所需的原型對象便可:javascript
var o1 = object.create({x:1, y:2}); // o1繼承了屬性x和y
經過原型繼承建立一個新對象前端
// 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
若是o中已有x屬性,則需先斷定x屬性是o繼承的屬性仍是自有屬性,從而進一步斷定屬性x是否爲只讀屬性,若是o的原型鏈中存在該屬性但不容許修改則會致使屬性賦值失敗;segmentfault
總結:屬性賦值要麼失敗,要麼建立一個屬性,要麼在原始對象中設置屬性,不會影響到原型鏈。但有一個例外,若是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的屬性(儘管能夠刪除不可擴展對象的可配置屬性)。某些內置對象的屬性是不可配置的,好比經過變量聲明和函數聲明建立的全局對象的屬性。在嚴格模式中,刪除一個不可配置屬性會報一個類型錯誤。
檢測一個對象是不是另外一個對象的原型。或者說一個對象是否被包含在另外一個對象的原型鏈中
實例一:
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屬性做爲原型
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操做符不能視爲一個可靠的數組檢測方法。
對象的hasOwnProperty()方法用來檢測給定的名字是不是對象的自有屬性。對於繼承屬性它將返回false:
var o = { x: 1 } o.hasOwnProperty("x"); // true: o有一個自有屬性x o.hasOwnProperty("y"); // false: o中不存在屬性y o.hasOwnProperty("toString"); // false: toString是繼承屬性
in運算符左側是屬性名,右側是對象,若是對象的自有屬性或者繼承屬性中包含這個屬性則返回true。
var o = { x = 1 } "x" in o; // =>true "y" in o; // =>false "toString" in o; // =>true: toString是繼承屬性
只有檢測到是自有屬性且這個屬性可枚舉(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: 不可枚舉
能夠在循環體中遍歷對象中全部可枚舉的屬性(包括自有屬性和繼承的屬性),把屬性名稱賦值給循環變量。對象繼承的內置方法不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的(除非用下文中提到的一個方法將它們轉換爲不可枚舉的)。例如:
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; // 跳過方法 }
返回一個由指定對象的全部自身屬性的屬性名(包括不可枚舉屬性但不包括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"]
返回一個由一個給定對象的自身可枚舉屬性組成的數組,數組中屬性名的排列順序和使用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)。
對象序列化(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,年輕的前端攻城獅一枚,愛專研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流前端各類問題!