国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript學(xué)習(xí)筆記 - 面向?qū)ο笤O(shè)計(jì)

brianway / 2526人閱讀

摘要:與構(gòu)造函數(shù)模式不同的是,新對(duì)象的這些屬性和方法是由所有實(shí)例共享。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針內(nèi)部屬性,指向構(gòu)造函數(shù)的原型對(duì)象。

本文記錄了我在學(xué)習(xí)前端上的筆記,方便以后的復(fù)習(xí)和鞏固。

ECMAScript中沒有類的概念,因此它的對(duì)象也與基于類的語言的對(duì)象有所不同。
ECMA-262把對(duì)象定義為:"無序?qū)傩缘慕M合,其屬性可以包含基本值,對(duì)象或者函數(shù)。"對(duì)象的每個(gè)屬性或方法都有一個(gè)名字,而每個(gè)名字映射到一個(gè)值。我們可以把ECMAScript的對(duì)象想象成散列表:無非就是一組名值對(duì),其中值可以使數(shù)據(jù)或函數(shù)。
每個(gè)對(duì)象都是基于一個(gè)引用類型創(chuàng)建的,這個(gè)引用類型可以使原生類型,也可以是開發(fā)人員定義的類型

1. 理解對(duì)象

創(chuàng)建自定義對(duì)象最簡單的方式就是創(chuàng)建一個(gè)Object的實(shí)例,然后再為它添加屬性和方法

var person = new Object();
person.name = "Jason";
person.age = 18;
person.job = "Web";

person.sayName = function() {
    console.log(this.name);
};

對(duì)象字面量創(chuàng)建:

var person = {
    name: "Jason",
    age: 18,
    job: "Web",
    sayName = function() {
        console.log(this.name);
    }
}

這兩個(gè)方法的person對(duì)象是一樣的,都有相同的屬性和方法,這些屬性在創(chuàng)建的時(shí)都帶有一些特征值(characteristic),JavaScript通過這些特征值來定義它們的行為。

1.1 屬性類型

ECMAScript中有兩種屬性:數(shù)據(jù)屬性和訪問器屬性

數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個(gè)數(shù)據(jù)值的位置。在這個(gè)位置可以讀取和寫入值。數(shù)據(jù)屬性有4個(gè)描述其行為的特性。

[[Configurable]]: 表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為true。

[[Enumerable]]: 表示能否通過for-in循環(huán)返回屬性。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為true。

[[Writable]]: 表示能否修改屬性的值。直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)為true。

[[Value]]: 包含這個(gè)屬性的數(shù)據(jù)值。讀取屬性值的時(shí)候,從這個(gè)位置讀;寫入屬性值的時(shí)候。把新值保存在這個(gè)位置。這個(gè)特性的默認(rèn)值為undefined

要修改屬性默認(rèn)的特性,必須使用ECMAScript5的Object.defineProperty()方法。這個(gè)方法接收三個(gè)參數(shù): 屬性所在的對(duì)象、屬性的名字和一個(gè)描述符對(duì)象。其中,描述符(descriptor)對(duì)象的屬性必須是: configurable、enumerable、writable和value。設(shè)置其中的一或多個(gè)值,可以修改對(duì)應(yīng)的特征值。

var person = {};
Object.defineProperty(person, "name", {
    writable: false,      //不能修改屬性的值....
    configurable: false,  //不能通過delete刪除屬性.....
    value: "Jason"        //寫入屬性值
});
console.log(person.name); //Jason
person.name = "Cor";
console.log(person.name); //Jason
delete person.name;
console.log(person.name); //Jason

一旦把屬性定義為不可以配置的,就不能再把它變回可配置的了。此時(shí)再調(diào)用Object.defineProperty()方法修改除writable之外的特性,都會(huì)導(dǎo)致錯(cuò)誤

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Jason"
});
//拋出錯(cuò)誤
Object.defineProperty(person, "name", {
    comfogirable: true,    //這行代碼修改了特性導(dǎo)致報(bào)錯(cuò)
    value: "Cor"
});

也就是說,可以多次調(diào)用Object.defineProperty()方法修改同一屬性,但在吧configurable特性設(shè)置為false之后就會(huì)有限制了。

注意:在調(diào)用Object.defineProperty()方法時(shí),如果不指定,configurable、enumerable和writable特性的默認(rèn)值都是false。

訪問器屬性
訪問器屬性不包含數(shù)據(jù)值;它們包含一對(duì)兒gettersetter函數(shù)(不過,這兩個(gè)函數(shù)都不是必需的)。在讀取訪問器屬性時(shí),會(huì)調(diào)用getter函數(shù),這個(gè)函數(shù)負(fù)責(zé)返回有效的值;在寫入訪問器屬性時(shí),會(huì)調(diào)用setter函數(shù)并傳入新值,這個(gè)函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù)。訪問器屬性有如下4個(gè)特性。

[[Configurable]]: 表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為數(shù)據(jù)屬性。對(duì)于直接在對(duì)象上定義的屬性,這個(gè)特性默認(rèn)值為true。

[[Enumerable]]: 表示能否通過for-in循環(huán)返回屬性。對(duì)于直接在對(duì)象上定義的屬性,它們的這個(gè)特性默認(rèn)值為true。

[[Get]]: 在讀取屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined。

[[Set]]: 在寫入屬性時(shí)調(diào)用的函數(shù)。默認(rèn)值為undefined。

訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義。

var book = {
    _year: 2004,
    edition: 1
};

Object.defineProperty(book, "year", {
    get: function() {
        return this._year;
    },
    set: function(newValue) {    //接受新值的參數(shù)
        if(new Value > 2004) {
            this._year = new Value;
            this.edition += newValue - 2004;
        }
    }
});
book.year = 2005;            //寫入訪問器,會(huì)調(diào)用setter并傳入新值
console.log(book.edition);  //2

不一定非要同時(shí)指定gettersetter。只指定getter意味著屬性是不能寫,嘗試寫入屬性會(huì)被忽略。在嚴(yán)格模式下,嘗試寫入只指定getter函數(shù)的屬性會(huì)拋出錯(cuò)誤。同樣只指定setter函數(shù)的屬性也不能讀,否則在非嚴(yán)格模式下會(huì)返回undefined,在嚴(yán)格模式下會(huì)拋出錯(cuò)誤。

1.2 定義多個(gè)屬性

由于為對(duì)象定義多個(gè)屬性的可能性很大,ECMAScript5又定義了一個(gè)Object.defineProperties()方法。利用這個(gè)方法可以通過描述符一次定義多個(gè)屬性。這個(gè)方法接收兩個(gè)對(duì)象參數(shù): 第一個(gè)對(duì)象是要添加和修改其屬性的對(duì)象,第二個(gè)對(duì)象的屬性與第一個(gè)對(duì)象中要添加或修改的屬性一一對(duì)應(yīng)

var book = {};

