溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JavaScript 常見的繼承方式匯總

發(fā)布時間:2020-10-08 13:13:07 來源:腳本之家 閱讀:324 作者:guoqi 欄目:開發(fā)技術(shù)

原型鏈機制:

  在ECMAscript中描述了原型鏈的概念,并將原型鏈作為實現(xiàn)繼承的主要方法,其基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。

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

  每個構(gòu)造函數(shù)都有一個原型對象(prototype),原型對象都包含一個指向構(gòu)造函數(shù)的指針(constructor),而實例都包含一個指向原型對象的內(nèi)部指針 ( __propto__ ) 。關(guān)系圖如下圖所示:

JavaScript 常見的繼承方式匯總

  每一個Function都是Object基類的一個實例,所以每一個Function上都有一個__proto__指向了Object.prototype。

  當查找一個實例的屬性時,會先從這個實例的自定義屬性上找,如果沒有的話通過__proto__去實例所屬類的原型上去找,如果還沒有的話再通過原型(原型也是對象,只要是對象就有__proto__屬性)的__proto__到Object的原型上去找,一級一級的找,如果沒有就undefined。

  所以引用類型之間的繼承就是通過原型鏈機制實現(xiàn)的。

一.原型繼承

  原型繼承:把父類的私有+公有的屬性和方法,都作為子類公有的屬性。

  核心:不是把父類私有+公有的屬性克隆一份一模一樣的給子類的公有。他是通過__proto__建立和子類之間的原型鏈,當子類的實例需要使用父類的屬性和方法的時候,可以通過__proto__一級級找上去使用?!?/p>

function Parent(){
  this.x = 199;
  this.y = 299;
}
Parent.prototype.say = function(){
  console.log('say')
}
function Child(){
  this.g = 90;
}
Child.prototype = new Parent();
var p = new Parent();
var c = new Child();
console.dir(c)

   實現(xiàn)的本質(zhì)是重寫了原型對象 ,通過將子類的原型指向了父類的實例,所以子類的實例就可以通過__proto__訪問到 Child.prototype 也就是 Parent的實例,這樣就可以訪問到父類的私有方法。然后再通過__proto__指向父類的prototype就可以獲得到父類原型上的方法。

  這樣就做到了將父類的私有、公有方法和屬性都當做子類的公有屬性。這樣就通過原型鏈實現(xiàn)了繼承。

  但是別忘了默認的原型,因為所有引用類型都是繼承了Object的,所有說子類也可以訪問到Object上的方法如toString() 、valueOf() 等。

  結(jié)果如下圖所示:

JavaScript 常見的繼承方式匯總

  有的時候我們需要在子類中添加新的方法或者是重寫父類的方法時候,切記一定要放到替換原型的語句之后

function Parent(){
  this.x = 199;
  this.y = 299;
}
Parent.prototype.say = function(){
  console.log('say')
}
function Child(){
  this.g = 90;
}
/*Child.prototype.Bs = function(){
  console.log('Bs')
}*/在這里寫子類的原型方法和屬性是沒用的因為會改變原型的指向,所以應(yīng)該放到重新指定之后
Child.prototype = new Parent();
Child.prototype.constructor=Child//由于重新修改了Child的原型導致默認原型上的constructor丟失,我們需要自己添加上,其實沒啥用,加不加都一樣
Child.prototype.Bs = function(){
  console.log('Bs')
}
Child.prototype.say = function(){
  console.log('之后改的')
}
var p = new Parent();
var c = new Child();
console.dir(c)
c.Bs() //Bs
c.say()  // 之后改的
p.say() //say 不影響父類實例訪問父類的方法

  存在的問題:

  1. 子類繼承父類的屬性和方法是將父類的私有屬性和公有方法都作為自己的公有屬性和方法,我們要清楚一件事情就是我們操作基本數(shù)據(jù)類型的時候操作的是值,在操作應(yīng)用數(shù)據(jù)類型的時候操作的是地址,如果說父類的私有屬性中引用類型的屬性,那他被子類繼承的時候會作為公有屬性,這樣子類一操作這個屬性的時候,會影響到子類二。

  2. 在創(chuàng)建子類的實例時,不能向父類型的構(gòu)造函數(shù)中傳遞參數(shù)。應(yīng)該說是沒有辦法在不影響所有對象實例的情況下,給父類的構(gòu)造函數(shù)傳遞參數(shù)。

  所以在實際中很少單獨使用原型繼承。

