小議JS原型鏈、繼承

nyz發表於2018-12-08

繼承是前端面試必問,說到繼承,就必須談一談原型鏈。

原型鏈

我面試的時候都會這麼回答原型鏈:js萬物皆物件,用var a={} 或 var a = new Object()或者用建構函式的形式:var a = new A()建立一個物件時,該物件不僅可以訪問它自身的屬性,還會根據__proto__屬性找到它原型鏈上的屬性,直到找到Object上面的null。如有不貼切,望評論不足之處哈。
更詳細的參考mdn: developer.mozilla.org/zh-CN/docs/…

對於下面的例子:

var A = function(){
    this.name="xiaoming";
}
A.prototype.age=9;
var a = new A();
console.log(a.age); //9
複製程式碼

我參考以前學習java時對例項和Class的圖畫了一個原型鏈的圖,不太好,但是畫圖不易,小女子求您一個贊哈。

小議JS原型鏈、繼承
言歸正傳,圖中長方形代表例項物件a,圓形代表原型,三角形代表建構函式。由圖可知:

a.__proto__ === A.prototype; //true
A.prototype.constructor===A; //true
A.prototype.__proto__===Object.prototype; //true
Object.prototype.__proto__===null; //true
複製程式碼

上方示例可以在看完我的解釋之後再回顧一遍。例項和原型之間是通過__proto__屬性連線,且是單向的,從例項指向原型原型和建構函式之間連線是雙向的,通過constructor和prototype連線,具體見圖;原型鏈上的屬性是所有例項共享的,看下面的例子:

var A = function(){
    this.name="xiaoming";
}
var a = new A();
A.prototype.age=9;
var b = new A();
console.log(a.age); //9
console.log(b.age); //9
複製程式碼

a、b都可以訪問A原型鏈上的屬性age。

Function和Object比較特殊,他們既是物件又是函式,兩者內部同時含有proto和prototype屬性,可看下面程式碼:

Object.__proto__ === Function.prototype //true
Object.__proto__ === Function.__proto__//true
Object.prototype === Function.prototype.__proto__ // true
Function instanceof Object //true
Object instanceof Function //true
複製程式碼

至此,原型鏈的知識差不多可以理解了,後面介紹繼承的幾種方式。

原型鏈繼承

既然可以訪問原型鏈的所有屬性,那麼就可以用原型鏈的原理實現繼承。原型鏈繼承用new的方式(實現A繼承B):
A.prototype=new B();關於new可以看下我另一篇文章this那些事

程式碼:

function B(){
    this.nameB='B';
}
B.prototype.nameProto="PROTO";
function A(){
    this.nameA="A";
}
A.prototype=new B(); //原型鏈繼承:A繼承B
var a=new A();
console.log(a);
複製程式碼

列印結果:

小議JS原型鏈、繼承
上段程式碼A繼承B,通過A建構函式new的示例a不僅可以繼承B(可訪問nameB),而且可以繼承B原型上的屬性(nameProto),且是所有例項共享的。

好處:可以繼承原型鏈的屬性
缺點:無法實現多繼承,A繼承了B,就無法再繼承C

構造繼承

構造繼承就是利用建構函式繼承,即改變this指向的方式(call/apply/bind)執行一次建構函式B,具體可我另一篇文章this那些事。廢話不多說,上例子:

function B(){
    this.nameB='B';
}
B.prototype.nameProto="PROTO";
function A(){
    B.call(this); //A繼承B,只舉了call的例子,apply、bind類似
    this.nameA="A";
}
var a=new A();
console.log(a);
複製程式碼

列印結果:

小議JS原型鏈、繼承
根據a的列印結果,我們看到nameB和nameA是同一層級,雖然實現了A繼承B,但是通過a的結構看不出來,而且無法繼承B原型鏈上的屬性nameProto,不過它的好處是可以多繼承,可以通過C.call(this)繼承C。

好處:可以多繼承
缺點:無法繼承原型鏈上的屬性

組合繼承

組合繼承就是為了解決原型鏈繼承無法多繼承、構造繼承無法繼承原型鏈上的屬性的問題而誕生的,將兩種方式結合。

function B(){
    this.nameB='B';
}
B.prototype.nameProto="PROTO";
function A(){
    B.call(this); //構造繼承
    this.nameA="A";
}
A.prototype=new B(); //原型鏈繼承:A繼承B
var a=new A();
console.log(a);
複製程式碼

列印結果:

小議JS原型鏈、繼承
觀察a的列印結果,似乎真的解決了上述兩個問題,它也引入了一個新的問題:nameB屬性有兩個,這樣造成了資源浪費(儲存佔用記憶體)。

原型式繼承

先看下面的示例:

function objectCreate(obj){
  function F(){};
  F.prototype = obj;
  return new F();
}
var a=objectCreate(A.prototype);
複製程式碼

上個例子中,

  1. 使用__proto__A.prototype.__proto__=B.prototype
  2. 使用Object.createA.prototype=Object.create(B.prototype)

相關文章