前端知識總結系列筆記三:原型與原型鏈

前言

原型與原型鏈是面試的常考點之一,所以頗有必要理解並掌握,本文嘗試去弄清原型與原型鏈的關係,並經過圖解的方式去幫助自身創建起原型與原型鏈的知識體系,使本身能在面試中能與面試官侃侃而談,嘻嘻~面試

關於原型

JavaScript常被描述爲一種基於原型的語言(prototype-based language)---每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。所以若是往原型對象上添加屬性和方法,那麼全部由該對象實例化的實例對象均可以共享該原型上的屬性和方法。bash

// 代碼塊1
function SuperType() {}; // 構造函數
SuperType.prototype; // 原型對象

SuperType.prototype.sayHello = function() {
    console.log('Hello World!');
}

const s1 = new SuperType(); // 實例對象1
const s2 = new SuperType(); // 實例對象2

// 實例s1和實例s2均可以共享該原型上的方法sayHello()
s1.sayHello();
s2.sayHello();
複製代碼

每一個構造函數都有一個指向原型對象的指針,經過.prototype屬性訪問,而原型對象都包含一個指向構造函數的指針,經過.constructor訪問,以下圖所示,其實就是一個循環引用。函數

// 接上述代碼塊1
SuperType.prototype.constructor === SuperType; // true
s1.__proto__ === SuperType.prototype; // true
複製代碼

// 接上述代碼塊1
s1.constructor === SuperType; // true
複製代碼

從上述代碼咱們能夠猜測:經過構造函數new出來的實例對象,是否也有一個指向實例化自身的構造函數的指針,可經過.constructor訪問呢? 咱們嘗試把實例對象s1打印出來看看!this

從打印結果咱們能夠看出,實例對象s1自己並無constructor屬性,而是經過原型向上查找__proto__,共享原型上的constructor屬性,該屬性最終指向SuperType。spa

所以構造函數、實例對象、原型對象三者的關係以下:prototype

關於原型鏈

咱們來回顧一下構造函數、原型和實例的關係:指針

每一個構造函數都有一個原型對象,而原型對象都包含一個指向構造函數的指針(constructor),而實例都包含一個指向原型對象的內部指針(proto)。code

而什麼是原型鏈呢? 咱們知道,每一個對象都擁有一個原型對象,經過__proto__指針指向上一個原型,並從中繼承方法和屬性,同時原型對象也有可能擁有原型,這樣一層一層,最終指向null。這就是原型鏈(prototype chain),經過原型鏈一個對象會擁有定義在其餘對象中的屬性和方法。cdn

接下來簡單展現下原型鏈的運做機制:對象

function Person(name) {
    this.name = name;
}
const p = new Person('jiaxin');

p; // Person {name: 'jiaxin'}
p.__proto__ === Person.prototype; // true
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
複製代碼

圖解原型鏈

上面只是簡單地介紹了原型鏈的概念以及運做機制,而完整的原型鏈遠遠沒有那麼簡單,來看一張圖:

從上圖能夠看出,對象除了普通的對象(有__proto__屬性,沒有prototype屬性)、原型對象(有constru屬性指向構造函數,也有__proto__指向原型)外,還有函數對象,經過new Function()建立的都是函數對象,也有__proto__屬性指向Function.prototype,Function.prototype也有原型,__proto__指向Object.prototype,最終指向null。

const f = new Function();
f; // 函數對象
f.__proto__ === Function.prototype; // true
f.__proto__.__proto__ === Object.prototype; // true
f.__proto__.__proto__.__proto__ === null; // true
複製代碼

看下函數對象f的打印結果

所以能夠獲得另一條原型鏈,以下圖所示:

原型上的屬性和方法是定義在prototype對象上的,而非對象實例自己。當訪問一個對象的屬性/方法時,它不單單在該對象上查找,還會查找對象的原型,以及該對象的原型的原型,一層一層向上查找,直到找到一個名字匹配的屬性/方法或到達原型鏈的末尾(null)。

function Person(name) {
    this.name = name;
}

function Girl(name) {
    Person.call(this, name);
}

const girl = new Girl('jiaxin');
girl.call(); // Uncaught TypeError: girl.call is not a function
複製代碼

如上述代碼所示,Person函數裏面沒有定義call()方法,爲何Person能夠訪問call()方法呢?咱們把Person打印出來看一下,發現原來是在Person的原型上即Function.prototype上有call()方法能夠訪問。

而Girl函數裏也沒有定義call()方法,那麼爲何執行girl.call()卻會報錯呢?根據原型鏈的思想,咱們girl首先會看自身是否有call()方法,沒有則會一直沿着原型鏈即__proto__向上查找,直到到達原型鏈的末端(null)還找不到,則會報錯。咱們把girl打印出來看看,發現girl實例對象的原型鏈上的確沒有call方法(實際上是由於girl是一個對象,因此沒有函數原型上的call方法)。

總結

一、每一個構造函數都有一個原型對象,而原型對象都包含一個指向構造函數的指針(constructor),而實例都包含一個指向原型對象的內部指針(proto)。

二、每一個對象擁有一個原型對象,經過 proto 指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null,這種關係被稱爲原型鏈。

三、當訪問一個對象的屬性 / 方法時,它不單單在該對象上查找,還會查找該對象的原型,以及該對象的原型的原型,一層一層向上查找,直到找到一個名字匹配的屬性 / 方法或到達原型鏈的末尾(null)。

相關文章
相關標籤/搜索