二.call繼承

  改變方法的this指向,同時執(zhí)行方法。 在子類構(gòu)造函數(shù)中父類.call(this) 可以將父類的私有變成子類的私有。

function Parent() {
  this.x = 100;
  this.y = 199;
}
Parent.prototype.fn = function() {}
 
function Child() {
  this.d = 100;
  Parent.call(this); //構(gòu)造函數(shù)中的this就是當前實例
}
var p = new Parent();
var c = new Child();
console.log(p) //Parent {x: 100, y: 199}
console.log(c) //Child {d: 100, x: 100, y: 199}

  在子類的構(gòu)造函數(shù)中,改變父類的this指向,改變?yōu)樽宇?/span>的實例,同時運行父類方法,這樣父類中的this.x就變成了子類的實例.x ,通過這種方法就可以繼承了父類的私有屬性,且只能繼承父類的私有屬性和方法。

三.冒充對象繼承

  冒充對象繼承的原理是循環(huán)遍歷父類實例,然后父類實例的私有方法全部拿過來添加給子類實例。

function Parent(){
  this.x = 100;
}
Parent.prototype.getX = function(){
  console.log('getX')
}
function Child(){
  var p = new Parent();
  for(var attr in p){//for in 可以遍歷到原型上的公有自定義屬性
    this[attr] = p[attr]
  }
  //以下代碼是只獲得到私有方法和屬性,如果不加這個的話就可以遍歷到所有方法和屬性
  /*if(e.hasOwnProperty(attr)){
    this[attr] = e[attr]
  }
  e.propertyIsEnumerable()*///可枚舉屬性==> 可以拿出來一一列舉的屬性
}
var p = new Parent();
var c = new Child();
console.dir(c)

  for in 可以遍歷到原型上的公有自定義屬性 ,所以他可以拿到私有和公有的屬性和方法,這個你可以遍歷私有和公有的,需要你加限制條件。但是如果不做hasOwnProperty判斷那么就是把父類的公有的和私有的都拿過來當私有的。

四.混合繼承

  就是將call繼承和原型繼承集合在一起,無論是私有的還是公有的都拿過來了。但是有個問題就是子類的原型上的多了一套父類私有屬性,但是不會產(chǎn)生問題。因為子類的私有屬性也有一套相同的通過call繼承拿過來的。

