深刻理解 js 之繼承與原型鏈

原型鏈與繼承

當談到繼承時,JavaScript 只有一種結構:對象。每一個實例對象(object )都有一個私有屬性(稱之爲proto)指向它的原型對象(prototype)。該原型對象也有一個本身的原型對象(proto) ,層層向上直到一個對象的原型對象爲 null。根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節。數組

新建函數,並建立對象瀏覽器

function Car() {
    this.name = 'BMW'
    this.price = 95800
}
let carBMW = new Car()

這時咱們的腦海裏應該有這樣一張圖:函數

clipboard.png

或許你跟我初次接觸同樣。若是對該圖不怎麼理解,不要着急,繼續往下看!!!this

基於原型鏈的繼承

JavaScript 對象是動態的屬性「包」(指其本身的屬性)。JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不單單在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。spa

從 ECMAScript 6 開始,[[Prototype]] 能夠經過Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 __proto__。

接着上述代碼prototype

console.log(carBMW)  // *Car {name: "BMW", price: 95800}*

在 Car() 函數的原型上定義屬性code

Car.prototype.price = 200000
Car.prototype.speed = 300

console.log(carBMW.__proto__)  // {price: 200000, speed: 300, constructor: ƒ}
console.log(carBMW.__proto__.__proto__ == Object.prototype)  // true
console.log(carBMW.__proto__.__proto__.__proto__) // null

綜合上述代碼,能夠給出以下原型鏈對象

{name: "BMW", price: 95800} ---> {price: 200000, speed: 300, constructor: ƒ} ---> Object.prototype ---> null

繼續寫代碼blog

console.log(carBMW.name)   // BMW
// name 爲 carBMW 自身的屬性

console.log(carBMW.price)  // 95800
// price 爲 carBMW 自身的屬性,原型上也有一個'price'屬性,可是不會被訪問到,這種狀況稱爲"屬性遮蔽 (property shadowing)"

console.log(carBMW.speed)  // 300
// speed 不是 carBMW 自身的屬性,可是 speed 位於該原型鏈上,所以咱們依然能夠取到該值

// 固然若是你試着訪問一個不存在原型鏈上的屬性時,這時候會給你返回一個undefined

當咱們給對象設置一個屬性時,建立的屬性稱爲對象的自有屬性。繼承

函數的繼承與其餘的屬性繼承沒有差異,包括上面的「屬性遮蔽」(這種狀況至關於其餘語言的方法重寫)。

當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
let car = {
  price: 95800,
  getPrice: function(){
    return this.a
  }
}

console.log(car.getPrice()); // 95800
// 當調用 car.getPrice() 時,'this'指向了car.

let bmw = Object.create(car);
// bmw 是一個繼承自 car 的對象

bmw.price = 400000; // 建立 bmw 的自身屬性 price
console.log(bmw.getPrice()); // 400000
// 調用 bmw.getPrice() 時, 'this'指向 bmw. 
// 又由於 bmw 繼承 car 的 getPrice 函數
// 此時的'this.price' 即 bmw.a,即 price 的自身屬性 'price'

雖然有點繞,細讀以後邏輯並非很複雜

多種方法建立對象

基於字面量建立對象

也就是根據相應的語法結構直接進行建立
let car = {
  price: 95800,
  getPrice: function(){
    return this.a
  }
}
// car 爲一個對象,所以相應的原型鏈應該以下
// car ---> Object.prototype ---> null

let cars = ['BMW','Audi','WulingHongguang']
// cars 爲一個數組對象,相應的原型鏈應該以下
// cars ---> Array.prototype ---> Object.prototype ---> null

基於構造函數建立對象

在 JavaScript 中,構造器其實就是一個普通的函數。當使用 new 操做符 來做用這個函數時,它就能夠被稱爲構造方法(構造函數)。
function Car() {
    this.name = 'BMW'
    this.price = 95800
}
Car.prototype.speed = 300
let car = new Car()
// 能夠知道,car 的自身屬性 {name: "BMW", price: 95800}, 位於原型鏈上的屬性有 speed .

基於Object.create()建立對象

ECMAScript 5 中引入了一個新方法:Object.create()。能夠調用這個方法來建立一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數
var car = {price: 10000}; 
// car ---> Object.prototype ---> null

var carBMW = Object.create(car);
// carBMW ---> car ---> Object.prototype ---> null
console.log(carBMW.price); // 10000 (繼承而來)

基於 Class 關鍵字建立對象

ECMAScript6 引入了一套新的關鍵字用來實現 class。實質上仍是語法糖,底層原理依舊不變
class Point {
     constructor(x, y) {
        this.x = x;
        this.y = y;
     }
     toString() {
       return '(' + this.x + ', ' + this.y + ')';
     }
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true

以上代碼中,x和y都是實例對象point自身的屬性(由於定義在this變量上),因此hasOwnProperty方法返回true,而toString是原型對象的屬性(由於定義在Point類上),因此hasOwnProperty方法返回false。這些都與ES5的行爲保持一致。

相關文章
相關標籤/搜索