Object.defineProperties(book, {
    _year: {
        value: 2004
    },
    edition: {
        value: 1
    },
    
    year: {
        get: function() {
            return this._year;
        },
        set: function(newValue) {
            if(newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});
1.3 讀取屬性的特性

使用ES5的Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符。這個(gè)方法接收兩個(gè)參數(shù):屬性所在的對(duì)象和要讀取其描述符的屬性名稱。返回值是一個(gè)對(duì)象,如果是訪問器屬性,這個(gè)對(duì)象的屬性有configurable、enumerate、get和set;如果是數(shù)據(jù)屬性,這個(gè)對(duì)象的屬性有configurable、enumerable、writable和value。

var book = {};

Object.defineProperties(book, {
    _year: {
        value: 2004
    },
    edition: {
        value: 1
    },
    
    year: {
        get: function() {
            return this._year;
        },
        set: function(newValue) {
            if(newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }
        }
    }
});

var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
console.log(descriptor.value);             //2004
console.log(descriptor.configurable);   //false
console.log(typeof descriptor.get);     //"undefined"
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
console.log(descriptor.value);          //"undefined"
console.log(descriptor.enumerable);     //false
console.log(typeof descriptor.get);        //"function"
2.創(chuàng)建對(duì)象

雖然Object構(gòu)造函數(shù)或?qū)ο笞置媪慷伎梢杂脕韯?chuàng)建單個(gè)對(duì)象,但這些方式有個(gè)明顯得缺點(diǎn):使用同一個(gè)接口創(chuàng)建很多對(duì)象,會(huì)產(chǎn)生大量的重復(fù)的代碼。

2.1 工廠模式

這種模式抽象了創(chuàng)建具體對(duì)象的過程,考慮到ECMAScript中無法創(chuàng)建類,開發(fā)人員就發(fā)明了一種函數(shù),用函數(shù)來封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié)。

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name);
    };
    return o;
}

var person1 = createPerson("Jason", 18, "WEB");
var person2 = createPerson("Cor", 19, "WEB");

函數(shù)createPerson()能夠根據(jù)接受的參數(shù)來構(gòu)建一個(gè)包含所有必要信息的Person對(duì)象。可以無數(shù)次地調(diào)用這個(gè)函數(shù),而每次它都會(huì)返回一個(gè)包含三個(gè)屬性的對(duì)象。工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問題,但卻沒有解決對(duì)象識(shí)別的問題(就是怎么用知道一個(gè)對(duì)象的類型)。

2.2 構(gòu)造函數(shù)模式

ObjectArray這樣的原生構(gòu)造函數(shù),在運(yùn)行時(shí)會(huì)自動(dòng)出現(xiàn)在執(zhí)行環(huán)境中。也可以創(chuàng)建自定義構(gòu)造函數(shù),從而定義對(duì)象類型的屬性和方法。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        console.log(this.name);
    }
}
var person1 = new Person("Jason", 18, "WEB");
var person2 = new Person("Cor", 19, "WEB");

構(gòu)造函數(shù)模式和工廠模式存在以下不同之處:

沒有顯示地創(chuàng)建對(duì)象;

直接將屬性和方法賦給了this對(duì)象;

沒有return語句

像上面創(chuàng)建的Person構(gòu)造函數(shù)。構(gòu)造函數(shù)使用都應(yīng)該以一個(gè)大寫字母開頭,而非構(gòu)造函數(shù)則應(yīng)該以一個(gè)小寫字母開頭。

要?jiǎng)?chuàng)建Person的新實(shí)例,必須使用new操作符。這樣調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷一下4個(gè)步驟:

創(chuàng)建一個(gè)新對(duì)象

將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象)

執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性)

返回新對(duì)象。

在前面例子的最后,person1person2分別保存著Person的一個(gè)不同的實(shí)例。這兩個(gè)對(duì)象都有一個(gè)constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。

console.log(person1.constructor == Person);        //true
console.log(person2.constructor == Person);         //true
console.log(person1 instanceof Object);                //true
console.log(person1 instanceof Person);                //true
console.log(person2 instanceof Object);                //true
console.log(person2 instanceof Person);                //true

創(chuàng)建對(duì)的對(duì)象既是Object的實(shí)例,同時(shí)也是Person的實(shí)例,上面通過instanceof驗(yàn)證。
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將它的實(shí)例標(biāo)識(shí)一種特定類型;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。person1person2之所以同時(shí)是Object的實(shí)例,是因?yàn)樗袑?duì)象均繼承自Object

以這種方式定義的構(gòu)造函數(shù)是定義在Global對(duì)象(在瀏覽器就是window對(duì)象)中的

2.2.1 把構(gòu)造函數(shù)當(dāng)函數(shù)
//當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("Jason", 18, "web");
person.sayName();        //"Jason"
//作為普通函數(shù)調(diào)用
Person("Cor", 19, "web");    //添加到window
window.sayName();        //"cor"
//在另一個(gè)對(duì)象的作用域中調(diào)用
var o = new Object();
Person.call(o, "Kristen", 22, "web");
o.sayName();               //"kriten"

當(dāng)在全局作用域中調(diào)用一個(gè)函數(shù)時(shí),this對(duì)象總是指向Global對(duì)象(在瀏覽器中的window對(duì)象),最后使用了call() ( 或者apply() )在某個(gè)特殊對(duì)象的作用域中調(diào)用Person()函數(shù)。這里是在對(duì)象o的作用域調(diào)用的,因此調(diào)用后o就擁有了所有屬性和方法。

2.2.2 構(gòu)造函數(shù)的問題

構(gòu)造函數(shù)的主要問題,就是每個(gè)方法都要在每個(gè)實(shí)例上重新創(chuàng)建一遍。在前面例子中,person1和person2都有一個(gè)名為sayName()的方法,但那兩個(gè)方法不是同一個(gè)Function的實(shí)例。ECMAScript中的函數(shù)是對(duì)象,因此每定義一個(gè)函數(shù),也就是實(shí)例化了一個(gè)對(duì)象。從邏輯角度講,此時(shí)的構(gòu)造函數(shù)也可以這樣定義

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
        this.sayName = new Function(console.log(this.name)); //與聲明函數(shù)在邏輯上是等價(jià)的
}

以這種方式創(chuàng)建函數(shù),會(huì)導(dǎo)致不同的作用域鏈和標(biāo)識(shí)符解析,但創(chuàng)建Function新實(shí)例的機(jī)制仍然是相同的。因此不同的實(shí)例上的同名函數(shù)是不相等的

console.log(person1.sayName == person2.sayName);  //false
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}

function sayName() {
    console.log(this.name);
}

var person1 = new Person("Jason", 18, "WEB");
var person2 = new Person("Cor", 19, "WEB");

在構(gòu)造函數(shù)內(nèi)部我們把sayName屬性設(shè)置成等于全局的sayName函數(shù)。由于構(gòu)造函數(shù)的sayName屬性包含的是一個(gè)指向函數(shù)的指針,因此person1和person2對(duì)象就共享了在全局作用域中定義的同一個(gè)sayName()函數(shù)。

2.3 原型模式