function Parent(){
  this.x=100;
}
Parent.prototype.getX = function(){}
function Child(){
  Parent.call(this);
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var p = new Parent();
var c = new Child();
console.log(c)//Child {x: 100}

  存在的問題:

  無論在什么情況下,都會調(diào)用兩次構(gòu)造函數(shù):一次是在創(chuàng)建子類型原型的時候,另一次是在子類型構(gòu)造函數(shù)的內(nèi)部,沒錯,子類型最終會包含父類型對象的全部實例屬性,但我們不得不在調(diào)用子類構(gòu)造函數(shù)時重寫這些屬性。

  還有一種就是call+拷貝繼承

//混合繼承:call繼承+拷貝繼承
  function extend(newEle,oldEle){
    for(var attr in oldEle){
      newEle[attr]=oldEle[attr];
    }
  }
  function F(){
    this.x=100;
    this.showX=function(){}
  }
  F.prototype.getX=function(){};
  F.prototype.getX1=function(){};
  var f1=new F;
  console.dir(f1)
  function S(){
    F.call(this)//call繼承
  }
  extend(S.prototype, F.prototype);//拷貝繼承
  S.prototype.cc=function(){ }
  var p1=new S;
  console.dir(p1);

  這種方式使用call繼承將父類的私有方法繼承過來,使用for in 拷貝將父類的公有屬性和方法繼承過來,比較實用。

五.中間件繼承

  中間件繼承就是通過原型鏈的機制,子類的prototype.__proto__本來應(yīng)該是直接指向Object.prototype。

  從父類的原型上的__proto__也可以到Object.prototype,在父類.prototype上停留了下,父類.prototype就是一個中間件,所以子類可以繼承到父類的公有方法當做自己的公有方法。

function Parent(){
  this.x = 100;
}
Parent.prototype.getX = function(){}
function Child(){
  
}
Child.prototype.__proto__ = Parent.prototype;
var p = new Parent();
var c = new Child()
console.log(c)

六.寄生組合式繼承

   寄生式組合: call繼承+Object.create();

   所謂寄生組合式繼承就是通過借用構(gòu)造函數(shù)來繼承屬性,通過原型鏈的混合形式來繼承方法。

   基本思路是不必為了指定子類的原型而調(diào)用父類的構(gòu)造函數(shù),我們所需要的就是父類型原型的一個副本。

   本質(zhì)上,就是使用寄生式繼承父類的原型,然后再將結(jié)果指定給子類的原型。

function F(){
  this.x=100;
}
F.prototype.showX=function(){};
function S(){
  this.y = 200
  F.call(this)//只繼承了私有的;
}
function inheritPrototype(subType,superType){
  var prototype = Object.create(superType.prototype);//創(chuàng)建對象
  prototype.constructor = subType;//增強對象
  subType.prototype = prototype;//指定對象
}
inheritPrototype(S,F)
var p1=new S;
console.dir(p1)

  1、第一步是創(chuàng)建父類型原型的一個副本。

  2、第二步是為創(chuàng)建的副本增加constructor屬性,從而彌補了因為重寫原型而失去的默認的constructor屬性。

  3、第三步是將創(chuàng)建的對象賦值給子類型的原型。

  這個例子的高效率體現(xiàn)在他只調(diào)用了一次SuperType 構(gòu)造函數(shù),并且因此避免了在SubType.prototype上面創(chuàng)建不必要的、多余的屬性。與此同時原型鏈還能保持不變,所以可以正常使用instanceof 和 isPrototypeOf() ,所以寄生組合繼承是引用類型最理想的繼承方法。

七.class繼承

  class 可以通過extends關(guān)鍵字實現(xiàn)繼承,這比 ES5 的通過修改原型鏈實現(xiàn)繼承,要清晰和方便很多。

class Father{
  constructor(x, y) {
     this.x = x;
     this.y = y;
  }

  toString() {
     return '(' + this.x + ', ' + this.y + ')';
  }
}
class Son extends Father{
  constructor(x,y,color){
     super(x,y); // 調(diào)用父類的constructor(x, y)
     this.color = color;
  }
  toString() {
        console.log( super.toString()+this.color); // 調(diào)用父類的toString()
  }
}
let son = new Son(3,4,'red');
son.toString();//結(jié)果為(3,4)red

   上面代碼定義了一個Son類,該類通過extends關(guān)鍵字,繼承了Father類的所有屬性和方法。

  上面代碼中,constructor方法和toString方法之中,都出現(xiàn)了super關(guān)鍵字,它在這里表示父類的構(gòu)造函數(shù),用來新建父類的this對象。

  子類必須在constructor方法中調(diào)用super方法,否則新建實例時會報錯。這是因為子類自己的this對象,必須先通過父類的構(gòu)造函數(shù)完成塑造,得到與父類同樣的實例屬性和方法,然后再對其進行加工,加上子類自己的實例屬性和方法。如果不調(diào)用super方法,子類就得不到this對象。

以上就是JavaScript 常見的繼承方式匯總的詳細內(nèi)容,更多關(guān)于JavaScript 繼承方式的資料請關(guān)注億速云其它相關(guān)文章!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI