成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript系列--淺析原型鏈與繼承

draveness / 602人閱讀

摘要:綜上所述有原型鏈繼承,構(gòu)造函數(shù)繼承經(jīng)典繼承,組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身是實(shí)現(xiàn)基于類型繼承的最有效方法。

一、前言

繼承是面向?qū)ο螅∣OP)語言中的一個(gè)最為人津津樂道的概念。許多面對(duì)對(duì)象(OOP)語言都支持兩種繼承方式::接口繼承 和 實(shí)現(xiàn)繼承 。

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

二、概念 2.1簡(jiǎn)單回顧下構(gòu)造函數(shù),原型和實(shí)例的關(guān)系:

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

關(guān)系如下圖所示:

2.2什么是原型鏈

每一個(gè)對(duì)象擁有一個(gè)原型對(duì)象,通過__proto__指針指向上一個(gè)原型,并從中繼承方法和屬性,同時(shí)原型對(duì)象也可能擁有原型,這樣一層層的,最終指向null。這種關(guān)系成為原型鏈(prototype chain)。

假如有這樣一個(gè)要求:instance實(shí)例通過原型鏈找到了Father原型中的getFatherValue方法.

function Father(){
    this.property = true;
}
Father.prototype.getFatherValue = function(){
    return this.property;
}
function Son(){
    this.sonProperty = false;
}
//繼承 Father
Son.prototype = new Father();//Son.prototype被重寫,導(dǎo)致Son.prototype.constructor也一同被重寫
Son.prototype.getSonVaule = function(){
    return this.sonProperty;
}
var instance = new Son();
console.log(instance.getFatherValue());//true

注意: 此時(shí)instance.constructor指向的是Father,這是因?yàn)镾on.prototype中的constructor被重寫的緣故.

2.3確定原型和實(shí)例之間的關(guān)系

使用原型鏈后, 我們?cè)趺慈ヅ袛嘣秃蛯?shí)例的這種繼承關(guān)系呢? 方法一般有兩種.

1、第一種是使用 instanceof 操作符。只要用這個(gè)操作符來測(cè)試實(shí)例(instance)與原型鏈中出現(xiàn)過的構(gòu)造函數(shù),結(jié)果就會(huì)返回true. 以下幾行代碼就說明了這點(diǎn)。

console.log(instance instanceof Object);//true
console.log(instance instanceof Father);//true
console.log(instance instanceof Son);//true
由于原型鏈的關(guān)系, 我們可以說instance 是 Object, Father 或 Son中任何一個(gè)類型的實(shí)例. 因此, 這三個(gè)構(gòu)造函數(shù)的結(jié)果都返回了true.

2、第二種是使用 isPrototypeOf() 方法,。同樣只要是原型鏈中出現(xiàn)過的原型,isPrototypeOf() 方法就會(huì)返回true

console.log(Object.prototype.isPrototypeOf(instance));//true
console.log(Father.prototype.isPrototypeOf(instance));//true
console.log(Son.prototype.isPrototypeOf(instance));//true

2.4原型鏈的問題

原型鏈并非十分完美, 它包含如下兩個(gè)問題:

問題一: 當(dāng)原型鏈中包含引用類型值的原型時(shí),該引用類型值會(huì)被所有實(shí)例共享;

問題二: 在創(chuàng)建子類型(例如創(chuàng)建Son的實(shí)例)時(shí),不能向超類型(例如Father)的構(gòu)造函數(shù)中傳遞參數(shù).

有鑒于此, 實(shí)踐中很少會(huì)多帶帶使用原型鏈。為此,下面將有一些嘗試以彌補(bǔ)原型鏈的不足。

三、借用構(gòu)造函數(shù)(經(jīng)典繼承)

為解決原型鏈中上述兩個(gè)問題, 我們開始使用一種叫做借用構(gòu)造函數(shù)(constructor stealing)的技術(shù)(也叫經(jīng)典繼承).

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

function Father(){
    this.colors = ["red","blue","green"];
}
function Son(){
    Father.call(this);//繼承了Father,且向父類型傳遞參數(shù)
}
var instance1 = new Son();
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"

var instance2 = new Son();
console.log(instance2.colors);//"red,blue,green" 可見引用類型值是獨(dú)立的

很明顯,借用構(gòu)造函數(shù)一舉解決了原型鏈的兩大問題:

其一, 保證了原型鏈中引用類型值的獨(dú)立,不再被所有實(shí)例共享;