我們創(chuàng)建的每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性,這個(gè)屬性是一個(gè)指針,指向一個(gè)對(duì)象。而這個(gè)對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法。如果按照字面意思來理解,那么prototype就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的原型對(duì)象。使用原型對(duì)象的好處就是可以讓所有對(duì)象實(shí)例共享它所包含的方法。

function Person() {
}

Person.prototype.name = "Jason";
Person.prototype.age = 18;
Person.prototype.job = "Web";
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
person1.sayName();            //"Jason"
var person2 = new Person();
person2.sayName();            //"Jason"

console.log(person1.sayName == person2.sayName);     //true

與構(gòu)造函數(shù)模式不同的是,新對(duì)象的這些屬性和方法是由所有實(shí)例共享。這樣說吧,person1和person2訪問的都是同一組屬性和同一個(gè)sayName()函數(shù)。

2.3.1 理解原型對(duì)象

無論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)的情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針,就拿前面的例子來說,Person.prototype.constructor指向Person。而通過這個(gè)構(gòu)造函數(shù),我們還可以繼續(xù)為原型對(duì)象添加其他屬性和方法。

創(chuàng)建了自定義構(gòu)造函數(shù)之后,其原型對(duì)象默認(rèn)只會(huì)取得constructor屬性;至于其他方法,則都是從Object繼承而來的。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例的內(nèi)部將包含一個(gè)指針(內(nèi)部屬性),指向構(gòu)造函數(shù)的原型對(duì)象。ECMA-262第五版中管這個(gè)指正交[[Prototype]]。雖然在腳本中沒有標(biāo)準(zhǔn)的方式訪問[[Prototype]],但Firefox、Safari和Chrome在每個(gè)對(duì)象上都支持一個(gè)熟悉 __ proto__;而在其他實(shí)現(xiàn)中,這個(gè)屬性對(duì)腳本則是完全不可見的。不過在明確的真正重要一點(diǎn)就是,這個(gè)連接存在于實(shí)例與構(gòu)造函數(shù)的原型對(duì)象之間,而不是存在于實(shí)例與構(gòu)造函數(shù)之間。
前面例子如圖:

在此Person.prototype指向了原型對(duì)象,而Person.prototype.constructor又指回了Person。原型對(duì)象中除了都包含constructor屬性之外,還包含后來添加的屬性。Person的每個(gè)實(shí)例person1和person2都包含一個(gè)內(nèi)部屬性,該屬性僅僅指向了Person.prototype;換句話說它們與構(gòu)造函數(shù)沒有直接的關(guān)系。此外,雖然這兩個(gè)實(shí)例都不包含屬性和方法,但我們卻可以調(diào)用person1.sayName()。這是通過查找對(duì)象屬性的過程來實(shí)現(xiàn)的。

雖然在所有的實(shí)現(xiàn)中都無法訪問到[[Prototype]]可以通過isPrototypeOf()方法來確定對(duì)象之間是否存在這種關(guān)系。從本質(zhì)上講,如果[[Prototype]]指向調(diào)用isPrototypeOf()方法的對(duì)象(Person.prototype),那么這個(gè)方法就會(huì)返回true

console.log(Person.prototype.isPrototypeOf(person1))  //true
console.log(Person.prototype.isPrototypeOf(person2))  //true

ES5增加了一個(gè)新方法,叫Object.getPrototypeOf(),在所有支持的實(shí)現(xiàn)中,這個(gè)方法返回[[Prototype]]的值,可以方便地獲取一個(gè)對(duì)象的原型

console.log(Object.getPrototypeOf(person1) == Person.prototype); //true
console.log(Object.getPrototypeOf(person1).name);     //"Jason"

搜索機(jī)制:
每當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí),都會(huì)執(zhí)行一次搜索,目標(biāo)是具有給定名字的屬性。搜索首先從對(duì)象的實(shí)例本身開始。如果在實(shí)例中找到具有給定名字的屬性,則返回該屬性的值;如果沒有找到。則繼續(xù)搜索指針指向的原型對(duì)象,在原型對(duì)象中查找具有給定名字的屬性。如果在原型對(duì)象中找到這個(gè)屬性,則返回該屬性的值。簡單的說,就是會(huì)一層層搜索搜索到則返回,沒搜索到則繼續(xù)下層搜索。

原型最初只包含constructor屬性,該屬性也是共享的,因此可以通過對(duì)象實(shí)例訪問

雖然可以通過對(duì)象實(shí)例訪問原型的值,但卻不能通過對(duì)象實(shí)例重寫原型的值。如果我們?yōu)閷?duì)象實(shí)例添加了一個(gè)熟悉,并且該屬性名和實(shí)例原型中的一個(gè)屬性同名,就會(huì)在實(shí)例中創(chuàng)建該屬性,該屬性就會(huì)屏蔽原型中的相同屬性。因?yàn)樯厦嬷v過讀取對(duì)象的屬性時(shí),會(huì)進(jìn)行搜索,搜索會(huì)先從對(duì)象實(shí)例先開始搜索,因?yàn)閷?duì)象實(shí)例有這個(gè)屬性,原型就沒必要搜索了,就會(huì)返回對(duì)象實(shí)例的的屬性值。

function Person() {
}

Person.prototype.name = "Jason";
Person.prototype.age = 29;
Person.prototype.job = "Web";
Person.prototype.sayName = function() {
    console.log(this.name);
}

var person1 = new Person();
var person2 = new Person();
person1.name = "Cor";
person1.sayName();         //"Cor"
person2.sayName();        //"Jason"

如果繼續(xù)能夠重新訪問原型中的屬性可以用delete操作符

delete person1.name;
person1.sayName();       //"Jason"

使用hasOwnProperty()方法可以檢測(cè)一個(gè)屬性是存在實(shí)例中,還是存在于原型中。這個(gè)方法(不要忘了它是從Object繼承來的)只在給定屬性存在于對(duì)象實(shí)例中時(shí),才會(huì)返回true。

console.log(person1.hasOwnProperty("name"));        //false
person1.name = "Cor";
console.log(person1.hasOwnProperty("name"));        //true;
2.3.2 原型與in操作符

有兩種方式使用in操作符:多帶帶使用何在for-in循環(huán)中使用。在多帶帶使用時(shí),in操作符會(huì)在通過對(duì)象能訪問給定屬性時(shí)返回true,無論屬性存在于實(shí)例還是原型中

console.log("name" in person1); true

同時(shí)使用hasOwnProperty()方法和in操作符,就可以確定該屬性到底是存在于對(duì)象中,還是存在于原型中

function hasPrototypeProperty(object, name) {
    return !object.hasOwnProperty(name) && (name in object);
}

要取得對(duì)象上所有可枚舉的實(shí)例屬性,可以使用ES5的Object.key()方法。這個(gè)方法接收一個(gè)對(duì)象作為參數(shù),返回一個(gè)包含所有可枚舉屬性的字符串?dāng)?shù)組。

function Person(){}
Person.prototype.name = "Jason";
Person.prototype.age = 18;
Person.prototype.job = "Web";
Person.prototype.sayName = function() {
    console.log(this.name);
}

