JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記10 再訪js對(duì)象
1、對(duì)象再認(rèn)識(shí)
(1)對(duì)象屬性和特性
什么是屬性(Property),什么是特性(Attribute),這有什么區(qū)別?我不想也不會(huì)從語義學(xué)上去區(qū)分,對(duì)于這系列文章來說,屬性就是組成對(duì)象的一個(gè)部分,廣義上也包括對(duì)象的方法,而特性則是指被描述主體所具有的特征,換句話說,屬性是我們可以通過編碼來訪問的具體存在,而特性則主要是為了便于理解概念的抽象存在,當(dāng)然,特性也可以通過相應(yīng)的屬性來具體外化。這一小節(jié)所講的對(duì)象屬性的特性就是對(duì)對(duì)象屬性特征的一個(gè)描述,主要來自于ECMA-262規(guī)范的第5版,該規(guī)范使用兩個(gè)中括號(hào)的形式來描述不能直接訪問的內(nèi)部特性。
A、屬性類型(先給屬性分下類):
- 數(shù)據(jù)屬性:直接訪問屬性值的屬性
- 訪問器屬性:通過getter/setter方法來訪問屬性值的屬性
- 內(nèi)部屬性:不能通過代碼直接訪問的屬性,只是為了規(guī)范說明目的而存在,在規(guī)范中也使用兩個(gè)中括號(hào)的形式來描述
B、對(duì)象內(nèi)部屬性
內(nèi)部屬性不能通過代碼直接訪問,它主要是為了描述規(guī)范,也是給ECMAScript實(shí)現(xiàn)者參考的,而對(duì)于開發(fā)者來說,了解這些可以便于理解一些內(nèi)部機(jī)制。比如在給一個(gè)屬性賦值時(shí),在實(shí)現(xiàn)中會(huì)調(diào)用[[Put]]內(nèi)部方法,而讀取一個(gè)屬性值時(shí),則調(diào)用[[Get]]方法。
所有對(duì)象公共的內(nèi)部屬性 | 個(gè)別對(duì)象特有的內(nèi)部屬性 | |||
名稱 | 規(guī)范 | 名稱 | 規(guī)范 | 對(duì)象 |
[[Prototype]] | Object/Null | [[PrimitiveValue]] | primitive | Boolean|Date|Number|String |
[[Class]] | String | [[Construct]] | SpecOp(a List of any) → Object | new |
[[Extensible]] | Boolean | [[Call]] | SpecOp(any, a List of any) → any|Reference | call |
[[Get]] | SpecOp (propName) →any | [[HasInstance]] | SpecOp(any) → Boolean | Function |
[[GetOwnProperty]] | SpecOp (propName) →Undefined|Property Descriptor | [[Scope]] | Lexical Environment | Function |
[[GetProperty]] | SpecOp (propName) →Undefined|Property Descriptor | [[FormalParameters]] | List of Strings | Function |
[[Put]] | SpecOp (propName, any, Boolean) | [ 復(fù)制代碼 代碼如下: ] |
ECMAScript code | Function |
[[CanPut]] | SpecOp (propName) → Boolean | [[TargetFunction]] | Object | Function.prototype.bind |
[[HasProperty]] | SpecOp (propName) → Boolean | [[BoundThis]] | any | Function.prototype.bind |
[[Delete]] | SpecOp (propName, Boolean) → Boolean | [[BoundArguments]] | List of any | Function.prototype.bind |
[[DefaultValue]] | SpecOp (Hint) → primitive | [[Match]] | SpecOp(String, index) → MatchResult | RegExp |
[[DefineOwnProperty]] | SpecOp (propName, PropDesc, Boolean) → Boolean | [[ParameterMap]] | Object |
說明:
- 每一個(gè)對(duì)象都有一個(gè)原型對(duì)象[[Prototype]],一般我們不能在代碼中直接訪問這個(gè)內(nèi)部屬性,但可以通過Object.getPrototypeOf(object)來獲取原型對(duì)象(在Firefox中可以通過__proto__來直接訪問)。
- 在Object.prototype.toString方法中,按照規(guī)范內(nèi)建對(duì)象會(huì)返回包含[[Class]]的值“[object class]”,而內(nèi)建對(duì)象的[[Class]]值就是相應(yīng)的名稱(比如Array對(duì)象的[[Class]]值為'Array'),因此可以通過Object.prototype.toString.call(value) == '[object Array]'來判斷value是否是一個(gè)Array。
- 給一個(gè)屬性賦值時(shí),后臺(tái)調(diào)用[[Put]]來實(shí)現(xiàn),獲取一個(gè)屬性值時(shí),后臺(tái)調(diào)用[[Get]]來獲取。
- 使用new操作符調(diào)用一個(gè)函數(shù)時(shí),后臺(tái)調(diào)用[[Construct]],而使用call方法來調(diào)用函數(shù)時(shí),后臺(tái)會(huì)調(diào)用[[Call]]。
- [[HasInstance]]方法返回給定參數(shù)是否是通過調(diào)用函數(shù)來創(chuàng)建的,和Object的方法isPrototypeOf(obj)類似。
- 當(dāng)一個(gè)函數(shù)被執(zhí)行時(shí),就會(huì)創(chuàng)建一個(gè)[[Scope]]對(duì)象,可以理解為[[Scope]]就是我們前面所說的活動(dòng)對(duì)象,也就是說this、arguments、形參、函數(shù)內(nèi)部定義的變量和函數(shù)都是的[[Scope]]對(duì)象的屬性。
C、屬性特性(用來描述屬性的特性)
內(nèi)部特性 | 配置屬性 | 屬性類型 | 數(shù)據(jù)類型 | 默認(rèn)值 | 含義 | 備注 |
[[Configurable]] | configurable |
數(shù)據(jù)屬性 訪問器屬性 |
Boolean |
true |
能否通過delete刪除屬性從而重新定義屬性 能否修改屬性的特性 能否把屬性修改為訪問器特性 |
一旦把屬性定義為不可配置的,就不能再變?yōu)榭膳渲玫?/P> 如果為false,不能做刪除、也不能修改屬性特性,但是允許修改屬性值 非嚴(yán)格模式下會(huì)忽略相應(yīng)操作,嚴(yán)格模式下則拋出異常 |
[[Enumerable]] | enumerable |
數(shù)據(jù)屬性 訪問器屬性 |
Boolean | true | 能否通過for-in循環(huán)返回屬性 | 為true時(shí)可以通過for-in來枚舉,否則不能通過for-in枚舉 |
[[Writable]] | writable | 數(shù)據(jù)屬性 | Boolean | true | 能否修改屬性的值 | 為false時(shí)不能修改屬性值,非嚴(yán)格模式下會(huì)忽略相應(yīng)操作,嚴(yán)格模式下則拋出異常 |
[[Value]] | value | 數(shù)據(jù)屬性 | 任意類型 | undefined | 屬性值 | |
[[Get]] | get | 訪問器屬性 | Undefined/Function | undefined | 讀取屬性時(shí)調(diào)用的函數(shù) | 為一個(gè)函數(shù)時(shí),會(huì)無參數(shù)調(diào)用這個(gè)函數(shù),并將返回值作為屬性值返回 |
[[Set]] | set | 訪問器屬性 | Undefined/Function | undefined | 寫入屬性時(shí)調(diào)用的函數(shù) | 為一個(gè)函數(shù)時(shí),會(huì)將傳入的值作為參數(shù)調(diào)用這個(gè)函數(shù),賦給屬性 |
說明:
- 配置屬性是指使用下面要講的屬性定義方法時(shí)用以定義相關(guān)特性的配置項(xiàng)名稱。
- 對(duì)于訪問器屬性,[[Get]]、[[Set]]不一定都有,沒有[[Get]]的屬性不能讀(返回undefined,嚴(yán)格模式下拋出異常),沒有[[Set]]的屬性不能寫(會(huì)忽略,嚴(yán)格模式下拋出異常)。
- 注意區(qū)分對(duì)象內(nèi)部屬性和對(duì)象屬性的特性。
D、屬性定義方法(用來定義屬性的方法)
最常見的定義屬性的方法就是直接在對(duì)象上添加屬性,比如obj.name = 'linjisong',這種情況下定義的屬性所具有的內(nèi)部特性都是默認(rèn)的,如果想定義一個(gè)值不能被修改的屬性要怎么做呢?在ES中給我們提供了幾個(gè)方法用于實(shí)現(xiàn)類似的功能。
方法名 | 功能說明 | 參數(shù)和返回值 | 說明 | 調(diào)用示例 |
defineProperty() | 定義一個(gè)屬性 |
(1)目標(biāo)對(duì)象 (2)屬性的名字 (3)屬性描述符對(duì)象 |
使用屬性定義方法時(shí) |
|
defineProperties() | 定義一組屬性 |
(1)目標(biāo)對(duì)象 (2)多個(gè)屬性描述符組成的一個(gè)對(duì)象 | ||
getOwnPropertyDescriptor() | 獲取屬性的特性 |
(1)目標(biāo)對(duì)象 (2)屬性的名字 (3)返回一個(gè)包括了屬性特性的對(duì)象 |
注:這些方法設(shè)置或獲取的屬性特殊和屬性的類型有關(guān),比如數(shù)據(jù)屬性只能設(shè)置[[Confirurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。
(2)防篡改對(duì)象
所謂防篡改對(duì)象,就是給對(duì)象一定級(jí)別的保護(hù)以防止在這個(gè)級(jí)別上對(duì)對(duì)象的變更,在ES5規(guī)范中,定義了依次升高的三種保護(hù)級(jí)別:
保護(hù)級(jí)別 | 描述 | 操作方法 | 判斷方法 | 說明 |
不可擴(kuò)展 | 不能給對(duì)象添加新屬性和方法,但可以修改已有屬性和方法 | preventExtensions() | isExtensible():不能擴(kuò)展時(shí)返回false | |
密封 | 不可擴(kuò)展,并且已有成員的[[Configurable]]設(shè)置為false,不能刪除屬性,但可以修改屬性值 | seal() | isSeal():被密封時(shí)返回true | isSeal()為true時(shí)一定有isExtensible()為false |
凍結(jié) | 密封,其[[Writable]]設(shè)置為false,但如果定義了[[Set]],訪問器屬性仍然可寫 | freeze() | isFrozen():被凍結(jié)時(shí)返回true | isFrozen()為true時(shí)一定有isSeal()為true,isExtensible()為false |
注:一旦定義成了防篡改對(duì)象,就不能撤銷。
(3)對(duì)象的其它方法
名稱 | 描述 |
create(prototype[,descriptors]) | 創(chuàng)建一個(gè)具有指定原型且可選擇性地包含指定屬性的對(duì)象 |
getOwnPropertyNames(object) | 返回對(duì)象的屬性(方法)的名稱 |
getPrototypeOf(object) | 返回對(duì)象的原型 |
keys(object) | 返回對(duì)象的可枚舉屬性(方法)的名稱 |
這里的create(prototype[,descriptors])是一個(gè)非常有意思的方法,規(guī)范中這樣描述它的行為:
[code]
①如果prototype不是Null或Object,拋出TypeError異常
②var obj = new Object()
③設(shè)置obj的內(nèi)部屬性[[Prototype]]為prototype
④如果descriptors存在且不為undefined,使用Object.defineProperties(obj,descriptors)來添加屬性
⑤返回obj
由于一般對(duì)象的[[Prototype]]不能直接訪問,可以使用函數(shù)來進(jìn)行下面模擬實(shí)現(xiàn):
(function(){
function Base(){};
Object.create = function(prototype, descriptors){
var typeVal = typeof prototype;
if(typeVal !== null && typeVal !== 'object' && typeVal !== 'function'){
throw new TypeError('類型錯(cuò)誤,請(qǐng)檢查第一個(gè)參數(shù)的類型');
}
Base.prototype = prototype;
var result = new Base();
if(descriptors){
Object.defineProperties(result, descriptors);
}
return result;
};
})();
測試一下:
try{
var one = Object.create(1);//異常
}catch(e){
console.info(e);//TypeError
}
var base = {
name:'linjisong',
getName:function(){
return this.name;
}
};
var two = Object.create(base);
console.info(two.name);//linjisong
console.info(two.getName());//linjisong
var three = Object.create(base, {
name:{value:'oulinhai'},
age:{value:23}
});
console.info(three.getName());//oulinhai
console.info(three.age);//23
這里實(shí)現(xiàn)了一個(gè)簡單的繼承,這也引出下一個(gè)主題。
2、原型對(duì)象
(1)原型與原型鏈
每個(gè)對(duì)象都有一個(gè)原型對(duì)象,而原型對(duì)象本身也是一個(gè)對(duì)象,也有自己的原型對(duì)象,這樣就形成了一個(gè)原型鏈直至null對(duì)象。對(duì)象的內(nèi)部屬性[[Prototype]]指向的就是對(duì)象的原型對(duì)象,而Object.prototype的原型對(duì)象為null。
(2)屬性查找
在訪問一個(gè)對(duì)象的屬性(方法)時(shí),引擎會(huì)先查找對(duì)象本身有沒有這個(gè)屬性,如果有,返回這個(gè)屬性值,如果沒有,則查找對(duì)象的原型是否有這個(gè)屬性,如果有,返回,如果沒有就繼續(xù)查找原型對(duì)象的原型直至最后一個(gè)原型對(duì)象。
注意區(qū)分屬性查找和和前面說過的標(biāo)識(shí)符查找的異同。屬性查找是沿著原型鏈,標(biāo)識(shí)符查找是沿著作用域鏈,但都有一個(gè)逐級(jí)查找的過程。
(3)對(duì)象的原型對(duì)象[[Prototype]]與函數(shù)的原型屬性prototype
•每一個(gè)對(duì)象都有一個(gè)原型對(duì)象,在規(guī)范中使用[[Prototype]]來表示,這個(gè)對(duì)象一般不能直接訪問,但可以通過getPrototypeOf()這個(gè)方法來獲取,而在Firefox中還可以通過__proto__直接訪問,來驗(yàn)證一下:
var obj = {};
console.info(obj.__proto__===Object.getPrototypeOf(obj));//true
console.info(obj.__proto__===Object.prototype);//true
•每一個(gè)函數(shù)都有一個(gè)屬性prototype,這個(gè)屬性是在函數(shù)定義過程中添加的,它指向的對(duì)象就是所有使用該函數(shù)創(chuàng)建的實(shí)例對(duì)象的原型對(duì)象。
var fn = function(){};
console.info(typeof fn.prototype);//object,一旦定義了函數(shù),就會(huì)添加prototype屬性,指向原型對(duì)象
var obj1 = new fn();
console.info(fn.prototype === Object.getPrototypeOf(obj1));//true,所有使用fn創(chuàng)建的實(shí)例的原型對(duì)象都指向fn.prototype
var obj2 = new fn();
console.info(fn.prototype === Object.getPrototypeOf(obj2));//true
console.info(Object.getPrototypeOf(fn) === Function.prototype);//true
當(dāng)然,fn本身也是一個(gè)對(duì)象,也有自己的原型對(duì)象,它的原型對(duì)象就是Function的屬性prototype了(fn.__proto__===Function.prototype)。
我們知道,每一個(gè)對(duì)象都可以訪問一個(gè)屬性constructor,指向創(chuàng)建這個(gè)對(duì)象的函數(shù)(構(gòu)造函數(shù)),實(shí)際上,constructor屬性只不過是構(gòu)造函數(shù)的原型對(duì)象的一個(gè)屬性而已,因此通過構(gòu)造函數(shù)創(chuàng)建的實(shí)例都能夠訪問constructor。
var fn = function fn(){};
var obj1 = new fn();
console.info(fn.constructor);//Function()
console.info(fn.prototype.constructor);//fn(),函數(shù)原型對(duì)象的constructor指向函數(shù)本身console.info(obj1.hasOwnProperty('constructor'));//false,實(shí)例本身沒有constructor屬性console.info(fn.prototype.constructor === obj1.constructor);//true,實(shí)例可以訪問到原型對(duì)象中的constructor屬性
•函數(shù)的原型對(duì)象具有動(dòng)態(tài)性,即便先創(chuàng)建實(shí)例,后修改原型對(duì)象,也還是能夠通過實(shí)例訪問到對(duì)原型對(duì)象所做的變更。
var fn = function fn(){};
var obj = new fn();
console.info(obj.name);//undefined
fn.prototype.name = 'linjisong';
console.info(obj.name);//linjisong
3、創(chuàng)建對(duì)象
創(chuàng)建方式 | 示例 | 說明 |
傳統(tǒng)方式 |
var person = new Object(); person.name = 'linjisong'; person.job = 'it'; |
傳統(tǒng)方式創(chuàng)建對(duì)象容易產(chǎn)生大量重復(fù)的代碼 |
對(duì)象字面量 |
var person = { name : 'linjisong', job : 'it' }; |
使用對(duì)象字面量創(chuàng)建簡潔明了,非常適合創(chuàng)建作為函數(shù)實(shí)參的對(duì)象 |
工廠模式 |
function createPerson(name, job){ var o = new Object(); o.name = name; o.job = job; return o; } var person = createPerson('linjisong','it'); |
1、工廠模式能有效解決重復(fù)代碼問題。 2、但是不能判定對(duì)象的類型 |
構(gòu)造函數(shù)模式 |
function Person(name, job){ this.name = name; this.job = job; this.getName = function(){ return this.name; } } var person = new Person('linjisong','it'); |
構(gòu)造函數(shù)模式能解決重復(fù)代碼問題,也能夠判定對(duì)象的類型 但是這種模式下創(chuàng)建的每個(gè)實(shí)例都有一份屬性和方法的Copy 對(duì)于方法來說,每個(gè)實(shí)例都保存一份是沒有必要的 使用new調(diào)用構(gòu)造函數(shù)的內(nèi)部步驟: (1)創(chuàng)建一個(gè)新對(duì)象 (2)將構(gòu)造函數(shù)的作用域賦給新對(duì)象(構(gòu)造函數(shù)內(nèi)this指向新創(chuàng)建對(duì)象) (3)執(zhí)行構(gòu)造函數(shù)中的代碼 (4)返回新對(duì)象 |
原型模式 |
function Person(){} Person.prototype.name = 'linjisong'; Person.prototype.job = 'it; Person.prototype.getName = fucntion(){ return this.name; }; var person = new Person(); |
原型模式能夠解決構(gòu)造函數(shù)模式的方法實(shí)例有多個(gè)副本的問題 但是同時(shí)每個(gè)實(shí)例的屬性也共享了,對(duì)于引用類型的屬性來說 這會(huì)導(dǎo)致非常嚴(yán)重的問題,修改一個(gè)實(shí)例的屬性會(huì)導(dǎo)致另一個(gè)實(shí)例也修改 而且也不能接受參數(shù) function Angle(){}; Angle.prototype.coordinate = [0,0]; var a1 = new Angle(); var a2 = new Angle(); a1.coordinate[0] = 1; console.info(a2.coordinate);//[1,0]修改a1會(huì)導(dǎo)致a2變更 |
組合構(gòu)造原型模式 |
function Person(name, job){ this.name = name; this.job = job; } Person.prototype.getName = fucntion(){ return this.name; }; var person = new Person('linjisong','it'); |
結(jié)合構(gòu)造函數(shù)模式和原型模式 使用構(gòu)造函數(shù)模式創(chuàng)建屬性,每個(gè)實(shí)例保存一份 使用原型模式共享方法,所有實(shí)例共享保存一份 這是目前使用最廣泛的對(duì)象創(chuàng)建方式 |
動(dòng)態(tài)原型模式 |
function Person(name, job){ this.name = name; this.job = job; if(!Person.prototype.getName){ Person.prototype.getName = fucntion(){ return this.name; }; } } var person = new Person('linjisong','it'); |
這種模式實(shí)際上是對(duì)于不習(xí)慣將構(gòu)造函數(shù)和原型分離而引入的 在判斷的時(shí)候,可以只判斷其中一個(gè)屬性 |
寄生構(gòu)造函數(shù)模式 |
function Person(name, job){ var o = new Object(); o.name = name; o.job = job; o.getName = fucntion(){ return this.name; }; return o; } var person = new Person('linjisong','it'); |
工廠模式不使用new,寄生構(gòu)造函數(shù)模式使用new操作符 構(gòu)造函數(shù)模式不返回,寄生構(gòu)造函數(shù)模式返回對(duì)象 不能使用instanceof判斷類型 |
穩(wěn)妥構(gòu)造函數(shù)模式 |
function Person(name, job){ var o = new Object(); o.getName = fucntion(){ return name; }; return o; } var person = Person('linjisong','it'); |
穩(wěn)妥對(duì)象:不使用this和new 穩(wěn)妥構(gòu)造模式類似寄生構(gòu)造模式,但只能通過提供的方法訪問成員 不能使用instanceof判斷類型 |
各種創(chuàng)建對(duì)象的模式需要根據(jù)具體情況來看,最常用的還是對(duì)象字面量和組合構(gòu)造原型方式。
4、繼承
在ECMAScript中,沒有接口繼承,只有實(shí)現(xiàn)繼承,這些繼承主要是通過原型鏈來實(shí)現(xiàn)的。像對(duì)象創(chuàng)建一樣,下面也通過一張表格來瀏覽一下一些實(shí)現(xiàn)繼承的方法。
繼承方式 | 示例 | 說明 |
原型鏈 |
function Square(){//正方形 this.width = 10;//邊長 this.coordinate = [0,0];//左上頂點(diǎn)的坐標(biāo) } Square.prototype.getArea = function(){//計(jì)算面積 return this.width * this.width; }; function ColorSquare(){//有顏色的正方形 this.color = 'red'; } ColorSquare.prototype = new Square();//實(shí)現(xiàn)了繼承 ColorSquare.prototype.getColor = function(){//獲取顏色 return this.color; } var cs = new ColorSquare(); console.info(cs.width);//10 console.info(cs.getArea());//100 console.info(cs.color);//red console.info(cs.getColor());//red |
1、通過修改子類型創(chuàng)建函數(shù)的原型實(shí)現(xiàn)繼承。 2、通過原型給子類型添加新方法時(shí),一定要在替換子類型原型之后添加,而后也不能通過對(duì)象字面量修改子類型的原型。 3、可以通過兩種方法確定原型和實(shí)例之間的關(guān)系:只要實(shí)例原型鏈中出現(xiàn)過構(gòu)造函數(shù)fn,都返回true (1)instance instanceof fn (2)fn.prototype.isPrototype(instance) 4、使用原型鏈繼承時(shí),創(chuàng)建子對(duì)象時(shí)無法傳遞參數(shù)。 5、引用類型的父類屬性會(huì)被所有子類型實(shí)例共享從而產(chǎn)生問題: 修改一個(gè)子類型實(shí)例的引用類型屬性會(huì)導(dǎo)致其它所有子類型實(shí)例相應(yīng)的修改 var cs2 = new ColorSquare(); console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs2.coordinate);//[0,1],修改cs會(huì)導(dǎo)致cs2也修改 |
借用構(gòu)造函數(shù) |
function Square(){//正方形 this.width = 10;//邊長 this.coordinate = [0,0];//左上頂點(diǎn)的坐標(biāo) } Square.prototype.getArea = function(){//計(jì)算面積 return this.width * this.width; }; function ColorSquare(){//有顏色的正方形 Square.call(this);//實(shí)現(xiàn)繼承 this.color = 'red'; } var cs = new ColorSquare(); var cs2 = new ColorSquare(); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,0],互相獨(dú)立,修改cs不影響cs2 try{ console.info(cs.getArea());//異常,不能訪問父類原型中方法 }catch(e){ console.info(e);//TypeError } |
1、使用借用構(gòu)造函數(shù)時(shí),可以在call調(diào)用時(shí)傳遞參數(shù)。 2、同時(shí)也不存在引用類型共享的問題。 3、借用構(gòu)造函數(shù)的缺點(diǎn)是,子類不能訪問父類原型中定義的方法 |
組合繼承 |
function Square(){//正方形 this.width = 10;//邊長 this.coordinate = [0,0];//左上頂點(diǎn)的坐標(biāo) } Square.prototype.getArea = function(){//計(jì)算面積 return this.width * this.width; }; function ColorSquare(){//有顏色的正方形 Square.call(this);//創(chuàng)建子類實(shí)例時(shí),第二次調(diào)用父類構(gòu)造函數(shù) this.color = 'red'; } ColorSquare.prototype = new Square();//第一次調(diào)用 ColorSquare.prototype.getColor = function(){//獲取顏色 return this.color; } var cs = new ColorSquare(); var cs2 = new ColorSquare(); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,0],互相獨(dú)立,修改cs不影響cs2 console.info(cs.getArea());//100,可以訪問 |
1、組合繼承也稱為偽經(jīng)典繼承,是將原型鏈和借用構(gòu)造函數(shù)兩種方式結(jié)合起來的繼承方式。 2、基本思想是: (1)使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承 (2)使用借用構(gòu)造函數(shù)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承 3、組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺點(diǎn),融合了它們的優(yōu)點(diǎn),是最常用的繼承方式。 4、組合繼承的缺點(diǎn)是需要調(diào)用兩次父類的構(gòu)造函數(shù) |
原型式繼承 |
function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } var square = { width:10, coordinate:[0,0] }; var cs = create(square); var cs2 = create(square); console.info(cs.coordinate);//[0,0] console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs.coordinate);//[0,1] console.info(cs2.coordinate);//[0,1],和原型鏈一樣,會(huì)有共享問題 |
1、這種方式實(shí)際上就是前面說的模擬ES5中create函數(shù)來實(shí)現(xiàn)繼承。 2、ES5及前面模擬的create還可以接受另外的屬性描述參數(shù)。 3、和原型鏈與借用構(gòu)造函數(shù)不同的是,這種方式需要先有一個(gè)對(duì)象,然后直接創(chuàng)建子對(duì)象。 前者是構(gòu)造函數(shù)的繼承,而后者是對(duì)象實(shí)例的繼承。 4、和使用原型鏈繼承一樣,也會(huì)有引用類型實(shí)例屬性的共享問題。 |
寄生式繼承 |
function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } var square = { width:10, coordinate:[0,0] }; function colorSquare(original){ var s = create(original); s.color = 'red'; return s; } var cs = colorSquare(square); console.info(cs.width);//10 console.info(cs.coordinate);//[0,0] |
1、首先,這里的create函數(shù)不是必需的,任何返回新對(duì)象的函數(shù)都可以。 2、其次,這種模式也有引用類型實(shí)例屬性共享的問題。 3、這種方式,可以看成將上面的對(duì)象繼承包裝成構(gòu)造函數(shù)。 |
寄生組合式繼承 |
function create(o){ var fn = function(){}; fn.prototype = o; return new fn(); } function inherit(sub, sup){ var prototype = create(sup.prototype); prototype.constructor = sub; sub.prototype = prototype; } function Square(){//正方形 this.width = 10;//邊長 this.coordinate = [0,0];//左上頂點(diǎn)的坐標(biāo) } Square.prototype.getArea = function(){//計(jì)算面積 return this.width * this.width; }; function ColorSquare(){//有顏色的正方形 Square.call(this); this.color = 'red'; } inherit(ColorSquare, Square); ColorSquare.prototype.getColor = function(){//獲取顏色 return this.color; } var cs = new ColorSquare(); console.info(cs.width);//10 console.info(cs.getArea());//100 console.info(cs.color);//red console.info(cs.getColor());//red var cs2 = new ColorSquare(); console.info(cs2.coordinate);//[0,0] cs.coordinate[1] = 1; console.info(cs2.coordinate);//[0,0] |
1、這種方式只調(diào)用了一次父類構(gòu)造函數(shù),從而避免了在子類型的原型對(duì)象上創(chuàng)建不必要的屬性。 2、能夠保證原型鏈不變,從而可以正常使用instanceof和isPrototypeOf()。 |
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記13 ECMAScript5新特性
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記12 js正則表達(dá)式
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記11 內(nèi)建js對(duì)象
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記9 js函數(shù)(下)
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記8 js函數(shù)(中)
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記7 js函數(shù)(上)
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記6 初識(shí)js對(duì)象
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記5 js語句
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記4 js運(yùn)算符和操作符
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記3 js簡單數(shù)據(jù)類型
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記2 js基礎(chǔ)語法
- JavaScript高級(jí)程序設(shè)計(jì)(第3版)學(xué)習(xí)筆記 概述