簡單地理解,繼承就是一個對象能夠訪問另一個對象中的屬性和方法。在JavaScript 中,咱們經過原型和原型鏈的方式來實現了繼承特性。javascript
觀察上圖,由於 B 繼承了 A,那麼 B 能夠直接使用 A 中的 color 屬性,就像這個屬性是 B 自帶的同樣。java
JavaScript中任意對象都有一個內置屬性 [[Prototype]] ,可是ES5以前沒有訪問這個 [[Prototype]] 屬性的標準方式,因此大多數瀏覽器會在每一個對象上暴露__proto__
屬性,經過這個屬性能夠訪問對象的原型。瀏覽器
看下圖:函數
C.__proto__ = B
咱們把 __proto__
屬性稱之爲 C 對象的原型 (prototype),__proto__
指向了內存中的 B 對象,咱們就把 __proto__
指向的 B 對象稱爲 C 對象的原型對象,那麼 C 對象就能夠直接訪問其原型對象 B 的方法或者屬性。性能
構成原型鏈,用於實現基於原型的繼承。舉個例子,當咱們訪問 C 這個對象中的 color 屬性時,若是在 C 中找不到,那麼就會沿着__proto__
依次查找。this
let A = { color: 'orange' } let B = { name: 'pipi' } let C = { type: 'dog' } C.__proto__ = B B.__proto__ = A console.log(C.color) // orange
__proto__
屬性在ES6
時才被標準化,以確保 Web 瀏覽器的兼容性,可是不推薦使用。spa
- 這是隱藏屬性,並非標準定義的;
- 使用該屬性會形成嚴重的性能問題。
爲了更好的支持,推薦使用
Object.getPrototypeOf()
來獲取 [[Prototype]] 。prototype
那應該怎麼去正確地設置對象的原型對象呢?設計
答案是使用構造函數來建立對象。指針
看下面這段代碼:
function DogFactory(type,color){ this.type = type this.color = color } let dog = new DogFactory('Dog','Black')
要建立 DogFactory
的實例,應使用 new
操做符。以這種方式調用構造函數會執行以下操做。
咱們主要看第2步,這裏 dog 對象內部的 [[Prototype]] 被賦值爲了DogFactory.prototype
。
dog.__proto__ = DogFactory.prototype
那麼 prototype
是什麼呢?
每一個函數對象中都有一個公開的 prototype 屬性,當你將這個函數做爲構造函數來建立一個新的對象(實例)時,新建立對象(實例)的原型對象就指向了該函數的 prototype 屬性。
固然了,若是你只是正常調用該函數,那麼 prototype 屬性將不起做用。
看下面這段代碼:
function DogFactory(type,color){ this.type = type this.color = color //Mammalia } DogFactory.prototype.constant_temperature = 1 let dog1 = new DogFactory('Dog','Black') let dog2 = new DogFactory('Dog','Black') let dog3 = new DogFactory('Dog','Black') //實例經過__proto__連接到原型對象,它實際上指向隱藏特性[[Prototype]],構造函數經過 prototype 屬性連接到原型對象。 //也就是說實例與構造函數沒有直接聯繫,與原型對象有直接聯繫。 console.log(dog1.__proto__ === DogFactory.prototype) // true
因此此時:
dog1.__proto__ = DogFactory. prototype dog2.__proto__ = DogFactory. prototype dog3.__proto__ = DogFactory. prototype
這樣咱們三個 dog 對象的原型對象(DogFactory)都指向了 prototype,而 prototype 又包含了constant_temperature
屬性,這就是咱們實現繼承的正確方式。
默認狀況下,全部原型對象自動得到一個名爲 constructor 的屬性,指回與之關聯的構造函數。
仍是看上面的例子:
function DogFactory(type,color){ this.type = type this.color = color //Mammalia } DogFactory. prototype.constant_temperature = 1 let dog1 = new DogFactory('Dog','Black') console.log(DogFactory.prototype.constructor === DogFactory) // true
__proto__
) 屬性引用其原型對象;ECMA-262 把原型鏈定義爲 ECMAScript 的主要繼承方式。其基本思想就是經過原型繼承多個引用類型的屬性和方法。
上面咱們知道了原型鏈的實現主要是靠__proto__
,固然,實際使用中仍是要用 prototype 。
function DogFactory() { this.type = 'toypoodle' this.color = 'black' } DogFactory.prototype.getInfo = function () { return `Type is: ${this.type},color is ${this.color}.` } function Dog() { this.name = 'doudou' } // 繼承 DogFactory Dog.prototype = new DogFactory(); Dog.prototype.getDogInfo = function () { return this.name } let dog1 = new Dog() console.log(dog1.getInfo()) // Type is: toypoodle,color is black.
下面補充幾個知識點。
默認狀況下,全部引用類型都繼承自 Object,這也是經過原型鏈實現的。任何函數的默認原型都是一個 Object 的實例,這意味着這個實例有一個內部指針指向
Object.prototype。這也是爲何自定義類型可以繼承包括 toString()、valueOf()在內的全部默認方法的緣由。
仍是用上面的代碼舉例:
function DogFactory(type, color) { this.type = type this.color = color //Mammalia } console.log(DogFactory.prototype.__proto__ === Object.prototype); // true console.log(DogFactory.prototype.__proto__.constructor === Object); // true console.log(DogFactory.prototype.__proto__.__proto__ === null); // true
也就是說,正常的原型鏈都會終止於 Object 的原型對象,Object 原型的原型是 null 。
若是一個實例的原型鏈中出現過相應的構造函數,則 instanceof 返回 true。
console.log(dog1 instanceof Object); // true console.log(dog1 instanceof DogFactory); // true console.log(dog1 instanceof Dog); // true
原型鏈中的每一個原型均可以調用這個方法,只要原型鏈中包含這個原型,這個方法就返回 true。
console.log(Object.prototype.isPrototypeOf(dog1)); // true console.log(DogFactory.prototype.isPrototypeOf(dog1)); // true console.log(Dog.prototype.isPrototypeOf(dog1)); // true
function DogFactory() { this.type = 'toypoodle' this.color = 'black' } DogFactory.prototype.getInfo = function () { return `Type is: ${this.type},color is ${this.color}.` } function Dog() { this.name = 'doudou' } // 繼承 DogFactory Dog.prototype = new DogFactory(); Dog.prototype.getDogInfo = function () { return this.name } //子類重寫新方法 Dog.prototype.getInfo = function(){ return `Color is ${this.color}.` } let dog1 = new Dog() console.log(dog1.getInfo()) // Color is black.
Dog 實例上調用 getInfo() 時調用的是這個方法。而 DogFactory 的實例仍然會調用最初的方法。
重點在於上述兩個方法都是在把原型賦值爲 DogFactory 的實例以後定義的。
以對象字面量方式建立原型方法會破壞以前的原型鏈,由於這至關於重寫了原型鏈。
function DogFactory() { this.type = 'toypoodle' this.color = 'black' } DogFactory.prototype.getInfo = function () { return `Type is: ${this.type},color is ${this.color}.` } function Dog() { this.name = 'doudou' } // 繼承 DogFactory Dog.prototype = new DogFactory(); Dog.prototype.getDogInfo = function () { return this.name } // 經過對象字面量添加新方法,這會致使上一行無效 Dog.prototype = { getDoggInfo() { return this.color } } let dog1 = new Dog() console.log(dog1.getInfo()) // Uncaught TypeError: dog1.getInfo is not a function
原型鏈的第一個問題是,原型中包含的引用值會在全部實例間共享。
看下面的代碼:
function DogFactory() { this.colors = ["red", "blue", "green"] } function Dog() {} // 繼承 DogFactory Dog.prototype = new DogFactory(); let dog1 = new Dog() dog1.colors.push('black') console.log(dog1.colors) // ["red", "blue", "green", "black"] let dog2 = new Dog() console.log(dog2.colors) // ["red", "blue", "green", "black"]
經過 dog1 改動 colors 屬性也會反映到 dog2 上,而這每每不是咱們想要的。這也是爲何屬性一般會在構造函數中定義而不會定義在原型上的緣由。
原型鏈的第二個問題是,子類型在實例化時不能給父類型的構造函數傳參。
[javascript高級程序設計第四版]