var keys = Object.keys(Person.prototype);
console.log(keys);        //"name,age,job,sayName"

var p1 = new Person();
p1.name = "cor";
p1.age = 11;
var p1keys = Object.keys(p1);
console.log(p1keys);    //"name,age"

所有你想得到所有實(shí)例屬性,無論它是否可以枚舉,都可以使用Object.getOwnPropertyNames()方法。

var keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys);      //"constructor,name,age,job,sayName"
2.3.3 更簡單的原型語法

可以用一個(gè)包含所有屬性和方法的對(duì)象字面量來重寫整個(gè)原型對(duì)象

function Person(){}

Person.prototype = {
    name : "Jason",
    age : 29,
    job : "Web",
    sayName : function() {
        console.log(this.name)
    }
};

用對(duì)象字面的方法和原來的方法會(huì)有區(qū)別: constructor屬性不再指向Person了。每創(chuàng)建一個(gè)函數(shù),就會(huì)同時(shí)創(chuàng)建它的prototype對(duì)象,這個(gè)對(duì)象也會(huì)自動(dòng)獲得constructor屬性。而對(duì)象字面量的寫法,本質(zhì)上重寫了默認(rèn)的prototype對(duì)象,因此constructor屬性也就變成了新對(duì)象的constructor屬性(指向Object構(gòu)造函數(shù)),不再指向Person函數(shù)。次數(shù)盡管instanceof操作符漢能返回正確的結(jié)果,但通過constructor已經(jīng)無法確定對(duì)象的類型了。

var friend = new Person();

console.log(friend instanceof Object);  //true
console.log(friend instanceof Person);    //true
console.log(friend.constructor == Person);  //false
console.log(friend.constructor == Object);  //true

如果constructor的值真的很重要,可以像下面這樣特意將它設(shè)置回適當(dāng)?shù)闹怠?/p>

function Person(){}

Person.prototype = {
    constructor : Person,
    name : "Jason",
    age : 29,
    job : "Web",
    sayName : function() {
        console.log(this.name);
    }
};

默認(rèn)情況下,原生的constructor屬性是不可枚舉的,因此如果你使用兼容ES5的JavaScript引擎,可以試下Object.defineProperty()

function Person(){}

Person.prototype = {
    name : "Jason",
    age : 29,
    job : "Web",
    sayName : function() {
        console.log(this.name);
    }
};
//重設(shè)構(gòu)造函數(shù),只適用ES5兼容的瀏覽器
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});
2.3.4 原型的動(dòng)態(tài)性

由于在原型中查找值的過程是一次搜索,因此我們隊(duì)原型對(duì)象所做的任何修改都能夠立即從實(shí)例上反映出來——即使是先創(chuàng)建了實(shí)例后修改原型也一樣。

var friend = new Person();

Person.prototype.sayHi = function() {
    alert("hi");
};

friend.sayHi();        //"hi"

即使person實(shí)例是在添加新方法之前創(chuàng)建的,但它仍然可以訪問這個(gè)新方法。其原因可以歸結(jié)為實(shí)例與原型之間松散連接關(guān)系。因?yàn)閷?shí)例與原型之間的連接只不過是一個(gè)指針,而非一個(gè)副本,因此就可以在原型中找到新的sayHi屬性并返回保存在那的函數(shù)。

如果是重寫整個(gè)原型對(duì)象,情況就和上面不一樣了。調(diào)用函數(shù)時(shí)會(huì)為實(shí)例添加一個(gè)指向最初原型的[[Prototype]]指針,而把原型修改為另外一個(gè)對(duì)象就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系。請(qǐng)記?。簩?shí)例中的指針僅指向原型,而不指向構(gòu)造函數(shù)。

function Person(){}

var friend = new Person();

Person.prototype = {
    constructor : Person,
    name : "Jason",
    age : 29,
    job : "Web",
    sayName : function() {
        console.log(this.name);
    }
};

friend.sayName();    //error

我們先創(chuàng)建了一個(gè)實(shí)例,然后重寫了其原型對(duì)象。下圖展示了整個(gè)過程

如圖,重寫原型對(duì)象切斷了現(xiàn)有原型與任務(wù)之前已經(jīng)存在的對(duì)象實(shí)例之間的聯(lián)系;friend實(shí)例引用的仍然是最初的原型

2.3.5 原生對(duì)象的原型

原型模式的重要性不僅體現(xiàn)在創(chuàng)建自定義類型方面,就連所有原生的引用類型。都采用這種模式創(chuàng)建的。所有原生引用類型(Object、Array、String,等等)都在其構(gòu)造函數(shù)的原型上定義了方法。

console.log(Array.prototype.sort);            //function(){...}        console.log(String.prototype.substring);    //function(){...}

通過原生對(duì)象的原型,我們也可以自己定義新方法。

String.prototype.startsWith = function(text) {
    return this.indexOf(text) == 0;
};

var msg = "Hello world!";
console.log(msg.startsWith("Hello")); //true
2.3.6 原型對(duì)象的問題

原型的所有屬性是被很多實(shí)例共享的,這種共享對(duì)于函數(shù)非常合適。原型的問題就是其共享的本性造成的。

function Person(){}

Person.prototype = {
    constructor : Person,
    name : "Jason",
    age : 29,
    job : "Web",
    friend : ["Cor", "Sam"],
    sayName : function() {
        console.log(this.name);
    }
}

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Court");
console.log(person1.friends);   //"Cor,Sam,Court"
console.log(person2.friends);   //"Cor,Sam,Court"
console.log(person1.friends === person2.friends);    //true

通過原型共享雖然全部實(shí)例都可以共享屬性和方法??墒菍?shí)例一般都是要有屬于自己的全部屬性。

2.4 組合使用構(gòu)造函數(shù)模式和原型模式

創(chuàng)建自定義類型的最常用方式,就是組合使用構(gòu)造函數(shù)模式與原型模式。構(gòu)造函數(shù)模式用于定義實(shí)例屬性,而原型模式用于定義方法和共享的屬性。這樣每個(gè)實(shí)例都會(huì)有自己的一份實(shí)例屬性的副本,但同時(shí)共享著對(duì)方法的引用,最大限度地節(jié)省了內(nèi)存。這種混成模式還支持向構(gòu)造函數(shù)傳遞參數(shù)。結(jié)合了兩種模式之長。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Cor", "Sam"];
}

Person.prototype = {
    constructor : Person,
    sayName : function() {
        console.log(this.name);
    }
};

var person1 = new Person("Jason", 18, "Web");
var person2 = new Person("cou", 19, "doctor");

person1.friends.push("Van");
console.log(person1.friends);    //"Cor,Sam,Van"
console.log(person2.friends);    //"Cor,Sam"
console.log(person1.friends === person2.friends); //false
console.log(person1.sayName === person2.sayName); //true

構(gòu)造函數(shù)定義屬性,原型定義共享的方法和屬性。這種構(gòu)造函數(shù)與原型混成的模式,是目前在ECMAScript中使用最廣泛,認(rèn)同度最高的一種創(chuàng)建自定義類型的方法。可以說,這是用來定義引用類型的一種默認(rèn)模式。

