JavaScript 高級程序設(shè)計(第6章 面向?qū)ο蟪绦蛟O(shè)計)

第6章 面向?qū)ο蟪绦蛟O(shè)計

1.理解對象

(1) 建自定義對象的方式
*創(chuàng)建一個object實例曲横,然后添加屬性和方法番电。

var person = new Object();
    person.name = "Nicholas";
    person.age = 29;
    person.job = "Software Engineer";
    person.sayName = function(){
        alert(this.name);
};

* 對象字面量

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName: function(){
        alert(this.name);
} };

(2) 屬性類型
ECMAScript 中有兩種屬性:數(shù)據(jù)屬性訪問器屬性蛀缝。
* 數(shù)據(jù)屬性:
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置案疲。在這個位置可以讀取和寫入值芥喇。
數(shù)據(jù)屬性有4 個描述其行為的特性权逗。

  1. [[Configurable]]:表示能否通過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改為訪問器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的
    這個特性默認值為 true捣染。
  2. [[Enumerable]]:表示能否通過 for-in 循環(huán)返回屬性骄瓣。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值為 true。
  3. [[Writable]]:表示能否修改屬性的值耍攘。像前面例子中那樣直接在對象上定義的屬性,它們的這個特性默認值為 true榕栏。
  4. [[Value]]:包含這個屬性的數(shù)據(jù)值。讀取屬性值的時候,從這個位置讀;寫入屬性值的時候,把新值保存在這個位置。這個特性的默認值為 undefined。

1. Object.defineProperty()方法 :修改屬性默認的特性危尿,這個方法接收三個參數(shù):屬性所在的對象寿弱、屬性的名字和一個描述符對象。
2. 在調(diào)用 Object.defineProperty()方法時,如果不指定,configurable嚷硫、enumerable 和 writable 特性的默認值都是 false检访。

var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas"
 person.name = "Greg";  //在嚴格模式下會導(dǎo)致錯誤。
alert(person.name); //"Nicholas"

3. 一旦把屬性定義為不可配置的, 就不能再把它變回可配置了仔掸。


var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
alert(person.name); //"Nicholas" 
delete person.name; //在嚴格模式下會導(dǎo)致錯誤脆贵。
alert(person.name); //"Nicholas"

//拋出錯誤
Object.defineProperty(person, "name", {
    configurable: true,
    value: "Nicholas"
});

* 訪問器屬性
訪問器屬性不包含數(shù)據(jù)值,它們包含一對兒 getter 和 setter 函數(shù)。
問器屬性有如下 4 個特性:

  1. [[Configurable]]:表示能否通過 delete 刪除屬性從而重新定義屬性,能否修改屬性的特 性,或者能否把屬性修改為數(shù)據(jù)屬性起暮。對于直接在對象上定義的屬性,這個特性的默認值為 true卖氨。
  2. [[Enumerable]]:表示能否通過 for-in 循環(huán)返回屬性。對于直接在對象上定義的屬性,這 5 個特性的默認值為 true负懦。
  3. [[Get]]:在讀取屬性時調(diào)用的函數(shù)筒捺。默認值為 undefined。
  4. [[Set]]:在寫入屬性時調(diào)用的函數(shù)纸厉。默認值為 undefined系吭。

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

var book = {
    _year: 2004,
edition: 1 };
Object.defineProperty(book, "year", {
    get: function(){
        return this._year;
    },
    set: function(newValue){
        if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
      } 
   }
});
book.year = 2005; alert(book.edition); //2


===========================

//定義訪問器的舊有方法 
book.__defineGetter__("year", function(){
    return this._year;
});
book.__defineSetter__("year", function(newValue){
    if (newValue > 2004) {
        this._year = newValue;
        this.edition += newValue - 2004;
    }
});

(3) 定義多個屬性
Object.defineProperties()方法:可以通過描述符一次定義多個屬性颗品。

這個方法接收兩個對象參數(shù):

  1. 第一 個對象是要添加和修改其屬性的對象肯尺。
  2. 第二個對象的屬性與第一個對象中要添加或修改的屬性一一對應(yīng)。
