本文共 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
物件,就是 原型
用示意圖來表示:
查詢物件例項屬性時,會沿著原型鏈向上找,在現代瀏覽器中,標準讓每個物件都有一個 __proto__
屬性,指向原型物件。那麼,我們可以知道物件例項和函式原型物件之間的關係。
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
複製程式碼
3. 原型鏈頂層:Object.prototype.__proto__
== null
既然 JS 通過原型鏈查詢屬性,那麼鏈的頂層是什麼呢,答案就是 Object 物件,Object 物件其實也有 __proto__
屬性,比較特殊的是 Object.prototype.__proto__
指向 null
, 也就是空。
Object.prototype.__proto__ === null
複製程式碼
我們回過頭來看函式物件:
所有函式物件的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 原型和原型鏈關係圖:
對於以上看似很複雜的關係圖,只需要理解 5 點:
- 每個函式都有一個原型屬性 prototype 物件
- 普通物件的建構函式是 Object(),所以
Person.prototype.__proto__ === Object.prototype
- 函式物件都來自於 Function.prototype
- 函式物件也是物件,所有
Function.prototype.__proto__ === Object.prototype
- 記住,所有函式原型的都是 Object() 的例項
Object.prototype.__proto__
是 null
總結
以上就是 JavaScript 中原型和原型鏈的知識。由於 JS 沒有`類`, 所以採用了原型的方式實現繼承,正確的說法是引用或者委託,因為物件之間的關係不是複製,而是委託。在查詢屬性的時候,引用(委託)原型物件的屬性,也就是我們常說的原型繼承。
歡迎關注個人微信訂閱號,專注分享原創文章