2.5 動(dòng)態(tài)原型模式

動(dòng)態(tài)原型模式把所有信息都封裝在了構(gòu)造函數(shù)中,而通過構(gòu)造函數(shù)中初始化原型(僅在必要的情況下),又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn)。簡單說可以通過檢查某個(gè)應(yīng)該存在的方法是否有效, 來決定是否需要初始化原型。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;

    //方法
    if(typeof this.sayName != "function") {
        Person.prototype.sayName = function() {
            console.log(this.name);
        };
    }
}

var friend = new Person("Jason", 18, "Web");
friend.sayName();

方法那塊,只在sayName()方法不存在的情況下,才會(huì)將它添加到原型中。這段代碼只會(huì)在初次調(diào)用中構(gòu)造函數(shù)時(shí)才會(huì)執(zhí)行。此后,原型已經(jīng)完成初始化嗎,不需要再做什么修改了。不過要記住,這里對(duì)原型所做的修改,能夠立即在所有實(shí)例中得到反映。因此,這種方法確實(shí)可以說是非常完美。其中if語句檢查的可以是初始化之后應(yīng)該存在的任何屬性和方法——不必用一大堆if語句檢查每個(gè)屬性的每個(gè)方法;只需要檢查一個(gè)即可。對(duì)于采用這種模式創(chuàng)建的對(duì)象,還可以使用instanceof操作符確定它的類型。

注意:使用動(dòng)態(tài)原型模式時(shí),不能使用對(duì)象字面量重寫原型。前面已經(jīng)解釋過了,如果在已經(jīng)創(chuàng)建了實(shí)例的情況下重寫原型,那么就會(huì)切斷現(xiàn)有實(shí)例與新原型之間的練習(xí)。

2.6 寄生構(gòu)造函數(shù)模式

在前面幾種模式都不適用的情況下,可以使用寄生構(gòu)造函數(shù)模式。這種模式的基本思想是創(chuàng)建一個(gè)函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對(duì)象的代碼,然后再返回新創(chuàng)建的對(duì)象;但從表面上看,這個(gè)函數(shù)又很像是典型的構(gòu)造函數(shù)。

function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        console.log(this.name);
    };
    return o;
}

var friend = new Person("Jason", 19, "Web");
friend.sayName();     //"Jason"

除了使用new操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)之外,這個(gè)模式跟工廠模式其實(shí)是一模一樣的。構(gòu)造函數(shù)在不返回值的情況下,默認(rèn)會(huì)返回新對(duì)象的實(shí)例。而通過在構(gòu)造函數(shù)的末尾添加一個(gè)return語句,可以重寫調(diào)用構(gòu)造函數(shù)時(shí)返回的值。

這個(gè)模式可以在特殊的情況下用來為對(duì)象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個(gè)模式。

function SpecialArray() {
    //創(chuàng)建數(shù)組
    var values = new Array();
    
    //添加值
    values.push.apply(values, arguments);

    //添加方法
    values.toPipedString = function() {
        return this.join("|");
    };

    //返回?cái)?shù)組
    return values;
}

var colors = new SpecialArray("red", "blue", "green");
console.log(colors.toPipedString());   //"red|blue|green"

關(guān)于寄生構(gòu)造函數(shù)模式,有一點(diǎn)需要說明: 首先,返回的對(duì)象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型之間沒有關(guān)系;也就是說,構(gòu)造函數(shù)返回的對(duì)象與在構(gòu)造函數(shù)外部創(chuàng)建對(duì)象沒有什么不同。不能依賴instanceof操作符來確定對(duì)象類型。

2.7 穩(wěn)妥構(gòu)造函數(shù)模式

所謂穩(wěn)妥對(duì)象,指的是沒有公共屬性,而且其方法也不引用this的對(duì)象。穩(wěn)妥對(duì)象最適合在一些安全的環(huán)境中(這些環(huán)境中會(huì)禁止使用this和new)

function Person(name, age, job) {
    
    //創(chuàng)建要返回的對(duì)象
    var o = new Object();

    //可以定義私有變量和函數(shù)

    //添加方法
    o.sayName = function() {
        console.log(name)""
    }
    
    //返回對(duì)象
    return o;
}

注意,在以這種模式創(chuàng)建的對(duì)象中,除了使用sayName()方法之外,沒有其他辦法訪問到name的值??梢韵裣旅媸褂梅€(wěn)妥的Person構(gòu)造函數(shù)

var friend = Person("Jason", 18, "Web");
friend.sayName();    //"Jason"

這樣,變量friend中保存的是一個(gè)穩(wěn)妥對(duì)象,除了調(diào)用sayName()方法外,沒有別的方式可以訪問其數(shù)據(jù)成員。

3.繼承

由于函數(shù)沒有簽名,在ECMAScript中無法實(shí)現(xiàn)接口繼承。ECMAScript只支持實(shí)現(xiàn)繼承,而且其實(shí)現(xiàn)繼承主要是依靠原型鏈來實(shí)現(xiàn)的。

3.1 原型鏈

原型鏈?zhǔn)菍?shí)現(xiàn)繼承的主要方法。其基本思想是利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法。

回顧下構(gòu)造函數(shù)、原型和實(shí)例的關(guān)系:

每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象;

原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針;

實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針{{Prototype}}

我們讓原型對(duì)象等于另一個(gè)類型的實(shí)例,原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針,相應(yīng)地,另一個(gè)原型也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針。假如另一個(gè)原型又是另一個(gè)類型的實(shí)例,那么上訴關(guān)系依然成立,如此層層遞進(jìn),就構(gòu)成了實(shí)例與原型的鏈條。

實(shí)現(xiàn)原型鏈的基本模式

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//繼承了SuperType
//SubType的原型對(duì)象等于SubperType的實(shí)例,
//這樣SubType內(nèi)部就會(huì)有一個(gè)指向SuperType的指針從而實(shí)現(xiàn)繼承
SubType.prototype = new SuperType();

SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue());  //true

SubType繼承了superType,而繼承是通過創(chuàng)建SuperType實(shí)例,并將該實(shí)例賦給SubType.prototype實(shí)現(xiàn)的。原來存在于SuperType的實(shí)例中的所有屬性和方法,現(xiàn)在也存在于SubType.prototype中了。這個(gè)例子中的實(shí)例、構(gòu)造函數(shù)和原型之間的關(guān)系如圖:

要注意instance.constructor現(xiàn)在指向的是SuperType,是因?yàn)镾ubType的原型指向了另一個(gè)對(duì)象SuperType的原型,而這個(gè)原型對(duì)象的constructor屬性指向的是SuperType。

通過實(shí)現(xiàn)原型鏈,本質(zhì)上擴(kuò)展了前面說的原型搜索機(jī)制。在通過原型鏈實(shí)現(xiàn)繼承的情況下,搜索過程就得以沿著原型鏈繼續(xù)向上。拿上面的例子來說。調(diào)用instance.getSuperValue()會(huì)經(jīng)歷三個(gè)搜索步驟:

