JavaScript有兩種開(kāi)發(fā)模式:1.函數(shù)化(過(guò)程化),2.面向?qū)ο螅∣OP)。面向?qū)ο蟮恼Z(yǔ)言有一個(gè)標(biāo)志,那就是類的概念,而通過(guò)類可以創(chuàng)建任意多個(gè)具有相同屬性和方法的對(duì)象。但是,ECMAScript沒(méi)有類(class)的概念,因此,它的對(duì)象也與基于類的語(yǔ)言中的對(duì)象有所不同。
一、 對(duì)象
現(xiàn)在我想創(chuàng)建兩個(gè)對(duì)象,分別是小強(qiáng)和小明,我們可以使用var obj = new Object這種方式,也可以使用以下方式。
const xiaoming = {
name: 'xiaoming',
age: 18,
sayHello: function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
};
const xiaoqiang = {
name: 'xiaoqiang',
age: 16,
sayHello: function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
};
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
這種方式創(chuàng)建出的兩個(gè)對(duì)象,我們發(fā)現(xiàn)有很多相同之處,比如說(shuō)他們都有name,age,都有一樣的sayHello方法??煞窬?jiǎn)一下寫(xiě)法呢?于是我在第二個(gè)對(duì)象處做了如下更改:
const xiaoqiang = xiaoming;
xiaoqiang.name = 'xiaoqiang';
xiaoqiang.age = 16;
console.log(xiaoming.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
這里xiaoqiang和xiaoming在內(nèi)存中的指針其實(shí)是一樣的,指向同一個(gè)地方。修改其中一個(gè),另外一個(gè)也會(huì)被修改。這是一個(gè)錯(cuò)誤的演示。
1. 工廠模式
為了解決多個(gè)類似對(duì)象聲明的問(wèn)題,我們可以使用一種叫做工廠模式的方法,這種方法就是為了解決實(shí)例化對(duì)象產(chǎn)生大量重復(fù)的問(wèn)題。其實(shí)就是寫(xiě)了一個(gè)函數(shù),函數(shù)返回一個(gè)對(duì)象。這樣就實(shí)現(xiàn)了傳參而創(chuàng)造新的對(duì)象的想法。
// 工廠模式
function People(name, age) {
var obj = {
name,
age,
sayHello() {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
};
return obj;
}
const xiaoming = People('xiaoming', 18);
const xiaoqiang = People('xiaoqiang', 16);
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
工廠模式解決了重復(fù)實(shí)例化的問(wèn)題,但還有一個(gè)問(wèn)題,那就是識(shí)別問(wèn)題,因?yàn)楦緹o(wú)法搞清楚他們是哪個(gè)對(duì)象的實(shí)例。instanceof運(yùn)算符,驗(yàn)證原型對(duì)象與實(shí)例對(duì)象之間的關(guān)系。
console.log(typeof xiaoming); // Object
console.log(xiaoming instanceof Object);// true
console.log(xiaoming instanceof People);
// false 無(wú)法嚴(yán)重實(shí)例xiaoming與原型對(duì)象People之間的關(guān)系
ECMAScript中可以采用構(gòu)造函數(shù)(構(gòu)造方法)可用來(lái)創(chuàng)建特定的對(duì)象。類型于Object對(duì)象。
2. 構(gòu)造函數(shù)
- 構(gòu)造函數(shù)沒(méi)有
new Object,創(chuàng)建對(duì)象的寫(xiě)法,但是后臺(tái)會(huì)自動(dòng)創(chuàng)建對(duì)象; - this指向了創(chuàng)建實(shí)例的空對(duì)象;
- 構(gòu)造函數(shù)不需要返回obj(對(duì)象引用)。后臺(tái)自動(dòng)返回;
function People(name, age) {
this.name = name;
this.age = age;
this.sayHello = function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
}
const xiaoming = new People('xiaoming', 18);
const xiaoqiang = new People('xiaoqiang', 16);
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
使用構(gòu)造函數(shù)方法,即解決了重復(fù)實(shí)例化的問(wèn)題,又解決了對(duì)象識(shí)別問(wèn)題。
console.log(typeof xiaoming); // Object
console.log(xiaoming instanceof Object) // true
console.log(xiaoming instanceof People)
// true 可以驗(yàn)證xiaoming這個(gè)實(shí)例 就是從原型對(duì)象People而來(lái)
3. 構(gòu)造函數(shù)的一些規(guī)范
- 構(gòu)造函數(shù)也是函數(shù),但是函數(shù)名第一個(gè)字母大寫(xiě)
- 創(chuàng)建實(shí)例必須使用new運(yùn)算符,如new Obj(),而且這里第一個(gè)字母也是大寫(xiě)
function People(name, age) {
this.name = name; // 實(shí)例屬性
this.age = age; // 實(shí)例屬性
this.sayHello = function () { // 實(shí)例方法
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
}
const xiaoming = new People('xiaoming', 18); // 創(chuàng)建實(shí)例
const xiaoqiang = new People('xiaoqiang', 16); // 創(chuàng)建實(shí)例
構(gòu)造函數(shù)和普通函數(shù)的唯一區(qū)別,就是他們的調(diào)用方式不一樣。只不過(guò),構(gòu)造函數(shù)也是函數(shù),必須用new運(yùn)算符來(lái)調(diào)用,否則就是普通函數(shù)。
const xiaoming = new People('xiaoming', 18); // 構(gòu)造模式調(diào)用
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
People('xiaoqiang', 18); // 普通模式調(diào)用無(wú)效
var xiaoqiang = {};
People.call(xiaoqiang, 'xiaoqiang', 16);
console.log(xiaoqiang.sayHello());
// hello, my name is xiaoqiang, I am 16 years old.
const people1 = new People('xiaoming', 18);
const people2 = new People('xiaoming', 18);
console.log(people1.name === people2.name);
// true
console.log(people1.sayHello === people2.sayHello);
// false 實(shí)例后的方法是兩個(gè)不同指針的引用類型對(duì)象 引用類型絕對(duì)不相等
引用地址不一致,這樣造成了一些資源上的浪費(fèi)。因?yàn)閮烧叩膶傩苑椒ㄊ且恢碌摹D敲慈绾螌?shí)現(xiàn)讓他們的引用地址一致呢。第一個(gè)想到的是,把這個(gè)函數(shù)寫(xiě)在外面,如下:
function People(name, age, sayHello) {
this.name = name;
this.age = age;
this.sayHello = sayHello;
}
function sayHello() {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
const people1 = new People('xiaoming', 18);
const people2 = new People('xiaoming', 18);
console.log(people1.name === people2.name);
// true
console.log(people1.sayHello === people2.sayHello);
// true 通過(guò)全局實(shí)現(xiàn)引用地址的一致 但是不推薦這樣寫(xiě),會(huì)出現(xiàn)惡意調(diào)用,而且代碼閱讀起來(lái)不舒服
// 這樣我們就用到了原型這個(gè)概念,也就是prototype
二、 原型
我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)對(duì)象,它的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。邏輯上可以這么理解:prototype是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象的原型對(duì)象。使用原型的好處可以讓所有實(shí)例共享它所包含的屬性和方法。也就是說(shuō),不必在構(gòu)造函數(shù)中定義對(duì)象信息,而是可以直接將這些信息添加到原型中。
首先,我們需要牢記兩點(diǎn):①__proto__和constructor屬性是對(duì)象所獨(dú)有的;② prototype屬性是函數(shù)所獨(dú)有的。但是由于JS中函數(shù)也是一種對(duì)象,所以函數(shù)也擁有__proto__和constructor屬性,這點(diǎn)是致使我們產(chǎn)生困惑的很大原因之一。
- 每個(gè)對(duì)象都具有一個(gè)名為_(kāi)proto_的屬性;
- 每個(gè)構(gòu)造函數(shù)(構(gòu)造函數(shù)標(biāo)準(zhǔn)為大寫(xiě)開(kāi)頭,如Function(),Object()等等JS中自帶的構(gòu)造函數(shù),以及自己創(chuàng)建的)都具有一個(gè)名為prototype的方法(注意:既然是方法,那么就是一個(gè)對(duì)象(JS中函數(shù)同樣是對(duì)象),所以prototype同樣帶有_proto_屬性);
- 每個(gè)對(duì)象的_proto_屬性指向自身構(gòu)造函數(shù)的prototype;
需要注意的指向是
Function的_proto_指向其構(gòu)造函數(shù)Function的prototype;
Object作為一個(gè)構(gòu)造函數(shù)(是一個(gè)函數(shù)對(duì)象!!函數(shù)對(duì)象!!),所以他的proto指向Function.prototype;
Function.prototype的_proto_指向其構(gòu)造函數(shù)Object的prototype;
Object.prototype的_prototype_指向null(盡頭);
下面這張圖如果不要覺(jué)得麻煩,仔細(xì)閱讀會(huì)被原型、原型鏈、構(gòu)造器有些認(rèn)識(shí):

