再說說__proto__和prototype以及js的繼承

1.__proto__和prototype

JS中的原型鏈已是一個老生常談的問題,畢竟也是JS 這門語言的特點之一了。app

這裏寫圖片描述
首先「萬物皆對象「,雖然這句話一直有爭議,可是有它的道理的,null類型這些的爭論這裏就不說了。
對象中有個屬性__proto__,被稱爲隱式原型,這個隱式原型指向構造改對象的構造函數的原型,這也保證了實例可以訪問在構造函數原型中定義的屬性和方法。這個實例多是如圖中的new Foo()出來的實例。函數

構造該對象的f1,f2構造函數是fuction Foo(),它的原型是Foo.prototype,那麼f1,f2就指向了構造該對象的構造函數的原型,也就是Foo.prototype,那麼構造函數Foo()它的proto指向哪裏了,仍是找它的構造函數,它的構造函數是Function(),那麼它的proto就指向了Fuction.prototype,沿着proto這條路最上就是Object.prototype,Object的proto就是null了。性能

剛剛說的對象有個proto屬性,方法也是對象,方法中除了有proto以外(這個proto指向構造該函數/對象的構造原型,也就是上一層了),還有prototype,這個屬性就是原型屬性,他是一個指針,指向一個對象,這個對象就叫原型對象,這裏放着包含全部實例共享的屬性和方法,這個原型對象裏面有一個屬性constructor,這個屬性也包含一個指針,指回了原構造函數。this

1.構造函數Foo()構造函數的原型屬性Foo.prototype指向了原型對象,在原型對象裏有共有的方法,全部構造函數聲明的實例(這裏是f1,f2)均可以共享這個方法。prototype

2.原型對象Foo.prototypeFoo.prototype保存着實例共享的方法,有一個指針constructor指回構造函數。3d

3.實例f1和f2是Foo這個對象的兩個實例,這兩個對象也有屬性__proto__,指向構造函數的原型對象,這樣子就能夠像上面1所說的訪問原型對象的全部方法。指針

4.構造函數Foo()除了是方法,也是對象,它也有__proto__屬性,指向誰呢?指向它的構造函數的原型對象。函數的構造函數不就是Function嘛,所以這裏的__proto__指向了Function.prototype。其實除了Foo(),Function(), Object()也是同樣的道理。原型對象也是對象,它的__proto__屬性,又指向誰呢?同理,指向它的構造函數的原型對象。這裏是Object.prototype.最後,Object.prototype的__proto__屬性指向null。code

5.對象有屬性__proto__,指向該對象的構造函數的原型對象。
方法除了有屬性__proto__,還有屬性prototype,prototype指向該方法的原型對象。對象

6.再看圖。blog

2.繼承

1.父類的實例做爲子類的原型

function Animal(name) {
  // 屬性
  this.name =  name || "Animal";
  // 實例方法
  this.sleep = function() {
    console.log(this.name + '正在睡覺!');
  }
}

// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃' + food);
};


function Cat() { 
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';

var cat = new Cat();
console.log(cat.name);
console.log(cat.eat('fish'));
console.log(cat.sleep());
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true

**cat.__proto__ === Cat.prototype //true**

構造cat對象的構造函數是Cat(),它的原型是Cat.prototype,那麼隱式原型__proto__就指向構造該對象的構造函數的原型對象,那麼cat的__proto__就指向的是Cat.prototype。

**Cat.prototype.__proto__ === Animal.prototype //true**
原型對象也是對象,是對象就有__proto__,Animal的實例返回給了這個原型對象,那麼這個原型對象的隱式原型__proto__就指向的是構造該對象的構造函數的原型,咱們看看這個原型對象的構造函數是誰
這裏寫圖片描述

那麼Animal()構造函數的原型對象就是Animal.prototype了。天然就有上面true的結果了。

這種方法的繼承的缺點:

  1. 父類的引用屬性和原型對象的引用屬性是全部實例共享的
  2. 建立子類實例時,沒法向父類構造函數傳參
  3. 不能多繼承
    第一個致命缺點,由於咱們每一個實例各自的屬性互不干擾纔對:
    這裏寫圖片描述

注意原型上的方法/屬性是共享的
這裏寫圖片描述

2.構造繼承

沒有用到原型,使用父類的構造函數來加強子類實例,等於直接是複製父類的實例屬性給子類。

經典繼承也叫作 「借用構造函數」 或 「僞造對象」 。其基本思想是:在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象,所以能夠經過使用apply() 和call() 方法也能夠在新建立的對象上執行構造函數。(JS高程)

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

特色:

解決了1中,子類實例共享父類引用屬性的問題
建立子類實例時,能夠向父類傳遞參數(經過call的後面參數)
能夠實現多繼承(call多個父類對象)

缺點:

實例並非父類的實例,只是子類的實例
只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
沒法實現函數複用,每一個子類都有父類實例函數的副本,影響性能

3.組合繼承

經過調用父類構造,繼承父類的屬性並保留傳參的優勢,而後經過將父類實例做爲子類原型,實現函數複用

function Cat(name){
  Animal.call(this);
  this.name = name || 'Tom';
}
Cat.prototype = new Animal();

//組合繼承也是須要修復構造函數指向的。

Cat.prototype.constructor = Cat;

// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

這種方式看似是原型繼承和構造繼承的組合,彌補了構造繼承只能繼承實例屬性/方法,不能繼承原型屬性/方法的缺點,也彌補了原型繼承引用屬性共享的問題,可向父類傳參,函數可複用,便是子類的實例,也是父類的實例。

缺點就是調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)
這裏寫圖片描述

相關文章
相關標籤/搜索