簡介編程
在基於原型的對象系統中,至少包含如下規則:數組
(1) 一切皆對象瀏覽器
(2) 建立一個對象的方式是找到一個原型對象,並克隆它,而不是經過實例化類函數
(3) 對象會記住它的原型this
(4) 對象會繼承它的原型鏈中的全部內容spa
建立對象:prototype
在 JS 中能夠經過對象字面量或者構造函數來建立對象code
//示例一:經過對象字面量建立對象 var boy = { name: 'Bob', sex: 'male' }
//示例二:經過構造函數建立對象 function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person('Bob', 'male');
對象的屬性:對象
在 JS 的對象中有兩種屬性:數據屬性和訪問器屬性。blog
數據屬性即常見的鍵值對映射,一個屬性名對應一個屬性值,另外有4個用於描述其行爲的描述符:
configurable(是否可配置):表示可否經過 delete 刪除屬性從而從新定義屬性,可否修改屬性的描述符,或者可否把屬性修改成訪問器屬性,默認爲 false
enumerable(是否可枚舉):表示可否經過 for-in 循環返回屬性,默認爲 false
writable(是否可寫入):表示可否修改屬性值,默認爲 false
value:用於存儲對應這個屬性的屬性值,默認爲 undefind
若是像前面 "示例1"、"示例2" 中那樣,直接定義對象的屬性,那麼它們的 "configurable"、"enumerable"、"writable" 都被默認設置爲 true,"value" 則被設置爲指定的值,若是要修改屬性的描述符,必須使用 Object.defineProperty() 方法,這個方法有三個參數:"屬性所在的對象"、"屬性名"、"描述符對象",例如:
var obj = { test: '123' }; Object.defineProperty(obj,'test',{ writable: false }); obj.test = '456'; console.log(obj.test); //輸出123
此外,還能夠經過 Object.create() 方法,在建立對象的時候修改屬性的描述符,該方法有兩個參數:"原型對象"、"描述符對象",例如:
//示例三:經過 Object.create() 方法建立對象 var obj = { test: '123' }; var newObj = Object.create(obj,{ test: { writable: false, value: '12' } }); newObj.test = '456'; console.log(newObj.test); //輸出12
注意:若是這裏將描述符對象中的 "value" 屬性省略,那麼 "newObj.test" 的值將爲undefind,並不會繼承原型 "Obj.test" 的值(只要指定了描述符對象),能夠理解新的描述符對象將原型中的覆蓋了,而 "value" 的默認值爲 undefind。
訪問器屬性不能直接定義,必須經過 Object.defineProperty() 方法來定義。它也有4個用於描述其行爲的描述符:
configurable(是否可配置):表示可否經過 delete 刪除屬性從而從新定義屬性,可否修改屬性的描述符,或者可否把屬性修改成數據屬性,默認爲false
enumerable(是否可枚舉):表示可否經過 for-in 循環返回屬性,默認爲false
get:在讀屬性前調用的函數,默認爲undefind
set:在寫屬性前調用的函數,默認爲undefind
var obj = { test : '123' }; Object.defineProperty(obj,'tt',{ get: function() { console.log('讀屬性'); return this.test; }, set: function(newValue) { console.log('寫屬性'); this.test = newValue; } }); console.log(obj.tt); obj.tt = '456'; console.log(obj.tt); //讀屬性 //123 //寫屬性 //讀屬性 //456
注意:若是隻單獨指定了 "get" 或只單獨指定了 "set",表示只讀或只寫;不要在 "get" 方法裏面又讀本屬性,也不要在 "set" 方法裏面又寫本屬性,否則會致使無限遞歸循環;一個屬性只能定義爲數據屬性或訪問屬性中的一種,若是同時定義則會報錯,可是若是指定了 "configurable" 爲 true,能夠先經過 "delete" 操做符刪除屬性再從新定義。
delete 操做符:
用於刪除一個對象上的屬性。在嚴格模式下,若是對象是一個不可配置的屬性 (configurable 爲 false),刪除會拋出異常,非嚴格模式下返回 false,其餘狀況返回 true。
注意:一、delete 不能刪除從原型繼承來的屬性;
二、delete 也不能刪除內置對象的內置屬性,例如 Math.PI;
三、delete 刪除數組元素時,數組的長度並不會變小,刪除的值會被 undefind 代替;
一切皆對象
事實上,在 JS 中並不是一切皆對象,這只是一種籠統的說法,因爲 JS 引入了兩種數據類型:基本類型 ( Undefind、 Null、 Boolean、 Number 和 String ) 和對象類型 ( Object ),對象類型是對象天然沒必要多說,問題在於基本類型是對象嗎?咱們先上字符串類型來講明一下:
var str = 'Make life better'; console.log(str.length); //輸出 16
按理說 "str" 變量只是一個字符串,可是它卻使用了對象纔有的 "length" 屬性,輸出了字符串的長度,所以這裏咱們有理由把字符串類型當作對象,稱爲 "包裝對象"。這個對象是臨時的,也就是說只有在讀取它的屬性的時候 JS 纔會把這個字符串經過 new String() 方式建立成一個字符串對象,一旦引用結束這個對象就被銷燬了,換句話說 "包裝對象" 的屬性是 "只能讀,不能寫" 的。同理 "Boolean" 和 "Number" 在讀取屬性的時候也能夠經過本身的構造函數來建立本身的一個包裝對象,並像對象同樣引用各自的屬性。
其次,"null" 表示 "空值",對 "null" 執行 "typeof" 操做,輸出結果爲 "Object",因此咱們也能夠把 "null" 當作一個對象,稱爲 "空對象"
最後,"undefind" 表示 "未定義",當咱們對變量只聲明沒有初始化時,輸出 "undefind",或者引用一個不存在的屬性時,輸出也爲 "undefind",對 "undefind" 執行 "typeof" 操做的輸出結果爲 "undefind",這麼說來 "undefind" 其實並不屬於對象範疇
建立一個對象的方式是找到一個原型對象,並克隆它,而不是經過實例化類
在 JS 中,"Object.prototype" 是全部對象的原型,咱們並不須要關心克隆的細節,由於這是引擎內部負責實現的。咱們所須要作的只是顯式地調用 var obj1 = {}; 或者 var obj2 = new Object(),此時,引擎內部會從 "Object.prototype" 上面克隆一個對象出來,做爲新對象的原型。
示例一:
var obj1 = {}; var obj2 = new Object(); console.log(Object.getPrototypeOf(obj1) === Object.prototype); //輸出true console.log(obj2.__proto__ === Object.prototype); //輸出true
每一個對象都具備 "__proto__"(先後兩個下劃線) 屬性,它指向該對象的原型,可是它只是一個內部屬性,而不是一個正式的對外 API,原則上是不能訪問的,這是因爲不少瀏覽器的支持,才把這個屬性暴露出來了。在ES5中使用 "Object.getPrototypeOf()" 獲取一個對象的原型,在ES6中可使用 "Object.setPrototypeOf()" 設置一個對象的原型。所以,在這裏二者的做用都是同樣的,都是獲取對象的原型,而且它們的原型都是 "Object.prototype"。
只有函數纔有 "prototype" 屬性,例如 "Object.prototype",對於函數而言,"__proto__" 屬性指向它本身的原型, "prototype" 屬性則是經過這個函數構造出來的實例的原型,能夠理解爲這樣一條原型鏈,"__proto__" 老是指向原型鏈的頂端,而函數剛好能夠延長原型鏈,因而它將本身 "prototype" 屬性指向的對象壓入原型鏈的頂端,天然它構造出來的實例的 "__proto__" 屬性就指向了它本身的 "prototype"。
示例二:
function Person(name, sex) { this.name = name; this.sex = sex; } var boy = new Person('Bob', 'male'); console.log(Object.getPrototypeOf(Person) === Function.prototype); //true console.log(Object.getPrototypeOf(boy) === Person.prototype); //true
Person 函數繼承自 Function 對象,若是這麼寫就很直觀了:
var Person = new Function('name', 'sex', 'this.name = name;this.sex = sex;');
所以 Person 函數的原型指向 Function.prototype,boy 對象是經過 Person 函數構造而來的,所以它的原型指向 Person.prototype。
對象會記住它的原型
上面已經提到,JS 給全部對象提供了一個 "__proto__" 屬性,用於訪問它的原型
對象會繼承它的原型鏈中的全部內容
示例:
function A() {} A.prototype = { name: 'better' }; var a = new A(); console.log(a.name); //輸出better
a 對象自己沒有 "name" 屬性,因而在它的原型,即構造函數的 "prototype" 中去找,若是找到,則中止上溯,輸出結果。
面向對象編程
當要建立兩個或多個對象時,若是直接使用字面量或者構造函數方式將會產生沒必要要的重複代碼,例如:
var obj1 = { name: 'test1', sayName: function() { console.log(this.name); } } var obj2 = { name: 'test2', sayName: function() { console.log(this.name); } }
"obj1" 和 "obj2" 共享同一個方法 "sayName()",可是它們卻佔用了兩分內存,所以最好是經過構造函數的方式實現私有屬性的繼承,再經過原型的方式實現共有屬性和共有方法的繼承,例如:
function CreateObj(name) { this.name = name; } CreateObj.prototype.sayName = function() { console.log(this.name); } var obj1 = new CreateObj('test1'); var obj2 = new CreateObj('test2');