//book 對象上定義了兩個數(shù)據(jù)屬性(_year 和 edition)和一個訪問器屬性(year)躯枢。
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;
                }
             }
         }
});

(3) 讀取屬性的特性
bject.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符则吟。

  1. 這個方法接收兩個參數(shù):屬性所在的對象和要讀取其描述符的屬性名稱。
  2. 返回值是一個對象,如果是訪問器屬性,這個對象的屬性有 configurable锄蹂、enumerable氓仲、get 和 set;如果是數(shù)據(jù)屬性,這 個對象的屬性有 configurable、enumerable、writable 和 value寨昙。
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //"undefined"

var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value); //undefined 
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //"function"

2. 創(chuàng)建對象

(1) 工廠模式

function createPerson(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(this.name);
        };
        return o;
 }
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

(2) 構(gòu)造函數(shù)模式
1. 創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法讥巡。
2. 造函數(shù)始終都應(yīng)該以一個 大寫字母開頭,而非構(gòu)造函數(shù)則應(yīng)該以一個小寫字母開頭。
3. 要創(chuàng)建 Person 的新實例,必須使用 new 操作符舔哪。

function Person(name, age, job){
        this.name = name;
        this.age = age;
        this.job = job;
        this.sayName = function(){
        alert(this.name);
        };
 }
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

4. 創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實例標(biāo)識為一種特定的類型;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方欢顷。
* 將構(gòu)造函數(shù)當(dāng)作函數(shù)

任何函數(shù),只要通過 new 操作符來調(diào)用,那它就可以作為構(gòu)造函數(shù)。

// 當(dāng)作構(gòu)造函數(shù)使用
var person = new Person("Nicholas", 29, "Software Engineer"); 
person.sayName(); //"Nicholas"

// 作為普通函數(shù)調(diào)用
Person("Greg", 27, "Doctor"); // 添加到window 
window.sayName(); //"Greg"

// 在另一個對象的作用域中調(diào)用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse"); 
o.sayName(); //"Kristen"

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

使用構(gòu)造函數(shù)的主要問題,就是每個方法都要在每個 實例上重新創(chuàng)建一遍捉蚤。

(3) 原型模式

  1. 每個函數(shù)都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象, 而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法抬驴。
  2. 按照字面意思來理解,那 么 prototype 就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實例的原型對象。使用原型對象的好處是可以 讓所有對象實例共享它所包含的屬性和方法缆巧。換句話說,不必在構(gòu)造函數(shù)中定義對象實例的信息,而是 可以將這些信息直接添加到原型對象中布持。
    function Person(){
    }
    Person.prototype.name = "Nicholas";
    Person.prototype.age = 29;
    Person.prototype.job = "Software Engineer";
    Person.prototype.sayName = function(){
        alert(this.name);
    };
    var person1 = new Person();
    person1.sayName();   //"Nicholas"
    var person2 = new Person();
    person2.sayName(); //"Nicholas"
    alert(person1.sayName == person2.sayName);  //true

*理解原型對象
1.只要創(chuàng)建了一個新函數(shù),就會根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個 prototype 屬性,這個屬性指向函數(shù)的原型對象。
2. 在默認情況下,所有原型對象都會自動獲得一個 constructor (構(gòu)造函數(shù))屬性,這個屬性包含一個指向 prototype 屬性所在函數(shù)的指針陕悬。Person.prototype. constructor 指向 Person题暖。通過這個構(gòu)造函數(shù),我們還可繼續(xù)為原型對象 添加其他屬性和方法。

  1. 調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例后,該實例的內(nèi)部將包含一個指針(內(nèi)部 屬性),指向構(gòu)造函數(shù)的原型對象捉超。ECMA-262 第 5 版中管這個指針叫[[Prototype]]胧卤。
  2. 連接存在于實例與構(gòu)造函數(shù)的原型對象之間,而不是存在于實例與構(gòu)造函數(shù)之間。
Person 構(gòu)造函數(shù)拼岳、Person 的原型屬性以及 Person 現(xiàn)有的兩個實例之間的關(guān)系