function People() {}
People.prototype.name = 'xiaoming';
People.prototype.age = 18;
People.prototype.sayHello = function sayHello() {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
const xiaoming = new People();
const xiaoqiang = new People();
console.log(xiaoming.sayHello());
// hello, my name is xiaoming, I am 18 years old.
console.log(xiaoming.sayHello === xiaoqiang.sayHello);
// true
//如果是實(shí)例方法,不同的實(shí)例化,他們的方法地址是不一樣的,是唯一的。
//如果是原型方法,那么他們的地址是共享的,大家都是一樣的。
構(gòu)造函數(shù)方式:

構(gòu)造函數(shù)方式創(chuàng)建的實(shí)例內(nèi)部地址引用其實(shí)都不一樣,只是不好測(cè)試,但是hello這個(gè)方法方便測(cè)試,因?yàn)槭且妙愋汀?/p>
原型模式方式:

在原型模式聲明中,多了兩個(gè)屬性,這兩個(gè)屬性都是創(chuàng)建對(duì)象時(shí)自動(dòng)生成的。_proto_屬性是實(shí)例指向原型對(duì)象(People.prototype)的一個(gè)指針通過(guò)這兩個(gè)屬性,就可以訪問(wèn)到原型里的屬性和方法了。
console.log(xiaoming.__proto__);
// {name: "xiaoming", age: 18, sayHello: ?, constructor: ?}