搜索實(shí)例;

搜索SubType.prototype;

搜索SuperType.prototype,最后一步才會(huì)找到該方法。

在找不到屬性或方法的情況下,搜索過程總是要一環(huán)一環(huán)地前行到原型鏈末端才會(huì)停下來。

1. 別忘記默認(rèn)的原型

事實(shí)上,前面的例子中展示的原型鏈還少一環(huán)。所有引用類型默認(rèn)都繼承了Object,而這個(gè)繼承也是通過原型鏈實(shí)現(xiàn)的。要記住,所有函數(shù)的默認(rèn)原型都是Object的實(shí)例,因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)布指針,指向Object.prototype。這也正是所有自定義類型都會(huì)繼承toString()、valueOf()等默認(rèn)方法的根本原因。所以上面的例子展示的原型鏈中還應(yīng)該包含另一個(gè)繼承層次。

一句話,SubType繼承了SuperType,而SuperType繼承了Object。當(dāng)調(diào)用instance.toString()時(shí),實(shí)際上調(diào)用的是保存在Object.prototype中的那個(gè)方法。

2. 確定原型和實(shí)例的關(guān)系

有兩種方式可以確定。

instanceof操作符

只要用這個(gè)操作符來測(cè)試實(shí)例與原型鏈中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會(huì)返回true。

console.log(instance instanceof Object);    //true    
console.log(instance instanceof SuperType);    //true
console.log(instance instanceof SubType);    //true

由于原型鏈的關(guān)系,可以說instanceObject、SuperTypeSubType中任何一個(gè)類型的實(shí)例。因此,都返回true

isPrototypeOf()方法

同樣,只要是原型鏈中出現(xiàn)過得原型,都可以說是該原型鏈所派生的實(shí)例的原型。

console.log(Object.prototype.isPrototypeOf(instance));      //true
console.log(SuperType.prototype.isPrototypeOf(instance)); //true
console.log(SubType.prototype.isPrototypeOf(instance));   //true
3. 謹(jǐn)慎地定義方法

子類型(上例的SubType)有時(shí)候需要重寫超類型(上例的SuperType)中的某個(gè)方法,或者需要添加超類型中不存在的的某個(gè)方法。但不管怎樣,給原型添加方法一定要放在替換原型的語句之后。

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//繼承超類型
SubType.prototype = new SuperType;

//添加新方法
SubType.prototype.getSubValue = function(){
    return this.subproperty;
}

//重寫超類型中的方法
SubType.prototype.getSuperValue = function(){
    return false;
}

當(dāng)通過SubType的實(shí)例調(diào)用getSuperValue()時(shí),調(diào)用的就是這個(gè)重寫的方法,這是因?yàn)樗阉鳈C(jī)制搜索首先從實(shí)例中搜索然后到子類型的原型再到超類型的原型,然后子類型重寫了該方法,搜索機(jī)制首先在子類型的原型中找到了該方法就不會(huì)繼續(xù)繼續(xù)搜索了。但通過SuperType的實(shí)例調(diào)用getSuperValue()時(shí),還會(huì)繼續(xù)調(diào)用原來的那個(gè)方法。這里要注意的是,必須在用SuperType的實(shí)例替換原型之后,再定義著兩個(gè)方法。

還有一點(diǎn)需要注意,在通過原型鏈實(shí)現(xiàn)繼承時(shí),不能使用對(duì)象字面量創(chuàng)建原型方法。這樣做會(huì)重寫原型鏈。

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
}

function SubType(){
    this.subproperty = false;
}

//繼承了SuperType
SubTyoe.prototype = new SuperType();

//使用字面量添加新方法,會(huì)導(dǎo)致上一行代碼無效
SubType.prototype = {
    getSubValue : function(){
        return this.subproperty;
    },
    someOtherMethod : function(){
        return false;
    }
}

var instance = new SubType();
console.log(instance.getSuperValue());  //error!

由于現(xiàn)在的原型包含的是一個(gè)Object的實(shí)例,而非SuperType的實(shí)例,因此我們?cè)O(shè)想中的原型鏈已經(jīng)被切斷了。SubTypeSuperType之間已經(jīng)沒有關(guān)系了。

4. 原型鏈的問題

原型鏈最主要的問題來自包含引用類型值的原型。包含應(yīng)用類型值的原型屬性會(huì)被所有實(shí)例共享;而這也正是為什么要在構(gòu)造函數(shù)中,而不是在原型對(duì)象中定義屬性的原因。在通過原型來實(shí)現(xiàn)繼承時(shí),原型實(shí)際上會(huì)變成另一個(gè)類型的實(shí)例。于是,原先的實(shí)例實(shí)際上會(huì)變成另一個(gè)類型的實(shí)例。于是,原生的實(shí)例屬性也就順理成章地變成了現(xiàn)在的原型屬性了。

function SuperType(){
    this.colors = ["red", "blue", "green"];
}

function SubType(){
}

SubType.prototype = new SuperType();

var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);  //"red, blue, green, black"

var instance2 = new SubType();
console.log(instance2.colors);    //"red, blue, green, black"

這個(gè)例子中的SuperType構(gòu)造函數(shù)定義了一個(gè)colors屬性,該屬性包含一個(gè)數(shù)組(引用類型值)。SuperType的每個(gè)實(shí)例都會(huì)有各自包含自己數(shù)組的colors屬性。當(dāng)SubType通過原型鏈繼承了SuperType之后,SubType.prototype就變成了SuperType(),所以它也用用了一個(gè)colors屬性。就跟專門創(chuàng)建了一個(gè)SubType.prototype.colors屬性一樣。結(jié)果SubType得所有實(shí)例都會(huì)共享這一個(gè)colors屬性。

原型鏈的第二個(gè)問題是:在創(chuàng)建子類型的實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)。實(shí)際上,應(yīng)該說是沒有辦法在不影響所有對(duì)象實(shí)例的情況下,給超類型的構(gòu)造函數(shù)傳遞參數(shù)。所以在實(shí)際運(yùn)用中很少會(huì)多帶帶使用原型鏈。

3.2 借用構(gòu)造函數(shù)

在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。函數(shù)只不過是在特定環(huán)境中執(zhí)行代碼的對(duì)象,因此通過使用apply()call()方法也可以在(將來)新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

function SubType(){
    //繼承了SuperType,同時(shí)還傳遞了參數(shù)
    SuperType.call(this, "Jason");

    //實(shí)例屬性
    this.age = 18;
}

var instance1 = new SubType();
instance1.colors. push("black");
console.log(instance1.colors);         //"red,blue,green,black"
console.log(instance1.name);        //"Jason"
console.log(instance1.age);            //18

var instance2 = new SubType();
console.log(instance2.colors);        //"red,blue,green"

為了確保SuperType構(gòu)造函數(shù)不會(huì)重寫子類型屬性,可以再調(diào)用超類型構(gòu)造函數(shù)后,再添加應(yīng)該在子類型中定義的屬性。