Person.prototype 指向了原型對象,
Person.prototype.constructor 又指回了 Person枝誊。
原型對象中除了包含 constructor 屬性之外,還包括后來添加的其他屬性。
Person 的每個實例 person1 和 person2 都包含一個內(nèi)部屬性,該屬性僅僅指向了 Person.prototype惜纸。

isPrototypeOf() 方法

  1. 所有實現(xiàn)中都無法訪問到[[Prototype]],但可以通過 isPrototypeOf()方法來確定對象之間是否存在這種關(guān)系叶撒。
  2. 如果[[Prototype]]指向調(diào)用 isPrototypeOf()方法的對象 (Person.prototype),那么這個方法就返回 true。
//person1 和 person2它們內(nèi)部都 有一個指向 Person.prototype 的指針耐版。
 alert(Person.prototype.isPrototypeOf(person1));  //true
 alert(Person.prototype.isPrototypeOf(person2));  //true

Object.getPrototypeOf()方法 :在所有支持的實現(xiàn)中,這個 方法返回[[Prototype]]的值祠够。

alert(Object.getPrototypeOf(person1) == Person.prototype); //true 
alert(Object.getPrototypeOf(person1).name); //"Nicholas"

3. 每當(dāng)代碼讀取某個對象的某個屬性時,先后執(zhí)行兩次搜索,首先 從對象實例本身開始搜索,再續(xù)搜索指針指向的原型對象。
4.通過對象實例訪問保存在原型中的值,但卻不能通過對象實例重寫原型中的值椭更。

如果我們 在實例中添加了一個屬性,而該屬性與實例原型中的一個屬性同名,那我們就在實例中創(chuàng)建該屬性,該 屬性將會屏蔽原型中的那個屬性哪审。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer"; 
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Greg";
alert(person1.name); //"Greg"——來自實例 alert(person2.name); //"Nicholas"——來自原型

將這個屬性設(shè)置為 null,也 只會在實例中設(shè)置這個屬性,而不會恢復(fù)其指向原型的連接。使用 delete 操作符則可以完全刪 除實例屬性,從而讓我們能夠重新訪問原型中的屬性虑瀑。

person1.name = "Greg";
alert(person1.name);//"Greg"——來自實例
alert(person2.name); //"Nicholas"——來自原型
delete person1.name;
alert(person1.name);//"Nicholas"——來自原型

hasOwnProperty()方法

可以檢測一個屬性是存在于實例中,還是存在于原型中湿滓。
在給定屬性存在于對象實例中時,才會返回 true

alert(person1.hasOwnProperty("name"));  //false

person1.name = "Greg";
alert(person1.name); //"Greg"——來自實例 
alert(person1.hasOwnProperty("name")); //true

alert(person2.name); //"Nicholas"——來自原型 
alert(person2.hasOwnProperty("name")); //false

delete person1.name;
alert(person1.name); //"Nicholas"——來自原型 
alert(person1.hasOwnProperty("name")); //false

* 原型與in操作符
in 操作符:單獨使用和 在for-in 循環(huán)中使用。

  1. 在單獨使用時,in 操作符會在通 過對象能夠訪問給定屬性時返回 true,無論該屬性存在于實例中還是原型中舌狗。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var person1 = new Person();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name); //"Greg" ——來自實例 
alert(person1.hasOwnProperty("name")); //true 
alert("name" in person1); //true

alert(person2.name); //"Nicholas" ——來自原型
alert(person2.hasOwnProperty("name")); //false 
alert("name" in person2); //true
  • 同時使用 hasOwnProperty()方法和 in 操作符,就可以確定該屬性到底是存在于對象中,還是存在于原型中
function hasPrototypeProperty(object, name){
        return !object.hasOwnProperty(name) && (name in object);
}
  1. 在使用 for-in 循環(huán)時,返回的是所有能夠通過對象訪問的叽奥、可枚舉的(enumerated)屬性,其中既包括存在于實例中的屬性,也包括存在于原型中的屬性。屏蔽了原型中不可枚舉屬性(即將 [[Enumerable]]標(biāo)記為 false 的屬性)的``實例屬性也會在 for-in 循環(huán)中返回,因為根據(jù)規(guī)定,所有開發(fā)人員定義的屬性都是可枚舉的痛侍。