其二, 子類型創(chuàng)建時(shí)也能夠向父類型傳遞參數(shù).

隨之而來的是, 如果僅僅借用構(gòu)造函數(shù),那么將無法避免構(gòu)造函數(shù)模式存在的問題–方法都在構(gòu)造函數(shù)中定義, 因此函數(shù)復(fù)用也就不可用了.而且超類型(如Father)中定義的方法,對(duì)子類型而言也是不可見的. 考慮此,借用構(gòu)造函數(shù)的技術(shù)也很少多帶帶使用。

四、組合繼承

組合繼承, 有時(shí)候也叫做偽經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮兩者之長(zhǎng)的一種繼承模式.

基本思路: 使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,通過借用構(gòu)造函數(shù)來實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承.

這樣,既通過在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用,又能保證每個(gè)實(shí)例都有它自己的屬性. 如下所示.

function Father(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}
Father.prototype.sayName = function(){
    alert(this.name);
};
function Son(name,age){
    Father.call(this,name);//繼承實(shí)例屬性,第一次調(diào)用Father()
    this.age = age;
}
Son.prototype = new Father();//繼承父類方法,第二次調(diào)用Father()
Son.prototype.sayAge = function(){
    alert(this.age);
}
var instance1 = new Son("louis",5);
instance1.colors.push("black");
console.log(instance1.colors);//"red,blue,green,black"
instance1.sayName();//louis
instance1.sayAge();//5

var instance1 = new Son("zhai",10);
console.log(instance1.colors);//"red,blue,green"
instance1.sayName();//zhai
instance1.sayAge();//10

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

同時(shí)我們還注意到組合繼承其實(shí)調(diào)用了兩次父類構(gòu)造函數(shù), 造成了不必要的消耗, 那么怎樣才能避免這種不必要的消耗呢, 這個(gè)我們將在后面講到。

五、原型繼承

該方法最初由道格拉斯·克羅克福德于2006年在一篇題為 《Prototypal Inheritance in JavaScript》(JavaScript中的原型式繼承) 的文章中提出. 他的想法是借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象, 同時(shí)還不必因此創(chuàng)建自定義類型. 大意如下:

在object()函數(shù)內(nèi)部, 先創(chuàng)建一個(gè)臨時(shí)性的構(gòu)造函數(shù), 然后將傳入的對(duì)象作為這個(gè)構(gòu)造函數(shù)的原型,最后返回了這個(gè)臨時(shí)類型的一個(gè)新實(shí)例。

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

從本質(zhì)上講, object() 對(duì)傳入其中的對(duì)象執(zhí)行了一次淺復(fù)制. 下面我們來看看為什么是淺復(fù)制.

在 ECMAScript5 中,通過新增 object.create() 方法規(guī)范化了上面的原型式繼承.

object.create() 接收兩個(gè)參數(shù):1、一個(gè)用作新對(duì)象原型的對(duì)象;2、(可選的)一個(gè)為新對(duì)象定義額外屬性的對(duì)象

var person = {
    friends : ["Van","Louis","Nick"]
};
var anotherPerson = Object.create(person);
anotherPerson.friends.push("Rob");

var yetAnotherPerson = Object.create(person);
yetAnotherPerson.friends.push("Style");

alert(person.friends);//"Van,Louis,Nick,Rob,Style"

object.create() 只有一個(gè)參數(shù)時(shí)功能與上述object方法相同, 它的第二個(gè)參數(shù)與Object.defineProperties()方法的第二個(gè)參數(shù)格式相同: 每個(gè)屬性都是通過自己的描述符定義的.以這種方式指定的任何屬性都會(huì)覆蓋原型對(duì)象上的同名屬性.例如:

var person = {
    name : "Van"
};
var anotherPerson = Object.create(person, {
    name : {
        value : "Louis"
    }
});
alert(anotherPerson.name);//"Louis"

目前支持 Object.create() 的瀏覽器有 IE9+, Firefox 4+, Safari 5+, Opera 12+ 和 Chrome.

提醒: 原型式繼承中, 包含引用類型值的屬性始終都會(huì)共享相應(yīng)的值, 就像使用原型模式一樣.

六、寄生式繼承

寄生式繼承是與原型式繼承緊密相關(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)用object函數(shù)創(chuàng)建一個(gè)新對(duì)象
    clone.sayHi = function(){//以某種方式來增強(qiáng)這個(gè)對(duì)象
        alert("hi");
    };
    return clone;//返回這個(gè)對(duì)象
}