借用構(gòu)造函數(shù)的問題

如果僅僅是借用構(gòu)造函數(shù)那么也將無法避免構(gòu)造函數(shù)模式存在的問題——方法都在構(gòu)造函數(shù)中定義,因此函數(shù)服用就無從談起了。而卻,在超類型的原型中定義的方法,對(duì)子類型而言也是不可見的,結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式。借用構(gòu)造函數(shù)技術(shù)也是很少多帶帶使用的。

3.3 組合繼承

組合繼承(combination inheritance),有時(shí)候也叫做為經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長的一種繼承模式。其背后的思路是使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。這樣,既通過在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能保證每個(gè)實(shí)例都有它自己的屬性。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
};

function SubType(name, age){
    //繼承屬性
    SuperType.call(this, name);
    
    this.age = age;
}

SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
};

var instance1 = new SubType("Jason", 18);
instance1.colors.push("black");
console.log(instance1.colors);    //"red,blue,green,black"
instance1.sayName();            //"Jason"
instance1.sayAge();                //18

var instance2 = new SubType("Cor", 20);
console.log(instance2.colors)l    //"red,blue,green"
instance2.sayName();            //"Cor"
instance2.sayAge();                //20

這樣一來,就可以讓兩個(gè)不同的SubType實(shí)例即分別用有自己的屬性——包括colors屬性,又可有使用相同的方法了。如圖:

組合繼承避免了原型鏈和借用構(gòu)造函數(shù)的缺陷,融合了它們的優(yōu)點(diǎn),成為JavaScript中最常用的繼承模式。而且,instanceofisPrototypeof()也能夠用于識(shí)別基于組合繼承創(chuàng)建的對(duì)象。

3.4 原型式繼承

基本思想是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)還不必因此創(chuàng)建自定義類型。

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

object()函數(shù)內(nèi)部,先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù),然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)類型的一個(gè)新實(shí)例。從本質(zhì)上講,object()對(duì)傳入其中的對(duì)象執(zhí)行了一次深淺復(fù)制。

var person = {
    name: "Jason",
    friends: ["Cor", "Court", "Sam"]
};

var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");

console.log(person.friends);        //"Cor,Court,Sam,Rob,Barbie"

這種原型式繼承,要求你必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ)。如果有這么一個(gè)對(duì)象的話,可以把它傳遞給object()函數(shù),然后再根據(jù)具體需求對(duì)得到的對(duì)象加以修改即可。在這個(gè)例子中,可以作為另一個(gè)對(duì)象的基礎(chǔ)是person對(duì)象,于是我們把它傳入到object()函數(shù)中,然后該函數(shù)就會(huì)返回一個(gè)新對(duì)象。這個(gè)新對(duì)象將person作為原型,所以它的原型中就包含一個(gè)基本類型值屬性和一個(gè)引用類型值屬性。這以為著person.friends不僅屬于person所有,而且也會(huì)被anotherPerson以及yetAnotherPerson共享。實(shí)際上,這就相當(dāng)于又創(chuàng)建了person對(duì)象的兩個(gè)副本。

ECMAScript5通過新增Object.create()方法規(guī)范化了原型式繼承。這個(gè)方法接收兩個(gè)參數(shù):一個(gè)用于做新對(duì)象原型的對(duì)象和(可選的)一個(gè)為新對(duì)象定義額外屬性的對(duì)象。在傳入一個(gè)參數(shù)的情況下,Object.create()object()方法的行為相同

var person = {
    name: "Jason",
    friends: ["Cor", "Court", "Sam"]
};

var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");


var yetAnotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
yetAnotherPerson.friends.push("Barbie");

console.log(yetAnotherPerson.name);    //"Greg"
console.log(anotherPerson.name);    //"Jason"
console.log(person.friends);        //"Cor,Court,Sam,Rob,Barbie"

Object.create()方法的第二個(gè)參數(shù)與Object.defineProperties()方法的第二個(gè)參數(shù)格式相同:每個(gè)屬性都是通過自己的描述符定義的。以這種方式制定的任何屬性會(huì)覆蓋原型對(duì)象上的同名屬性。

別忘了,包含引用類型值的屬性始終都會(huì)共享相應(yīng)的值,就像使用原型模式一樣

3.5 寄生式模式

寄生式(parasitic)繼承是與原型式繼承緊密相關(guān)的一種思路。寄生式繼承的思路與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個(gè)僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強(qiáng)對(duì)象,最后再像真的是它做了所有工作一樣返回對(duì)象。

function createAnother(original){
    var clone = object(original);     //通過調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi = function(){         //以某種方式來增強(qiáng)這個(gè)對(duì)象
         alert("Hi");
    };
    return clone;                     //返回這個(gè)對(duì)象
}

可以像下面這樣來使用createAnother()函數(shù):

var person = {
    name: "Jason",
    friends: ["Shelby", "Court", "Van"]
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();     //"hi"

這個(gè)例子中的代碼基于person返回了一個(gè)新對(duì)象——anotherPerson。新對(duì)象不僅具有person的所有屬性和方法,而且還有自己的sayHi()方法。

3.6 寄生組合式繼承

組合繼承是JavaScript最常用的繼承模式;不過它也有不足。組合繼承最大的問題就是無論什么情況下,都會(huì)調(diào)用兩次超類型的構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時(shí)候,另一次是在子類型構(gòu)造函數(shù)內(nèi)部。

function SuperType(name){
    this.name = name;
    this.colors = ["red", "bule", "grenn"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
}

function SubType(name, age){
    SuperType.call(this, name);          //第二次調(diào)用SuperType
 
    this.age = age;
}

SubType.prototype = new SuperType();     //第一次調(diào)用SuperType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
    console.log(this.age);
}

有注釋的兩行代碼是調(diào)用SuperType構(gòu)造函數(shù)的代碼,第一次調(diào)用SuperType構(gòu)造函數(shù)時(shí),SubType.prototype會(huì)有SuperType的實(shí)例屬性。第二次調(diào)用SuperType的構(gòu)造函數(shù)時(shí)SubType會(huì)在構(gòu)造函數(shù)中添加了SuperType的實(shí)例屬性。當(dāng)創(chuàng)建SubType的實(shí)例它的[[Prototype]]和自身上都有相同屬性。根據(jù)搜索機(jī)制自身的屬性就會(huì)屏蔽SubType原型對(duì)象上的屬性。等于原型對(duì)象上的屬性是多余的了。如圖:

如圖所示,有兩組namecolors屬性:一組在實(shí)例上,一組在SubType原型中。這就是調(diào)用兩次SuperType構(gòu)造函數(shù)的結(jié)果。解決辦法是——寄生組合式繼承

所謂寄生組合式繼承,即通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路是:不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù),我們所需要的無非就是超類型原型的一個(gè)副本而已。本質(zhì)上,就是使用寄生式來繼承超類型的原型,然后再將結(jié)果指定給子類型的原型。寄生組合式的基本模式如下:

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);   //創(chuàng)建對(duì)象
    prototype.constructor = subType;               //增強(qiáng)對(duì)象
    subType.prototype = prototype                  //指定對(duì)象
}