var o = {
    toString : function(){
        return "My Object";
    }
};
for (var prop in o){
    if (prop == "toString"){
        alert("Found toString");//在 IE 中不會顯示
} }

ECMAScript 5 也將 constructor 和 prototype 屬性的[[Enumerable]]特性設(shè)置為 false,但并不是所有瀏覽器都照此實現(xiàn)朝氓。

Object.keys()方法

取得對象上所有可枚舉的實例屬性,接收一個對象作為參數(shù),返回一個包含所有可枚舉屬性的字符串?dāng)?shù)組魔市。

function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
var keys = Object.keys(Person.prototype);
alert(keys);       //"name,age,job,sayName"

var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);    //"name,age"

object.getOwnPropertyNames() 方法

得到所有實例屬性,無論它是否可枚舉。

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);    //"constructor,name,age,job,sayName"

* 更簡單的原型語法

用一個包含所有屬性和方法的對象字面量來重寫整個原型對象赵哲。

function Person(){
}
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job: "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

constructor 屬性不再指向 Person ,我們在這里使用的語法,本質(zhì)上完全重寫了默認的 prototype 對象,因此 constructor 屬性也就變成了新 對象的 constructor 屬性(指向 Object 構(gòu)造函數(shù)),不再指向 Person 函數(shù)待德。
盡管 instanceof 操作符還能返回正確的結(jié)果,但通過 constructor 已經(jīng)無法確定對象的類型了。

var friend = new Person();
alert(friend instanceof Object);//true
alert(friend instanceof Person);//true
alert(friend.constructor == Person);//false
alert(friend.constructor == Object);//true

重設(shè) constructor 屬性會導(dǎo)致它的[[Enumerable]]特性被設(shè)置為 true枫夺。默認 情況下,原生的 constructor 屬性是不可枚舉的

function Person(){
}
Person.prototype = {
    constructor : Person,
    name : "Nicholas", 
     age : 29,
    job: "Software Engineer",
    sayName : function () {
    alert(this.name);
    }
};

解決:

function Person(){
    }
    Person.prototype = {
        name : "Nicholas",
        age : 29,
        job : "Software Engineer",
        sayName : function () {
PrototypePatternExample07.htm
};
//重設(shè)構(gòu)造函數(shù),只適用于 ECMAScript 5 兼容的瀏覽器 
Object.defineProperty(Person.prototype, "constructor", {
        enumerable: false,
        value: Person
    });

* 4. 原型的動態(tài)性

  1. 我們對原型對象所做的任何修改都能夠立即從實例上反映出來——即使是先創(chuàng)建了實例后修改原型也照樣如此将宪。
    當(dāng)我們調(diào)用 person.sayHi() 時,首先會在實例中搜索名為 sayHi 的屬性,在沒找到的情況下,會繼續(xù)搜索原型。
var friend = new Person();
Person.prototype.sayHi = function(){
    alert("hi");
};
friend.sayHi(); //"hi"(沒有問題!)
  1. 調(diào)用構(gòu)造函數(shù)時會為實例添加一個指向最初原型的 [[Prototype]]指針,而把原型修改為另外一個對象就等于切斷了構(gòu)造函數(shù)與最初原型之間的聯(lián)系橡庞。
    實例中的指針僅指向原型,而不指向構(gòu)造函數(shù)
function Person(){
}
var friend = new Person();
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
friend.sayName();   //error
重寫原型對象

* 原生對象的原型

  1. 原生引用類型(Object较坛、Array、String,等等)都在其構(gòu)造函數(shù)的原型上定義了方法扒最。
  2. 通過原生對象的原型,不僅可以取得所有默認方法的引用,而且也可以定義新方法丑勤。

* 原型對象的問題

  1. 略了為構(gòu)造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié),結(jié)果所有實例在 默認情況下都將取得相同的屬性值。
  2. 通過在實例上添加一個同名屬性,可以隱藏原型中的對應(yīng)屬 性吧趣。然而,對于包含引用類型值的屬性來說,問題就比較突出了法竞。

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

  1. 構(gòu)造函數(shù)模式用于定義實例屬性。
  2. 原型模式用于定義方法和共享的屬性再菊。
    每個實例都會有自己的一份實例屬性的副本, 但同時又共享著對方法的引用,最大限度地節(jié)省了內(nèi)存爪喘。
