JavaScript原型與原型鏈

南波發表於2019-03-03

本文共 1475 字,讀完只需 6 分鐘

一、概述

在 JavaScript 中,是一種物件導向的程式設計語言,但是 JS 本身是沒有 “類” 的概念,JS 是靠原型和原型鏈實現物件屬性的繼承。

在理解原型前,需要先知道物件的建構函式是什麼,建構函式都有什麼特點?

1. 建構函式

// 建構函式 Person()
function Person(name, gender) {
    this.name = name;
    this.gender = gender;
}

var person = new Person("周杰倫", "男");

// 最後建立出來的物件例項 person
person

{
    name: "周杰倫",
    gender: "男"
}
複製程式碼

以上程式碼,普通函式 Person(),加上 new 關鍵字後,就構造了一個物件 person

所以建構函式的定義就是普通函式加上 new 關鍵字,並總會返回一個物件。

2. 函式物件
同時,JS 中的物件分為一般物件和函式物件。那什麼是一般物件,什麼又是函式物件呢?

JavaScript 的型別分為基本資料型別和引用資料型別,基本資料型別目前有 6 種(null, undefined, string, number, boolean, Symbol)。 其餘的資料型別都統稱為 object 資料型別,其中,包括 Array, Date, Function等,所以函式可以稱為函式物件。

let foo = function(){

}
foo.name = "bar";
foo.age = 24;
console.log(foo instanceof Function)  //true
console.log(foo.age)  // 24
複製程式碼

以上程式碼就說明了函式其實是一個物件,也可以具有屬性。

二、原型鏈

JavaScript 中的物件,有一個特殊的 [[prototype]] 屬性, 其實就是對於其他物件的引用(委託)。當我們在獲取一個物件的屬性時,如果這個物件上沒有這個屬性,那麼 JS 會沿著物件的 [[prototype]]鏈 一層一層地去找,最後如果沒找到就返回 undefined;

這條一層一層的查詢屬性的方式,就叫做原型鏈。

var o1 = {
    age: 6
}
複製程式碼

那麼,為什麼一個物件要引用,或者說要委託另外一個物件來尋找屬性呢?

本文開篇的第一句話,就指出來的,JavaScript 中,和一般的 OOP 語言不同,它沒有 `類`的概念,也就沒有 `模板` 來建立物件,而是通過字面量或者建構函式的方式直接建立物件。那麼也就不存在所謂的類的複製繼承。

三、原型

那什麼又是原型呢?

既然我們沒有類,就用其他的方式實現類的行為吧,看下面這句話↓↓。

1. 每個函式都有一個原型屬性 prototype 物件

function Person() {

}

Person.prototype.name = `JayChou`;

// person1 和 person2 都是空物件
var person1 = new Person();
var person2 = new Person();

console.log(person1.name) // JayChou
console.log(person2.name) // JayChou
複製程式碼

通過建構函式創造的物件,物件在尋找 name 屬性時,找到了 建構函式的 prototype 物件上。

這個建構函式的 prototype 物件,就是 原型

用示意圖來表示:

JavaScript原型與原型鏈

查詢物件例項屬性時,會沿著原型鏈向上找,在現代瀏覽器中,標準讓每個物件都有一個 __proto__ 屬性,指向原型物件。那麼,我們可以知道物件例項和函式原型物件之間的關係。

JavaScript原型與原型鏈

2. 每個原型物件都有一個 constructor 屬性指向關聯的建構函式

為了驗證這一說話,舉個例子。

function Person() {}

Person === Person.prototype.constructor; // true
複製程式碼

那麼物件例項是建構函式構造而來,那麼物件例項是不是也應該有一個 constructor 呢?

function Person() {}

const person = new Person();
person.constructor === Person // true
複製程式碼

但事實上,物件例項本身並沒有 constructor 屬性,物件例項的 constructor 屬性來自於引用了原型物件的 constructor 屬性

person.constructor === Person.prototype.constructor // true
複製程式碼
JavaScript原型與原型鏈

3. 原型鏈頂層:Object.prototype.__proto__== null

既然 JS 通過原型鏈查詢屬性,那麼鏈的頂層是什麼呢,答案就是 Object 物件,Object 物件其實也有 __proto__屬性,比較特殊的是 Object.prototype.__proto__ 指向 null, 也就是空。

Object.prototype.__proto__ === null
複製程式碼
JavaScript原型與原型鏈

我們回過頭來看函式物件:

所有函式物件的proto都指向Function.prototype,它是一個空函式(Empty function)

Number.__proto__ === Function.prototype  // true
Number.constructor == Function //true

Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true

String.__proto__ === Function.prototype  // true
String.constructor == Function //true

// 所有的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身
Object.__proto__ === Function.prototype  // true
Object.constructor == Function // true

// 所有的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身
Function.__proto__ === Function.prototype // true
Function.constructor == Function //true

Array.__proto__ === Function.prototype   // true
Array.constructor == Function //true

RegExp.__proto__ === Function.prototype  // true
RegExp.constructor == Function //true

Error.__proto__ === Function.prototype   // true
Error.constructor == Function //true

Date.__proto__ === Function.prototype    // true
Date.constructor == Function //true

複製程式碼

所有的構造器都來自於 Function.prototype,甚至包括根構造器Object及Function自身。所有構造器都繼承了·Function.prototype·的屬性及方法。如length、call、apply、bind

以圖會友,這就是網上經常看到的 JS 原型和原型鏈關係圖:

JavaScript原型與原型鏈

對於以上看似很複雜的關係圖,只需要理解 5 點:

  1. 每個函式都有一個原型屬性 prototype 物件
  2. 普通物件的建構函式是 Object(),所以 Person.prototype.__proto__ === Object.prototype
  3. 函式物件都來自於 Function.prototype
  4. 函式物件也是物件,所有 Function.prototype.__proto__ === Object.prototype
  5. 記住,所有函式原型的都是 Object() 的例項
  6. Object.prototype.__proto__ 是 null

總結

以上就是 JavaScript 中原型和原型鏈的知識。由於 JS 沒有`類`, 所以採用了原型的方式實現繼承,正確的說法是引用或者委託,因為物件之間的關係不是複製,而是委託。在查詢屬性的時候,引用(委託)原型物件的屬性,也就是我們常說的原型繼承。

歡迎關注個人微信訂閱號,專注分享原創文章

JavaScript原型與原型鏈

掘金專欄 JavaScript 系列文章

  1. JavaScript之變數及作用域
  2. JavaScript之宣告提升
  3. JavaScript之執行上下文
  4. JavaScript之變數物件
  5. JavaScript原型與原型鏈
  6. JavaScript之作用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中徹底理解this
  12. JavaScript專題之模擬實現call和apply

相關文章