這個(gè)例子中的代碼基于person返回了一個(gè)新對(duì)象–anotherPerson. 新對(duì)象不僅具有 person 的所有屬性和方法, 而且還被增強(qiáng)了, 擁有了sayH()方法.

注意: 使用寄生式繼承來為對(duì)象添加函數(shù), 會(huì)由于不能做到函數(shù)復(fù)用而降低效率;這一點(diǎn)與構(gòu)造函數(shù)模式類似.

七、寄生組合式繼承

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

寄生組合式繼承就是為了降低調(diào)用父類構(gòu)造函數(shù)的開銷而出現(xiàn)的

其背后的基本思路是: 不必為了指定子類型的原型而調(diào)用超類型的構(gòu)造函數(shù)

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

extend的高效率體現(xiàn)在它沒有調(diào)用superClass構(gòu)造函數(shù),因此避免了在subClass.prototype上面創(chuàng)建不必要,多余的屬性. 于此同時(shí),原型鏈還能保持不變; 因此還能正常使用 instanceof 和 isPrototypeOf() 方法。

綜上所述有:原型鏈繼承,構(gòu)造函數(shù)繼承(經(jīng)典繼承),組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身,是實(shí)現(xiàn)基于類型繼承的最有效方法。

下面我們來看下extend的另一種更為有效的擴(kuò)展.

function extend(subClass, superClass) {
  var F = function() {};
  F.prototype = superClass.prototype;
  subClass.prototype = new F(); 
  subClass.prototype.constructor = subClass;

  subClass.superclass = superClass.prototype;
  if(superClass.prototype.constructor == Object.prototype.constructor) {
    superClass.prototype.constructor = superClass;
  }
}

我一直不太明白的是為什么要 "new F()", 既然extend的目的是將子類型的 prototype 指向超類型的 prototype,為什么不直接做如下操作呢?

subClass.prototype = superClass.prototype;//直接指向超類型prototype
顯然, 基于如上操作, 子類型原型將與超類型原型共用, 根本就沒有繼承關(guān)系.

八、new操作符

為了追本溯源, 我順便研究了new運(yùn)算符具體干了什么?發(fā)現(xiàn)其實(shí)很簡(jiǎn)單,就干了三件事情.

var obj  = {};
obj.__proto__ = F.prototype;
F.call(obj);

第一行,我們創(chuàng)建了一個(gè)空對(duì)象obj;

第二行,我們將這個(gè)空對(duì)象的__proto__成員指向了F構(gòu)造函數(shù)的prototype對(duì)象;

第三行,我們將F函數(shù)對(duì)象的this指針替換成obj,然后再調(diào)用F函數(shù).

new 操作符調(diào)用構(gòu)造函數(shù)的時(shí)候,函數(shù)內(nèi)部實(shí)際上發(fā)生以下變化:

1、創(chuàng)建一個(gè)空對(duì)象,并且 this 變量引用該對(duì)象,同時(shí)還繼承了該函數(shù)的原型。

2、屬性和方法被加入到 this 引用的對(duì)象中。

3、新創(chuàng)建的對(duì)象由 this 所引用,并且最后隱式的返回 this.

九、屬性查找

使用了原型鏈后, 當(dāng)查找一個(gè)對(duì)象的屬性時(shí),JavaScript 會(huì)向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達(dá)原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性,就會(huì)返回 undefined。

此時(shí)若想避免原型鏈查找, 建議使用hasOwnProperty方法. 因?yàn)閔asOwnProperty是 JavaScript 中唯一一個(gè)處理屬性但是不查找原型鏈的函數(shù)。

console.log(instance1.hasOwnProperty("age"));//true
對(duì)比: isPrototypeOf 則是用來判斷該方法實(shí)例對(duì)象是不是參數(shù)的原型對(duì)象,是則返回true,否則返回false。如

console.log(Father.prototype.isPrototypeOf(instance1));//true
十、instanceof && typeof

instanceof 運(yùn)算符是用來在運(yùn)行時(shí)指出對(duì)象是否是構(gòu)造器的一個(gè)實(shí)例。

instanceof 可以正確的判斷對(duì)象的類型,因?yàn)閮?nèi)部機(jī)制是通過判斷對(duì)象的原型鏈中是不是能找到類型的 prototype。