這個(gè)實(shí)力的inheritPrototype()函數(shù)實(shí)現(xiàn)了寄生組合式繼承的最簡單形式。這個(gè)函數(shù)接受兩個(gè)參數(shù):子類型構(gòu)造函數(shù)和超類型構(gòu)造函數(shù)。在函數(shù)內(nèi)部,第一步是創(chuàng)建超類型原型的一個(gè)副本。第二部是為創(chuàng)建的副本添加constructor屬性,從而彌補(bǔ)因重寫原型而失去的默認(rèn)的constructor屬性。最后一步,將新創(chuàng)建的對(duì)象(即副本)賦值給子類型的原型。

function object(o){
    function F(){}    //創(chuàng)建個(gè)臨時(shí)構(gòu)造函數(shù)
    F.prototype = o;  //superType.prototype
    return new F();   //返回實(shí)例
}

function inheritPrototype(subType, superType){
    /*  創(chuàng)建對(duì)象
        傳入超類型的原型,通過臨時(shí)函數(shù)進(jìn)行淺復(fù)制,F(xiàn).prototype的指針就指向superType.prototype,在返回new F()    
    */
    var prototype = object(superType.prototype);   
    prototype.constructor = subType;               //增強(qiáng)對(duì)象
    /*  指定對(duì)象
        子類型的原型等于F類型的實(shí)例,當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例后,該實(shí)例會(huì)包含一個(gè)[[prototype]]的指針指向構(gòu)造函數(shù)的原型對(duì)象,所以subType.prototype指向了超類型的原型對(duì)象這樣實(shí)現(xiàn)了繼承,因?yàn)闃?gòu)造函數(shù)F沒有屬性和方法這樣就子類型的原型中就不會(huì)存在超類型構(gòu)造函數(shù)的屬性和方法了。
    */
    subType.prototype = prototype                  //new F();
}

function SuperType(name){
    this.name = name;
    this.colors = ["red", "bule", "grenn"];
}

SuperType.prototype.sayName = function(){
    console.log(this.name);
}

function SubType(name, age){
    SuperType.call(this, name);          
 
    this.age = age;
}

inheritPrototype(SubType, SuperType);A

SubType.prototype.sayAge = function(){
    console.log(this.age);
}

var ins1 = new SubType("Jason", 18);

下圖是我自己的理解:

最后,如有錯(cuò)誤和疑惑請(qǐng)指出,多謝各位大哥

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/82614.html

相關(guān)文章

  • 重學(xué)前端學(xué)習(xí)筆記(七)--JavaScript對(duì)象面向對(duì)象還是基于對(duì)象?

    摘要:對(duì)象有狀態(tài)對(duì)象具有狀態(tài),同一對(duì)象可能處于不同狀態(tài)之下。中對(duì)象獨(dú)有的特色對(duì)象具有高度的動(dòng)態(tài)性,這是因?yàn)橘x予了使用者在運(yùn)行時(shí)為對(duì)象添改狀態(tài)和行為的能力。小結(jié)由于的對(duì)象設(shè)計(jì)跟目前主流基于類的面向?qū)ο蟛町惙浅4螅瑢?dǎo)致有不是面向?qū)ο筮@樣的說法。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些...

    mayaohua 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(七)--JavaScript對(duì)象面向對(duì)象還是基于對(duì)象?

    摘要:對(duì)象有狀態(tài)對(duì)象具有狀態(tài),同一對(duì)象可能處于不同狀態(tài)之下。中對(duì)象獨(dú)有的特色對(duì)象具有高度的動(dòng)態(tài)性,這是因?yàn)橘x予了使用者在運(yùn)行時(shí)為對(duì)象添改狀態(tài)和行為的能力。小結(jié)由于的對(duì)象設(shè)計(jì)跟目前主流基于類的面向?qū)ο蟛町惙浅4?,?dǎo)致有不是面向?qū)ο筮@樣的說法。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些...

    yy736044583 評(píng)論0 收藏0
  • 重學(xué)前端學(xué)習(xí)筆記(七)--JavaScript對(duì)象面向對(duì)象還是基于對(duì)象?

    摘要:對(duì)象有狀態(tài)對(duì)象具有狀態(tài),同一對(duì)象可能處于不同狀態(tài)之下。中對(duì)象獨(dú)有的特色對(duì)象具有高度的動(dòng)態(tài)性,這是因?yàn)橘x予了使用者在運(yùn)行時(shí)為對(duì)象添改狀態(tài)和行為的能力。小結(jié)由于的對(duì)象設(shè)計(jì)跟目前主流基于類的面向?qū)ο蟛町惙浅4螅瑢?dǎo)致有不是面向?qū)ο筮@樣的說法。 筆記說明 重學(xué)前端是程劭非(winter)【前手機(jī)淘寶前端負(fù)責(zé)人】在極客時(shí)間開的一個(gè)專欄,每天10分鐘,重構(gòu)你的前端知識(shí)體系,筆者主要整理學(xué)習(xí)過程的一些...

    xingpingz 評(píng)論0 收藏0
  • JavaScript面向對(duì)象編程學(xué)習(xí)筆記---概念定義

    摘要:子類繼承自父類的方法可以重新定義即覆寫,被調(diào)用時(shí)會(huì)使用子類定義的方法什么是多態(tài)青蛙是一個(gè)對(duì)象,金魚也是一個(gè)對(duì)象,青蛙會(huì)跳,金魚會(huì)游,定義好對(duì)象及其方法后,我們能用青蛙對(duì)象調(diào)用跳這個(gè)方法,也能用金魚對(duì)象調(diào)用游這個(gè)方法。 1、專用術(shù)語 面向?qū)ο缶幊坛绦蛟O(shè)計(jì)簡稱:OOP,在面向?qū)ο缶幊讨谐S玫降母拍钣校簩?duì)象、屬性、方法、類、封裝、聚合、重用與繼承、多態(tài)。 2、什么是對(duì)象? 面向?qū)ο缶幊痰闹攸c(diǎn)...

    mikasa 評(píng)論0 收藏0
  • JavaScript學(xué)習(xí)筆記第四天_面向對(duì)象編程

    摘要:即另外,注意到構(gòu)造函數(shù)里的屬性,都沒有經(jīng)過進(jìn)行初始化,而是直接使用進(jìn)行綁定。并且在模式下,構(gòu)造函數(shù)沒有使用進(jìn)行調(diào)用,也會(huì)導(dǎo)致報(bào)錯(cuò)。調(diào)用構(gòu)造函數(shù)千萬不要忘記寫。 1. 基礎(chǔ) JavaScript不區(qū)分類和實(shí)例的概念,而是通過原型來實(shí)現(xiàn)面向?qū)ο缶幊?。Java是從高級(jí)的抽象上設(shè)計(jì)的類和實(shí)例,而JavaScript的設(shè)計(jì)理念,聽起來就好比Heros里的Peter,可以復(fù)制別人的能力。JavaS...

    weapon 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<