function Person(name, age, job){
this.name = name; 3 this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
2
}
Person.prototype = {
    constructor : Person,
    sayName : function(){
        alert(this.name);
    }
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert(person1.friends);    //"Shelby,Count,Van"
alert(person2.friends);    //"Shelby,Count"
alert(person1.friends === person2.friends);//false
alert(person1.sayName === person2.sayName);//true

(5) 動態(tài)原型模式

把所有信息都封裝在了構(gòu)造函數(shù)中,而通過在構(gòu)造函數(shù) 中初始化原型(僅在必要的情況下),又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點。

function Person(name, age, job){
//屬性
this.name = name;
this.age = age; 
this.job = job;
//方法
if (typeof this.sayName != "function"){
    Person.prototype.sayName = function(){
        alert(this.name);
    };
 }
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

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

基本思想是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后再返回新創(chuàng)建的對象纠拔。

function Person(name, age, job){
        var o = new Object();
        o.name = name;
        o.age = age;
        o.job = job;
        o.sayName = function(){
            alert(this.name);
        };
        return o; 
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

構(gòu)造函數(shù)在不返回值的情況下,默認會返回新對象實例。而通過在構(gòu)造函數(shù)的末尾添加一個 return 語句,可以重寫調(diào)用構(gòu)造函數(shù)時返回的值泛豪。

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

function SpecialArray(){
    //創(chuàng)建數(shù)組
    var values = new Array();
    //添加值
    values.push.apply(values, arguments);
    //添加方法
    values.toPipedString = function(){
            return this.join("|");
     };
    //返回數(shù)組
    return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"

寄生構(gòu)造函數(shù)模式說明:

  1. 返回的對象與構(gòu)造函數(shù)或者與構(gòu)造函數(shù)的原型屬 性之間沒有關(guān)系;也就是說,構(gòu)造函數(shù)返回的對象與在構(gòu)造函數(shù)外部創(chuàng)建的對象沒有什么不同诡曙。
  2. 不能依賴 instanceof 操作符來確定對象類型臀叙。

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

  1. 所謂穩(wěn)妥對象,指的是沒有公共屬性,而且其方法也不引用 this 的對象。
  2. 穩(wěn)妥對象最適合在 一些安全的環(huán)境中(這些環(huán)境中會禁止使用 this 和 new),或者在防止數(shù)據(jù)被其他應(yīng)用程序(如 Mashup 程序)改動時使用价卤。

穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)不同點:一是新創(chuàng)建對象的 實例方法不引用 this;二是不使用 new 操作符調(diào)用構(gòu)造函數(shù)劝萤。

function Person(name, age, job){
//創(chuàng)建要返回的對象
var o = new Object();
//可以在這里定義私有變量和函數(shù)
//添加方法
o.sayName = function(){
alert(name);
};
//返回對象
return o; 
}
//以這種模式創(chuàng)建的對象中,除了使用 sayName()方法之外,
//沒有其他辦法訪問 name 的值。

var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

3. 繼承

(1) 原型鏈

  1. ECMAScript 中描述了原型鏈的概念,并將原型鏈作為實現(xiàn)繼承的主要方法慎璧。
  2. 基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法床嫌。

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

每 個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針,而實例都包含一個指向原型 對象的內(nèi)部指針胸私。

讓原型對象等于另一個類型的實例:

此時的原型對象將包含一個指向另一個原型的指針,相應(yīng)地,另一個原型中也包含著一個指向另一個構(gòu)造函數(shù) 的指針厌处。

假如另一個原型又是另一個類型的實例,那么上述關(guān)系依然成立,如此層層遞進,就構(gòu)成了實 例與原型的鏈條。這就是所謂原型鏈的基本概念岁疼。

實現(xiàn)原型鏈基本模式:

function SuperType(){
        this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());//true

實現(xiàn)的本質(zhì)是重寫原型對象,代之以一個新類型的實例阔涉。換句話說,原來存在于 SuperType 的實例中的所有屬性和方法,現(xiàn)在也存在于 SubType.prototype 中了。`

實例以及構(gòu)造函數(shù)和原型之間的關(guān)系

  1. 要注意instance.constructor現(xiàn)在指向的 是 SuperType,這是因為原來 SubType.prototype 中的 constructor 被重寫了的緣故。(實際上,不是 SubType 的原型的 constructor 屬性被重寫了,而是 SubType 的原型指向了另一個對象—— SuperType 的原型,而這個原型對象的 constructor 屬性指向的是 SuperType瑰排。)
  2. 原型搜索機制:三個搜索步驟:
    1)搜索實例;
    2)搜索 SubType.prototype;
    3)搜索 SuperType.prototype,最后一步才會找到該方法贯要。

* 默認的原型
1. 所有引用類型默認都繼承了 Object,而 這個繼承也是通過原型鏈實現(xiàn)的。
2. 所有函數(shù)的默認原型都是 Object 的實例,因此默認原型都會包含一個內(nèi)部指針,指向 Object.prototype椭住。

完整的原型鏈

* 確定原型和實例的關(guān)系
instanceof 操作符

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

alert(instance instanceof Object);//true
alert(instance instanceof SuperType);//true
alert(instance instanceof SubType);//true

isPrototypeOf()方法

只要是原型鏈中出現(xiàn)過的原型,都可以說是該原型鏈所派生的實例的原型,因此 isPrototypeOf()方法也會返回 true。

 alert(Object.prototype.isPrototypeOf(instance));//true
 alert(SuperType.prototype.isPrototypeOf(instance));//true
 alert(SubType.prototype.isPrototypeOf(instance));//true

* 謹慎地定義方法
1. 原型添加方法的代碼一定要放在替換原型的語句之后函荣。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
//添加新方法
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
//重寫超類型中的方法
 SubType.prototype.getSuperValue = function (){
    return false;
};
var instance = new SubType();
alert(instance.getSuperValue());   //false

2. 過原型鏈實現(xiàn)繼承時,不能使用對象字面量創(chuàng)建原型方法显押。因為這 樣做就會重寫原型鏈。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
function SubType(){
    this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
//使用字面量添加新方法,會導(dǎo)致上一行代碼無效 
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
},
    someOtherMethod : function (){
        return false;
} };
var instance = new SubType();
alert(instance.getSuperValue());   //error!

* 原型鏈的問題
1. 最主要的問題來自包含引 用類型值的原型傻挂。

包含引用類型值的原型屬性會被所有實例共享;而 這也正是為什么要在構(gòu)造函數(shù)中,而不是在原型對象中定義屬性的原因乘碑。在通過原型來實現(xiàn)繼承時,原 型實際上會變成另一個類型的實例。于是,原先的實例屬性也就順理成章地變成了現(xiàn)在的原型屬性了金拒。

function SuperType(){
        this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//繼承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType(); instance1.colors.push("black"); 
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
 alert(instance2.colors); //"red,blue,green,black"

2. 在創(chuàng)建子類型的實例時,不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)兽肤。

(2) 借用構(gòu)造函數(shù)
基本思想:子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù)。

function SuperType(){
    this.colors = ["red", "blue", "green"];
function SubType(){
//繼承了 SuperType
    SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"

* 傳遞參數(shù)

function SuperType(name){
    this.name = name;
}
function SubType(){
//繼承了 SuperType,同時還傳遞了參數(shù) SuperType.call(this, "Nicholas");
//實例屬性
    this.age = 29;
}
var instance = new SubType();
alert(instance.name);    //"Nicholas";
alert(instance.age);     //29

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

方法都在構(gòu)造函數(shù)中定 義,因此函數(shù)復(fù)用就無從談起了绪抛。而且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式资铡。

(3) 組合繼承
基本思想: 使用原型鏈實現(xiàn)對原型屬性和方 法的繼承,而通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承。

function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
        alert(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(){
    alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);//"red,blue,green,black"
instance1.sayName();//"Nicholas";
instance1.sayAge();//29

var instance2 = new SubType("Greg", 27);
alert(instance2.colors);//"red,blue,green"
instance2.sayName();//"Greg";
instance2.sayAge();//27

(4) 原型式繼承
基本思想:借助原型可以基于已有的對象創(chuàng)建新對象,同時還不必因此創(chuàng)建自定義類型幢码。

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

var person = {
        name: "Nicholas",
        friends: ["Shelby", "Court", "Van"]
    };
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

Object.create()方法 :范化了原型式繼承

接收兩個參數(shù):

  1. 用作新對象原型的對象
  2. (可選的)一個為新對象定義額外屬性的對象

傳入一個參數(shù)的情況下:

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"

第二個參數(shù):

var person = {
        name: "Nicholas",
        friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
alert(anotherPerson.name); //"Greg"

(6) 寄生式繼承
基本思想:創(chuàng)建一個僅用于封裝繼承過程的函數(shù),該函數(shù)在內(nèi)部以某種方式來增強對象,最后再像真地是它做了所有工作一樣返回對象笤休。

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

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = createAnother(person);
anotherPerson.sayHi(); //"hi"
//基于 person 返回了一個新對象——anotherPerson。新對象不僅具有 person
// 的所有屬性和方法,而且還有自己的 sayHi()方法症副。

(6) 寄生組合式繼承
組合繼承問題就是無論什么情況下,都會調(diào)用兩次超類型構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時候,另一次是 在子類型構(gòu)造函數(shù)內(nèi)部店雅。

function SuperType(name){
        this.name = name;
        this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
        alert(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(){
    alert(this.age);
};
  1. 在第一次調(diào)用 SuperType 構(gòu)造函數(shù)時, SubType.prototype 會得到兩個屬性:name 和 colors;它們都是 SuperType 的實例屬性,只不過 現(xiàn)在位于 SubType 的原型中。
  2. 當(dāng)調(diào)用 SubType 構(gòu)造函數(shù)時,又會調(diào)用一次 SuperType 構(gòu)造函數(shù),這 一次又在新對象上創(chuàng)建了實例屬性 name 和 colors贞铣。于是,這兩個屬性就屏蔽了原型中的兩個同名屬 性闹啦。

寄生組合式繼承的基本模式:

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

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    alert(this.name);
};
function SubType(name, age){
    SuperType.call(this, name);
    this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
    alert(this.age);
};

寄生組合式繼承
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市辕坝,隨后出現(xiàn)的幾起案子窍奋,更是在濱河造成了極大的恐慌,老刑警劉巖酱畅,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件琳袄,死亡現(xiàn)場離奇詭異,居然都是意外死亡圣贸,警方通過查閱死者的電腦和手機挚歧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吁峻,“玉大人滑负,你說我怎么就攤上這事在张。” “怎么了矮慕?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵帮匾,是天一觀的道長。 經(jīng)常有香客問我痴鳄,道長瘟斜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任痪寻,我火速辦了婚禮螺句,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橡类。我一直安慰自己蛇尚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布顾画。 她就那樣靜靜地躺著取劫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪研侣。 梳的紋絲不亂的頭發(fā)上谱邪,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音庶诡,去河邊找鬼惦银。 笑死,一個胖子當(dāng)著我的面吹牛灌砖,可吹牛的內(nèi)容都是我干的璧函。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼基显,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了善炫?” 一聲冷哼從身側(cè)響起撩幽,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎箩艺,沒想到半個月后窜醉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡艺谆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年榨惰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片静汤。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡琅催,死狀恐怖居凶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情藤抡,我是刑警寧澤侠碧,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站缠黍,受9級特大地震影響弄兜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瓷式,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一替饿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贸典,春花似錦视卢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蔬充,卻和暖如春蝶俱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饥漫。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工榨呆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庸队。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓积蜻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彻消。 傳聞我的和親對象是個殘疾皇子竿拆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容