例如漏寫了new運(yùn)算符去調(diào)用某個(gè)構(gòu)造器, 此時(shí)構(gòu)造器內(nèi)部可以通過 instanceof 來判斷.(java中功能類似)

function f(){
  if(this instanceof arguments.callee)
    console.log("此處作為構(gòu)造函數(shù)被調(diào)用");
  else
    console.log("此處作為普通函數(shù)被調(diào)用");
}
f();//此處作為普通函數(shù)被調(diào)用
new f();//此處作為構(gòu)造函數(shù)被調(diào)用

對(duì)比: typeof 則用以獲取一個(gè)變量或者表達(dá)式的類型, 一般只能返回如下幾個(gè)結(jié)果:

1、typeof 對(duì)于基本類型,除了 null 都可以顯示正確的類型

2、typeof 對(duì)于對(duì)象,除了函數(shù)都會(huì)顯示 object

3、對(duì)于 null 來說,雖然它是基本類型,但是會(huì)顯示 object

總的來說:number,boolean,string,function(函數(shù)),object(NULL,數(shù)組,對(duì)象),undefined。

十一、參考

1、《JavaScript高級(jí)程序設(shè)計(jì)》

2、深入理解JavaScript系列(5):強(qiáng)大的原型和原型鏈

【注:我是saucxs,也叫songEagle,松寶寫代碼,文章首發(fā)于sau交流學(xué)習(xí)社區(qū)(https://www.mwcxs.top),關(guān)注我們每天閱讀更多精彩內(nèi)容】

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/104690.html

相關(guān)文章

  • 徹底理解Javascript中的原型鏈與繼承

    摘要:在節(jié)中,我們學(xué)習(xí)到了通過構(gòu)造函數(shù)創(chuàng)建對(duì)象的三個(gè)重要步驟,其中的一步是把構(gòu)造函數(shù)的對(duì)象設(shè)置為創(chuàng)建對(duì)象的原型。利用而不是直接用創(chuàng)建一個(gè)實(shí)例對(duì)象的目的是,減少一次調(diào)用父構(gòu)造函數(shù)的執(zhí)行。 JavaScript語言不像面向?qū)ο蟮木幊陶Z言中有類的概念,所以也就沒有類之間直接的繼承,JavaScript中只有對(duì)象,使用函數(shù)模擬類,基于對(duì)象之間的原型鏈來實(shí)現(xiàn)繼承關(guān)系,ES6的語法中新增了class關(guān)鍵...

    ziwenxie 評(píng)論0 收藏0
  • Js基礎(chǔ)知識(shí)(二) - 原型鏈與繼承精彩的講解

    摘要:有了原型鏈,就有了繼承,繼承就是一個(gè)對(duì)象像繼承遺產(chǎn)一樣繼承從它的構(gòu)造函數(shù)中獲得一些屬性的訪問權(quán)。這里其實(shí)就是一個(gè)原型鏈與繼承的典型例子,開發(fā)中可能構(gòu)造函數(shù)復(fù)雜一點(diǎn),屬性定義的多一些,但是原理都是一樣的。 作用域、原型鏈、繼承與閉包詳解 注意:本章講的是在es6之前的原型鏈與繼承。es6引入了類的概念,只是在寫法上有所不同,原理是一樣的。 幾個(gè)面試常問的幾個(gè)問題,你是否知道 insta...

    mrcode 評(píng)論0 收藏0
  • Js基礎(chǔ)知識(shí)(二) - 原型鏈與繼承精彩的講解

    摘要:有了原型鏈,就有了繼承,繼承就是一個(gè)對(duì)象像繼承遺產(chǎn)一樣繼承從它的構(gòu)造函數(shù)中獲得一些屬性的訪問權(quán)。這里其實(shí)就是一個(gè)原型鏈與繼承的典型例子,開發(fā)中可能構(gòu)造函數(shù)復(fù)雜一點(diǎn),屬性定義的多一些,但是原理都是一樣的。 作用域、原型鏈、繼承與閉包詳解 注意:本章講的是在es6之前的原型鏈與繼承。es6引入了類的概念,只是在寫法上有所不同,原理是一樣的。 幾個(gè)面試常問的幾個(gè)問題,你是否知道 insta...

    lingdududu 評(píng)論0 收藏0
  • JavasScript重難點(diǎn)知識(shí)

    摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

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

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

0條評(píng)論

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