[javascript總結]原型和原型鏈

繼承

簡單地理解,繼承就是一個對象能夠訪問另一個對象中的屬性和方法。在JavaScript 中,咱們經過原型和原型鏈的方式來實現了繼承特性。javascript

image-20210518172746716

觀察上圖,由於 B 繼承了 A,那麼 B 能夠直接使用 A 中的 color 屬性,就像這個屬性是 B 自帶的同樣。java

\_proto\_

JavaScript中任意對象都有一個內置屬性 [[Prototype]] ,可是ES5以前沒有訪問這個 [[Prototype]] 屬性的標準方式,因此大多數瀏覽器會在每一個對象上暴露__proto__屬性,經過這個屬性能夠訪問對象的原型瀏覽器

看下圖:函數

image-20210519090101754

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

image-20210519101816391

__proto__ 屬性在 ES6 時才被標準化,以確保 Web 瀏覽器的兼容性,可是不推薦使用spa

  1. 這是隱藏屬性,並非標準定義的;
  2. 使用該屬性會形成嚴重的性能問題。

爲了更好的支持,推薦使用 Object.getPrototypeOf()來獲取 [[Prototype]] 。prototype

那應該怎麼去正確地設置對象的原型對象呢?設計

答案是使用構造函數來建立對象。指針

構造函數建立對象

看下面這段代碼:

function DogFactory(type,color){
this.type = type
this.color = color
}
let dog = new DogFactory('Dog','Black')

要建立 DogFactory 的實例,應使用 new 操做符。以這種方式調用構造函數會執行以下操做。

  1. 在內存中建立一個新對象。
  2. 這個新對象內部的 [[Prototype]] 特性被賦值爲構造函數的 prototype 屬性。
  3. 構造函數內部的 this 被賦值爲這個新對象(即 this 指向新對象)。
  4. 執行構造函數內部的代碼(給新對象添加屬性)。
  5. 若是構造函數返回非空對象,則返回該對象;不然,返回剛建立的新對象。

咱們主要看第2步,這裏 dog 對象內部的 [[Prototype]] 被賦值爲了DogFactory.prototype

dog.__proto__ = DogFactory.prototype

那麼 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 屬性,這就是咱們實現繼承的正確方式。

image-20210519111757179

constructor

默認狀況下,全部原型對象自動得到一個名爲 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

image-20210519140643914

  1. 構造函數 DogFactory 有一個 prototype(__proto__) 屬性引用其原型對象;
  2. 這個原型對象也有一個 constructor 屬性,引用這個構造函數 DogFactory ;
  3. 構造函數 DogFactory 有一個 prototype 屬性引用其原型對象;
  4. ......
  5. 換句話說,二者循環引用。

原型鏈

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

若是一個實例的原型鏈中出現過相應的構造函數,則 instanceof 返回 true。

console.log(dog1 instanceof Object); // true
console.log(dog1 instanceof DogFactory); // true
console.log(dog1 instanceof Dog); // true

isPrototypeOf()

原型鏈中的每一個原型均可以調用這個方法,只要原型鏈中包含這個原型,這個方法就返回 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高級程序設計第四版]

圖解 Google V8

js中__proto__和prototype的區別和關係?

相關文章
相關標籤/搜索