第十一章 面向?qū)ο笤屠^承

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í):

yuanxinglian.jpg
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ù)方式:

gouzao.png

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

原型模式方式:

yuanxing.png

在原型模式聲明中,多了兩個(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: ?}
yuanxingjs.png
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í)行流程:

  1. 先查找構(gòu)造函數(shù)實(shí)例里的屬性或方法,如果有,立刻返回;
  2. 如果構(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)物

非構(gòu)造函數(shù)的繼承

寫(xiě)到著 面向?qū)ο蠛驮屠^承有些暈。準(zhǔn)備抽時(shí)間重新整理下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容