完全理解Javascript中的原型鏈與繼承

JavaScript語言不像面向對象的編程語言中有類的概念,因此也就沒有類之間直接的繼承,JavaScript中只有對象,使用函數模擬類,基於對象之間的原型鏈來實現繼承關係,
ES6的語法中新增了class關鍵字,但也只是語法糖,內部仍是經過函數和原型鏈來對類和繼承進行實現。編程

1 原型鏈

1.1 原型鏈定義

JavaScript對象上都有一個內部指針[[Prototype]],指向它的原型對象,而原型對象的內部指針[[Prototype]]也指向它的原型對象,直到原型對象爲null,這樣造成的鏈條就稱爲原型鏈。數組

這樣在訪問對象的屬性時,會如今本身的屬性中查找,若是不存在則會到上一層原型對象中查找。編程語言

注意:
根據 ECMAScript 標準,someObject.[[Prototype]] 符號是用於指派 someObject 的原型。這個等同於 JavaScript 的 proto 屬性(現已棄用)。從 ECMAScript 6 開始, [[Prototype]] 能夠用Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。函數

例如:學習

var obj2 = {
    height: 170
}
var obj3 = {
    name: 'obj3'
}
Object.setPrototypeOf(obj3, obj2);
console.log(obj3.height); // 170
var isproto = Object.getPrototypeOf(obj3) === obj2;
console.log(isproto); // true

1.2 不一樣方法建立對象與生成原型鏈

1.2.1 使用 Object.create 建立對象

ECMAScript 5 中引入了一個新方法:Object.create()。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數。this

例如:spa

var a = {a: 1};
// a ---> Object.prototype ---> null
 
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)
 
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
 
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 由於d沒有繼承Object.prototype

1.2.2 使用構造函數建立對象

在 JavaScript 中,構造函數其實就是一個普通的函數,通常函數名首字母大寫。當使用 new 操做符 來做用這個函數時,它就能夠被稱爲構造方法(構造函數)。
例如:prototype

function Person (name, age) {
    this.name = name;
    this.age = age;
}
 
Person.prototype = {
    sayName: function () {
        console.log(this.name);
    }
}
var person1 = new Person('yangyiliang', 23);
person1.sayName(); // yangyiliang

使用構造函數建立對象,經歷了以下三個關鍵步驟:設計

var temp = {}; //1  建立空對象
Person.call(temp, 'yangyiliang', 23); //2  以空對象爲this執行構造函數
Object.setPrototypeOf(temp, Person.prototype); //3  將構造函數的prototype 設置爲空對象的原型
return temp;

1.2.3 使用字面量方法建立對象

使用字面量方法建立的對象,根據對象的類型,他們的原型都會指向相應JavaScript內置構造函數的prototype,和直接使用內置構造函數建立對象生成的原型鏈相同,例如:指針

var o = {a: 1};
 
// o這個對象繼承了Object.prototype上面的全部屬性
// 因此能夠這樣使用 o.hasOwnProperty('a').
// hasOwnProperty 是Object.prototype的自身屬性。
// Object.prototype的原型爲null。
// 原型鏈以下:
// o ---> Object.prototype ---> null
 
var a = ["yo", "whadup", "?"];
 
// 數組都繼承於Array.prototype
// (indexOf, forEach等方法都是從它繼承而來).
// 原型鏈以下:
// a ---> Array.prototype ---> Object.prototype ---> null
 
function f(){
  return 2;
}
 
// 函數都繼承於Function.prototype
// (call, bind等方法都是從它繼承而來):
// f ---> Function.prototype ---> Object.prototype ---> null

2 繼承

在面向對象的語言當中,繼承關係應該指的是父類和子類之間的關係,子類繼承父類的屬性和方法,在JavaScript當中是父構造函數和子構造函數之間的關係。

類自己是對象的抽象形式,類的使用價值最後也是在於經過它可以建立對象,
因此子類可以繼承父類的屬性和方法的意義,就是經過子類建立出來的對象可以繼承經過父類建立出來的對象的屬性和方法。

clipboard.png

而這種對象之間的繼承關係,就是經過原型鏈實現。

在1.2.2節中,咱們學習到了經過構造函數建立對象的三個重要步驟,其中的一步是把構造函數的prototype對象設置爲建立對象的原型。

所以咱們將父類的實例對象做爲子類的prototype即可以達到繼承的目的,以下圖所示:

繼承的實現

function Person (name, age) {
    this.name = name;
    this.age = age
}
 
Person.prototype.sayName = function () {
    console.log('my name is ' + this.name);
}
 
function Student (name, age, school) {
    Person.call(this, name, age);
    this.school = school;
}
 
Student.prototype = Object.create(Person.prototype);
 
Student.prototype.saySchool = function () {
    console.log('my school is ' + this.school);
}

上面代碼實現的繼承,遵循了幾個原則:

  • 一、由於構造函數建立的對象將公用同一個原型,因此將每一個對象獨有的屬性寫在構造函數中,將對象之間能夠公用的方法寫在構造函數的prototype中,也就是對象的原型中
  • 二、子構造函數繼承父構造函數作了兩個地方的工做,一是在子構造函數中利用call,調用父構造函數的方法,二是利用Object.create方法建立一個以父構造函數的prototype爲原型的對象。

利用Object.create而不是直接用new 建立一個實例對象的目的是,減小一次調用父構造函數的執行。

  • 三、先經過prototype屬性指向父構造函數的實例,而後再向prototype添加想要放在原型上的方法。

最後上一張js高級程序設計第三版中的一張源於原型鏈繼承的圖

clipboard.png

利用class實現繼承

下面利用ES6引入的新語法糖,class、extends關鍵字對上述實現繼承的代碼進行改寫:

class Person {
    constructor (name, age) {
        this.name = name;
        this.age = age;
    }
 
    sayName () {
        console.log('my name is ' + this.name);
    }
}
 
class Student extends Person {
    constructor (name, age, school) {
        super(name, age);
        this.school = school;
    }
 
    saySchool () {
        console.log('my school is ' + this.school);
    }
}
  • class裏的constructor 對應原來的構造函數
  • class裏面的其餘方法都是寫在原來構造函數的prototype中的
  • 子類直接經過extends 關鍵字進行繼承
  • 子類中能夠經過super來調用父類中的方法

本文部份內容來自 https://developer.mozilla.org...

後續

function A() {
}
//函數默認會有一個prototype對象而且具備constructor屬性指向他自己

var a = new A()

a instanceof A
function A() {
}

function B() {
}

var proto = {}

B.prototype = proto

A.prototype = proto
var a = new A()
a instanceof B //true
a instanceof Object //true

instanceof  是遍歷a 的原型鏈 尋找是否有和 B.prototype  是同一個對象的__proto__  若是找到就爲true
相關文章
相關標籤/搜索