1. 程式人生 > >js 中繼承方式小談

js 中繼承方式小談

題外話

前段時間面試中筆試題有這道題目:
請實現一個繼承鏈,要求如下:

  • 建構函式A():建構函式中有consoleA方法,可以實現console.log("a")
    例項物件 a:a 可以呼叫consoleA方法

  • 建構函式B():建構函式中有consoleB方法,可以實現console.log("b")
    例項物件 b:b 可以呼叫consoleAconsoleB方法

  • 建構函式C():建構函式中有consoleC方法,可以實現console.log("c")
    例項物件 c:c 可以呼叫consoleAconsoleBconsoleB方法

ok,這個題目暫時擱置,再回到這個問題之前,我們先來看看 js 的繼承方式

約定

// 父類
function Super() {
  this.name = "parent0";
  this.colors = ["red", "blue", "yellow"];
}
Super.prototype.sex = "男";
Super.prototype.say = function() {
  console.log(" Oh,My God! ");
};

構造繼承

原理

通過使用 call、apply 方法可以在新建立的物件上執行建構函式,用父類的建構函式來增加子類的例項

實現

function Sub() {
  Super.call(this);
  this.type = "sub";
}

var s = new Sub();

console.log(s.colors); //[ 'red', 'blue', 'yellow' ]
s.say(); //報錯 sub.say is not a function

優缺點

  • 優點:簡單明瞭,直接繼承超類建構函式的屬性和方法
  • 缺點:無法繼承原型鏈上的屬性和方法

原型鏈式繼承(借用原型鏈實現繼承)

實現

function Sub() {
  this.type = "sub";
}
Sub.prototype = new Super();
var s1 = new Sub();
var s2 = new Sub();
sub1.colors.push("black");
console.log(s1.colors); //[ 'red', 'blue', 'yellow', 'black' ]
console.log(s2.colors); //[ 'red', 'blue', 'yellow', 'black' ]
sub1.say();

我們例項化了兩個 Sub,在例項 s1 中為父類的 colors 屬性 push 了一個顏色,但是 s2 也被跟著改變了。造成這種現象的原因就是原型鏈上中的原型物件它倆是共用的。這不是我們想要的,s1 和 s2 這個兩個物件應該是隔離的,

組合繼承

這裡所謂的組合是指組合借用建構函式和原型鏈繼承兩種方式。

function Sub() {
  Super.call(this);
  this.type = "sub";
}
Sub.prototype = new Super();
var s1 = new Sub();
var s2 = new Sub();
s1.colors.push("black");
console.log(s1.colors); //[ 'red', 'blue', 'yellow', 'black' ]
console.log(s2.colors); //[ 'red', 'blue', 'yellow' ]

可以看到,s2和s1兩個例項物件已經被隔離了,但這種方式仍有缺點。父類的建構函式被執行了兩次,第一次是Sub.prototype = new Super(),第二次是在例項化的時候,這是沒有必要的。那我們就繼續優化吧!!

組合式繼承優化1

function Sub(){
    Super.call(this)
    this.type = "sub";
}
Sub.prototype=Super.prototype
var s1=new Sub()
var s2=new Sub()

但是請看以下程式碼

console.log(s1.constructor.name);//Super

我們還可以用.constructor來觀察物件是不是某個類的例項:從這裡可以看到,s1的建構函式居然是父類Super,而不是子類sub,這顯然不是我們想要的。

組合式繼承優化2

function Sub(){
    Super.call(this)
    this.type = "sub";
}
Sub.prototype=Super.prototype
Sub.constructor=Sub
var s1=new Sub()
var s2=new Sub()
console.log(s1 instanceof Sub);//true
console.log(s1 instanceof Super);//true
console.log(s1.constructor.name);//Super

perfect!!

開題解答

回到開頭的問題中來,

function A(){}
A.prototype.consoleA=function(){
    console.log("a")
}
function B(){
    A.call(this)
}
B.prototype.consoleB=function(){
    console.log("b")
}
Object.assign(B.prototype,A.prototype)
function C(){
    B.call(this)
}
C.prototype.consoleC=function(){
    console.log("c")
}
Object.assign(C.prototype,B.prototype)

參考資料

前端面試必備之JS繼承方式總結
大前端之路----JS繼承的六種方式