console.log(xiaoming.__proto__ === People.prototype);
// true 實(shí)例的proto屬性就是指向構(gòu)造函數(shù)的prototype
console.log(xiaoming.constructor);
// ? People() {}
// 構(gòu)造屬性,可以獲取函數(shù)本身
// 作用是被原型指針定位,然后得到構(gòu)造函數(shù)本身
// 作用就是對(duì)象實(shí)例對(duì)應(yīng)原型對(duì)象
console.log(People.constructor);
// 構(gòu)造函數(shù)的構(gòu)造器指向Function
判斷一個(gè)對(duì)象實(shí)例(對(duì)象引用)是不是指向了對(duì)象的原型對(duì)象,實(shí)例化自動(dòng)指向。
console.log(People.prototype.isPrototypeOf(xiaoming)); //true
原型模式的執(zhí)行流程:
- 先查找構(gòu)造函數(shù)實(shí)例里的屬性或方法,如果有,立刻返回;
- 如果構(gòu)造函數(shù)實(shí)例沒(méi)有,則取它的原型對(duì)象里查找,如果有,就返回;
雖然我們可以通過(guò)對(duì)象實(shí)例訪問(wèn)保存在原型中的值,但卻不能訪問(wèn)通過(guò)對(duì)象實(shí)例重寫(xiě)原型中的值。
function People() {}
People.prototype.name = 'xiaoming';
People.prototype.age = 18;
People.prototype.sayHello = function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
var xiaoming = new People;
console.log(xiaoming.name);
// xiaoming 原型中的值 實(shí)例沒(méi)有 訪問(wèn)原型
xiaoming.name = 'xiaoqiang';
console.log(xiaoming.name);
// xiaoqiang 就近原則
刪除實(shí)例中的屬性
delete xiaoming.name;
console.log(xiaoming.name);
// xiaoming 已經(jīng)被刪除
刪除和覆蓋原型中的屬性
delete xiaoming.name;
delete People.prototype.name;
console.log(xiaoming.name);
// undefined 原型屬性被刪除
People.prototype.name = 'kakaxi';
console.log(xiaoming.name);
// kakaxi 覆蓋原型屬性
如何判斷屬性是在構(gòu)造函數(shù)的實(shí)例里,還是在原型里?可以使用hasOwnProperty()函數(shù)來(lái)驗(yàn)證:
console.log(xiaoming.hasOwnProperty('name'));
// 判斷實(shí)例中是否有屬性 有true 否則false
console.log('name' in xiaoming);
// 不管實(shí)例屬性或原型屬性,只要有該屬性就返回true 兩邊都沒(méi)有返回false
判斷只有原型中有屬性
function isProperty(object, property) {
return !object.hasOwnProperty(property) && (property in object);
}
console.log(isProperty(xiaoming, 'name')); //true
為了讓屬性和方法更好的提現(xiàn)封裝的效果,并且減少不必要的輸入,原型的創(chuàng)建可以使用字面量的方式:
function People() {}
People.prototype = {
name: 'xiaoming',
age: 18,
sayHello: function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
};
使用構(gòu)造函數(shù)創(chuàng)建原型對(duì)象和使用字面量創(chuàng)建對(duì)象在使用上基本相同。但還有一些區(qū)別,字面量創(chuàng)建的方式使用constructor屬性不會(huì)指向?qū)嵗?,而?huì)指向Object,構(gòu)造函數(shù)創(chuàng)建的方式則相反。
console.log(xiaoming.constructor);
//? Object() { [native code] }
如果想讓字面量的方式的constructor指向?qū)嵗龑?duì)象,那么可以這么做。
People.prototype = {
constructor: People //強(qiáng)制修改指向
};
字面量方式為什么constructor會(huì)指向Object?因?yàn)镻eople.prototype={}這種寫(xiě)法實(shí)際上是創(chuàng)建了一個(gè)對(duì)象。而每創(chuàng)建一個(gè)對(duì)象,就會(huì)同時(shí)創(chuàng)建它的prototype,這個(gè)對(duì)象也會(huì)自動(dòng)獲得constructor屬性。所以,新對(duì)象的constructor重寫(xiě)了People原來(lái)的constructor,因此會(huì)指向新對(duì)象,那個(gè)新對(duì)象沒(méi)有指定構(gòu)造函數(shù),那么默認(rèn)為Object。
原型的聲明是有先后順序的,所以,重寫(xiě)原型會(huì)切斷之前的原型。
People.prototype = {
age: 20
};
var xiaoming = new People();
console.log(xiaoming.name); //undefined
//這里等于修改了對(duì)象指向,重寫(xiě)了對(duì)象,沒(méi)有之前數(shù)據(jù)。
內(nèi)置引用類型
console.log(Array.prototype.splice);
// ? splice() { [native code] }
console.log(String.prototype.split);
// ? split() { [native code] }
//內(nèi)置引用類型拓展,但是不利于維護(hù),一般不推薦使用
String.prototype.addString = function () {
return this + ' ' + 'hello~~';
};
console.log('xiaoming'.addString());
// xiaoming hello~~
原型模式創(chuàng)建對(duì)象的缺點(diǎn)
它省略了構(gòu)造函數(shù)傳參初始化這一過(guò)程,帶來(lái)的缺點(diǎn)就是初始化的值是一樣的。而原型最大的缺點(diǎn)就是他的優(yōu)點(diǎn),那就是共享。
原型中所有的屬性是被很多實(shí)例共享的(不能傳參)。共享對(duì)于函數(shù)非常合適,對(duì)于包含基本值得屬性也還可以、但是對(duì)于屬性包含引用類型,就存在一些問(wèn)題。
function People() {}
People.prototype = {
constructor: People,
name: 'xiaoming',
age: 18,
friend: ['卡卡西', '鳴人', '佐助'],
sayHello: function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
};
var xiaoming = new People();
xiaoming.friend.push('哈哈'); //在第一個(gè)實(shí)例修改后引用類型,保持了共享
console.log(xiaoming.friend);
["卡卡西", "鳴人", "佐助", "哈哈"]
var xiaoqiang = new People();
console.log(xiaoqiang.friend); //共享xiaoming添加后的引用類型
["卡卡西", "鳴人", "佐助", "哈哈"]
數(shù)據(jù)共享的緣故,導(dǎo)致很多開(kāi)發(fā)者放棄使用原型,因?yàn)槊看螌?shí)例化的數(shù)據(jù)需要保留自己的特性,而不能更改。
解決這種問(wèn)題可以組合構(gòu)造函數(shù)+原型模式:
function People(name, age) {
this.name = name,
this.age = age,
this.friend = ['旗木卡卡西', '漩渦鳴人', '宇智波佐助']
}
People.prototype = {
constructor: People,
sayHello: function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
}
var xiaoming = new People('xiaoming', 18);
var libai = new People('libai', 20);
libai.friend.push('杜甫');
console.log(xiaoming.friend);
// ["旗木卡卡西", "漩渦鳴人", "宇智波佐助"]
console.log(libai.friend);
// ["旗木卡卡西", "漩渦鳴人", "宇智波佐助", "杜甫"]
這種混合模式很好解決了傳參引用共享的問(wèn)題,是創(chuàng)建對(duì)象比較好的辦法。
原型模式,不管你是否調(diào)用了原型中的共享方法,它都會(huì)初始化原型中的方法,并且在聲明一個(gè)對(duì)象時(shí),構(gòu)造函數(shù)+原型部分讓人感覺(jué)又讓人覺(jué)得很怪異。最好把構(gòu)造函數(shù)和原型封裝到一起。為了解決這個(gè)問(wèn)題,我們可以使用動(dòng)態(tài)原型模式。
動(dòng)態(tài)原型模式
function People(name, age) {
this.name = name;
this.age = age;
this.friend = ['旗木卡卡西', '漩渦鳴人', '宇智波佐助'];
console.log('執(zhí)行開(kāi)始');
People.prototype.sayHello = function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
}
console.log('執(zhí)行結(jié)束');
}
// 原型的初始化,只要第一次初始化就可以,沒(méi)必要每次構(gòu)造函數(shù)實(shí)例化的時(shí)候都初始化。
// 也就是new一個(gè)實(shí)例就初始化一次 以上console.log會(huì)分別執(zhí)行實(shí)例個(gè)數(shù)次
var xiaoming = new People('xiaoming', 18);
var xiaoqiang = new People('xiaoqiang', 16);
解決方法:
if (typeof this.hello !== 'function') {
console.log('執(zhí)行開(kāi)始');
People.prototype.sayHello = function () {
return `hello, my name is ${this.name}, I am ${this.age} years old.`;
};
console.log('執(zhí)行結(jié)束');
}
使用動(dòng)態(tài)原型模式,要注意一點(diǎn),不可以再使用字面量的方式重寫(xiě)原型,因?yàn)闀?huì)切換實(shí)例于新原型之間的聯(lián)系。
以上講解了各種方式創(chuàng)建對(duì)象,如果這幾種方式不能滿足需求,還可以使用一開(kāi)始的那種方式:寄生構(gòu)造函數(shù)。
三、繼承
繼承是面向?qū)ο笾幸粋€(gè)比較核心的概念。其他正統(tǒng)面向?qū)ο笳Z(yǔ)言都會(huì)用兩種方式實(shí)現(xiàn)繼承:一個(gè)是接口實(shí)現(xiàn),一個(gè)是繼承。而ECMAScript只支持繼承,不支持接口實(shí)現(xiàn),而實(shí)現(xiàn)繼承的方式依靠原型鏈完成。
以下來(lái)自阮一峰博客,繼承的五種方式。
簡(jiǎn)單說(shuō),現(xiàn)在有兩個(gè)構(gòu)造函數(shù):
function Animal() {
this.species = '動(dòng)物';
}
function Cat(name, color) {
this.name = name;
this.color = color;
}
如何讓貓繼承動(dòng)物呢
一、 構(gòu)造函數(shù)綁定
第一種方法也是最簡(jiǎn)單的方法,使用call或apply方法,將父對(duì)象的構(gòu)造函數(shù)綁定在子對(duì)象上,即在子對(duì)象構(gòu)造函數(shù)中加一行:
function Cat(name, color) {
Animal.apply(this, arguments);
this.name = name;
this.color = color;
}
const xiaobai = new Cat('xiaobai', 'white');
console.log(xiaobai.species); // 動(dòng)物
二、 prototype模式
第二種方法更常見(jiàn),使用prototype屬性。
如果"貓"的prototype對(duì)象,指向一個(gè)Animal的實(shí)例,那么所有"貓"的實(shí)例,就能繼承Animal了。
每個(gè)函數(shù)都有一個(gè)prototype對(duì)象,前面說(shuō)過(guò)。prototype對(duì)象中默認(rèn)含有constructor對(duì)象,這個(gè)對(duì)象指向創(chuàng)建這個(gè)實(shí)例的構(gòu)造函數(shù)。
// 通過(guò)原型鏈繼承,超類實(shí)例化后的對(duì)象實(shí)例,復(fù)制給子類型的原型屬性
Cat.prototype = new Animal();
// Animal {species: "動(dòng)物"}
console.log(xiaobai.species); // 動(dòng)物
但是這樣綁定后Cat.prototype.constructor指向了Animal,這顯然會(huì)導(dǎo)致繼承鏈的紊亂(cat1明明是用構(gòu)造函數(shù)Cat生成的),因此我們必須手動(dòng)糾正,將Cat.prototype對(duì)象的constructor值改為Cat。這就是第二行的意思。
Cat.prototype.constructor = Cat;
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
三、 直接繼承prototype
第三種方法是對(duì)第二種方法的改進(jìn)。由于Animal對(duì)象中,不變的屬性都可以直接寫(xiě)入Animal.prototype。所以,我們也可以讓Cat()跳過(guò) Animal(),直接繼承Animal.prototype。
現(xiàn)在,我們先將Animal對(duì)象改寫(xiě):
function Animal(){ }
Animal.prototype.species = "動(dòng)物";
然后,將Cat的prototype對(duì)象,然后指向Animal的prototype對(duì)象,這樣就完成了繼承。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
與前一種方法相比,這樣做的優(yōu)點(diǎn)是效率比較高(不用執(zhí)行和建立Animal的實(shí)例了),比較省內(nèi)存。缺點(diǎn)是 Cat.prototype和Animal.prototype現(xiàn)在指向了同一個(gè)對(duì)象,那么任何對(duì)Cat.prototype的修改,都會(huì)反映到Animal.prototype。
所以,上面這一段代碼其實(shí)是有問(wèn)題的。請(qǐng)看第二行
Cat.prototype.constructor = Cat;
這一句實(shí)際上把Animal.prototype對(duì)象的constructor屬性也改掉了!
alert(Animal.prototype.constructor); // Cat
四、 利用空對(duì)象作為中介
由于"直接繼承prototype"存在上述的缺點(diǎn),所以就有第四種方法,利用一個(gè)空對(duì)象作為中介。
var F = function(){};
F.prototype = Animal.prototype;
Cat.prototype = new F();
Cat.prototype.constructor = Cat;
F是空對(duì)象,所以幾乎不占內(nèi)存。這時(shí),修改Cat的prototype對(duì)象,就不會(huì)影響到Animal的prototype對(duì)象。
? alert(Animal.prototype.constructor); // Animal
我們將上面的方法,封裝成一個(gè)函數(shù),便于使用。
function extend(Child, Parent) {
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
使用的時(shí)候,方法如下
extend(Cat,Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
這個(gè)extend函數(shù),就是YUI庫(kù)如何實(shí)現(xiàn)繼承的方法。
另外,說(shuō)明一點(diǎn),函數(shù)體最后一行
Child.uber = Parent.prototype;
意思是為子對(duì)象設(shè)一個(gè)uber屬性,這個(gè)屬性直接指向父對(duì)象的prototype屬性。(uber是一個(gè)德語(yǔ)詞,意思是"向上"、"上一層"。)這等于在子對(duì)象上打開(kāi)一條通道,可以直接調(diào)用父對(duì)象的方法。這一行放在這里,只是為了實(shí)現(xiàn)繼承的完備性,純屬備用性質(zhì)。
五、 拷貝繼承
上面是采用prototype對(duì)象,實(shí)現(xiàn)繼承。我們也可以換一種思路,純粹采用"拷貝"方法實(shí)現(xiàn)繼承。簡(jiǎn)單說(shuō),如果把父對(duì)象的所有屬性和方法,拷貝進(jìn)子對(duì)象,不也能夠?qū)崿F(xiàn)繼承嗎?這樣我們就有了第五種方法。
首先,還是把Animal的所有不變屬性,都放到它的prototype對(duì)象上。
function Animal(){}
Animal.prototype.species = "動(dòng)物";
然后,再寫(xiě)一個(gè)函數(shù),實(shí)現(xiàn)屬性拷貝的目的。
function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}
這個(gè)函數(shù)的作用,就是將父對(duì)象的prototype對(duì)象中的屬性,一一拷貝給Child對(duì)象的prototype對(duì)象。
使用的時(shí)候,這樣寫(xiě):
extend2(Cat, Animal);
var cat1 = new Cat("大毛","黃色");
alert(cat1.species); // 動(dòng)物
寫(xiě)到著 面向?qū)ο蠛驮屠^承有些暈。準(zhǔn)備抽時(shí)間重新整理下。