JavaScript中的原型與原型鏈

前言

做爲前端高頻面試題之一,相信不少小夥伴都有遇到過這個問題。那麼你是否清楚完整的瞭解它呢?前端

國際慣例,讓咱們先拋出問題:git

  • 什麼是原型、原型鏈
  • 它們有什麼特色
  • 它們能作什麼
  • 怎麼肯定它們的關係

或許你已經有答案,或許你開始有點疑惑,不管是 get 新技能或是簡單的溫習一次,讓咱們一塊兒去探究一番吧es6

若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過github

如下↓web

原型

JavaScript 是基於原型的面試

咱們建立的每一個函數都有一個 prototype(原型) 屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。編程

簡單來講,就是當咱們建立一個函數的時候,系統就會自動分配一個 prototype屬性,能夠用來存儲可讓全部實例共享的屬性和方法函數

用一張圖來表示就更加清晰了: 學習

原型

圖解:ui

  • 每個構造函數都擁有一個 prototype 屬性,這個屬性指向一個對象,也就是原型對象
  • 原型對象默認擁有一個 constructor 屬性,指向指向它的那個構造函數
  • 每一個對象都擁有一個隱藏的屬性 __proto__,指向它的原型對象
function Person(){}

var p = new Person();

p.__proto__ === Person.prototype // true

Person.prototype.constructor === Person // true
複製代碼

那麼,原型對象都有哪些特色呢

原型特色

function Person(){}
Person.prototype.name = 'tt';
Person.prototype.age = 18;
Person.prototype.sayHi = function() {
    alert('Hi');
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'oo';
person1.name // oo
person1.age // 18
perosn1.sayHi() // Hi
person2.age // 18
person2.sayHi() // Hi
複製代碼

從這段代碼咱們不難看出:

  • 實例能夠共享原型上面的屬性和方法
  • 實例自身的屬性會屏蔽原型上面的同名屬性,實例上面沒有的屬性會去原型上面找

既然原型也是對象,那咱們可不能夠重寫這個對象呢?答案是確定的

function Person() {}
Person.prototype = {
    name: 'tt',
    age: 18,
    sayHi() {
        console.log('Hi');
    }
}

var p = new Person()
複製代碼

只是當咱們在重寫原型鏈的時候須要注意如下的問題:

function Person(){}
var p = new Person();
Person.prototype = {
    name: 'tt',
    age: 18
}

Person.prototype.constructor === Person // false

p.name // undefined
複製代碼

一圖賽過千言萬語

重寫原型鏈

  • 在已經建立了實例的狀況下重寫原型,會切斷現有實例與新原型之間的聯繫
  • 重寫原型對象,會致使原型對象的 constructor 屬性指向 Object ,致使原型鏈關係混亂,因此咱們應該在重寫原型對象的時候指定 constructor( instanceof 仍然會返回正確的值)
Person.prototype = {
    constructor: Person
}
複製代碼

注意:以這種方式重設 constructor 屬性會致使它的 Enumerable 特性被設置成 true(默認爲false)

既然如今咱們知道了什麼是 prototype(原型)以及它的特色,那麼原型鏈又是什麼呢?

原型鏈

JavaScript 中全部的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有本身的原型對象,這樣層層上溯,就造成了一個相似鏈表的結構,這就是原型鏈

一樣的,咱們使用一張圖來描述

原型鏈

  • 全部原型鏈的終點都是 Object 函數的 prototype 屬性
  • Objec.prototype 指向的原型對象一樣擁有原型,不過它的原型是 null ,而 null 則沒有原型

清楚了原型鏈的概念,咱們就能更清楚地知道屬性的查找規則,好比前面的 p 實例屬性.若是自身和原型鏈上都不存在這個屬性,那麼屬性最終的值就是 undefined ,若是是方法就會拋出錯誤

class類

ES6 提供了 Class(類) 這個概念,做爲對象的模板,經過 class 關鍵字,能夠定義類

爲何會提到 class

ES6class 能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的 class 寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// 能夠這麼改寫
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};
複製代碼

class 裏面定義的方法,其實都是定義在構造函數的原型上面實現實例共享,屬性定義在構造函數中,因此 ES6 中的類徹底能夠看做構造函數的另外一種寫法

除去 class 類中的一些行爲可能與 ES5 存在一些不一樣,本質上都是經過原型、原型鏈去定義方法、實現共享。因此,仍是文章開始那句話 JavaScript是基於原型的

更多 class 問題,參考這裏

關係判斷

instanceof

最經常使用的肯定原型指向關係的關鍵字,檢測的是原型,可是隻能用來判斷兩個對象是否屬於實例關係, 而不能判斷一個對象實例具體屬於哪一種類型

function Person(){}
var p = new Person();

p instanceof Person // true
p instanceof Object // true
複製代碼

hasOwnProperty

經過使用 hasOwnProperty 能夠肯定訪問的屬性是來自於實例仍是原型對象

function Person() {}
Person.prototype = {
    name: 'tt'
}
var p = new Person();
p.age = 15;

p.hasOwnProperty('age') // true
p.hasOwnProperty('name') // false
複製代碼

原型鏈的問題

因爲原型鏈的存在,咱們可讓不少實例去共享原型上面的方法和屬性,方便了咱們的不少操做。可是原型鏈並不是是十分完美的

function Person(){}
Person.prototype.arr = [1, 2, 3, 4];

var person1 = new Person();
var person2 = new Person();

person1.arr.push(5) 
person2.arr // [1, 2, 3, 4, 5]
複製代碼

引用類型,變量保存的就是一個內存中的一個指針。因此,當原型上面的屬性是一個引用類型的值時,咱們經過其中某一個實例對原型屬性的更改,結果會反映在全部實例上面,這也是原型 共享 屬性形成的最大問題

另外一個問題就是咱們在建立子類型(好比上面的 p)時,沒有辦法向超類型( Person )的構造函數中傳遞參數

後記

鑑於原型的特色和存在的問題,因此咱們在實際開發中通常不會單獨使用原型鏈。通常會使用構造函數和原型相配合的模式,固然這也就牽扯出了 JavaScript 中另外一個有意思的領域:繼承

那麼,什麼又是繼承呢

且聽下回分解

最後,推薦一波前端學習歷程,不按期分享一些前端問題和有意思的東西歡迎 star 關注 傳送門

參考文檔

JavaScript高級程序設計

ECMAScript6入門

相關文章
相關標籤/搜索