一、概述
在 JavaScript 中,是一種面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,但是 JS 本身是沒(méi)有 “類(lèi)” 的概念,JS 是靠原型和原型鏈實(shí)現(xiàn)對(duì)象屬性的繼承。
在理解原型前,需要先知道對(duì)象的構(gòu)造函數(shù)是什么,構(gòu)造函數(shù)都有什么特點(diǎn)?
1. 構(gòu)造函數(shù)
// 構(gòu)造函數(shù) Person()
function Person(name, gender) {
this.name = name;
this.gender = gender;
}
var person = new Person("周杰倫", "男");
// 最后創(chuàng)建出來(lái)的對(duì)象實(shí)例 person
person
{
name: "周杰倫",
gender: "男"
}
以上代碼,普通函數(shù) Person(),加上 new 關(guān)鍵字后,就構(gòu)造了一個(gè)對(duì)象 person
所以構(gòu)造函數(shù)的定義就是普通函數(shù)加上 new 關(guān)鍵字,并總會(huì)返回一個(gè)對(duì)象。
2. 函數(shù)對(duì)象
同時(shí),JS 中的對(duì)象分為一般對(duì)象和函數(shù)對(duì)象。那什么是一般對(duì)象,什么又是函數(shù)對(duì)象呢?
JavaScript 的類(lèi)型分為基本數(shù)據(jù)類(lèi)型和引用數(shù)據(jù)類(lèi)型,基本數(shù)據(jù)類(lèi)型目前有 6 種(null, undefined, string, number, boolean, Symbol)。 其余的數(shù)據(jù)類(lèi)型都統(tǒng)稱(chēng)為 object 數(shù)據(jù)類(lèi)型,其中,包括 Array, Date, Function等,所以函數(shù)可以稱(chēng)為函數(shù)對(duì)象。
let foo = function(){
}
foo.name = "bar";
foo.age = 24;
console.log(foo instanceof Function) //true
console.log(foo.age) // 24
以上代碼就說(shuō)明了函數(shù)其實(shí)是一個(gè)對(duì)象,也可以具有屬性。
二、原型鏈
JavaScript 中的對(duì)象,有一個(gè)特殊的 [[prototype]] 屬性, 其實(shí)就是對(duì)于其他對(duì)象的引用(委托)。當(dāng)我們?cè)讷@取一個(gè)對(duì)象的屬性時(shí),如果這個(gè)對(duì)象上沒(méi)有這個(gè)屬性,那么 JS 會(huì)沿著對(duì)象的 [[prototype]]鏈 一層一層地去找,最后如果沒(méi)找到就返回 undefined;
這條一層一層的查找屬性的方式,就叫做原型鏈。
var o1 = {
age: 6
}
那么,為什么一個(gè)對(duì)象要引用,或者說(shuō)要委托另外一個(gè)對(duì)象來(lái)尋找屬性呢?
本文開(kāi)篇的第一句話(huà),就指出來(lái)的,JavaScript 中,和一般的 OOP 語(yǔ)言不同,它沒(méi)有 '類(lèi)'的概念,也就沒(méi)有 '模板' 來(lái)創(chuàng)建對(duì)象,而是通過(guò)字面量或者構(gòu)造函數(shù)的方式直接創(chuàng)建對(duì)象。那么也就不存在所謂的類(lèi)的復(fù)制繼承。
三、原型
那什么又是原型呢?
既然我們沒(méi)有類(lèi),就用其他的方式實(shí)現(xiàn)類(lèi)的行為吧,看下面這句話(huà)↓↓。
1. 每個(gè)函數(shù)都有一個(gè)原型屬性 prototype 對(duì)象
function Person() {
}
Person.prototype.name = 'JayChou';
// person1 和 person2 都是空對(duì)象
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // JayChou
console.log(person2.name) // JayChou
通過(guò)構(gòu)造函數(shù)創(chuàng)造的對(duì)象,對(duì)象在尋找 name 屬性時(shí),找到了 構(gòu)造函數(shù)的 prototype 對(duì)象上。
這個(gè)構(gòu)造函數(shù)的 prototype 對(duì)象,就是 原型
用示意圖來(lái)表示:

查找對(duì)象實(shí)例屬性時(shí),會(huì)沿著原型鏈向上找,在現(xiàn)代瀏覽器中,標(biāo)準(zhǔn)讓每個(gè)對(duì)象都有一個(gè) __proto__ 屬性,指向原型對(duì)象。那么,我們可以知道對(duì)象實(shí)例和函數(shù)原型對(duì)象之間的關(guān)系。

2. 每個(gè)原型對(duì)象都有一個(gè) constructor 屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)
為了驗(yàn)證這一說(shuō)話(huà),舉個(gè)例子。
function Person() {}
Person === Person.prototype.constructor; // true
那么對(duì)象實(shí)例是構(gòu)造函數(shù)構(gòu)造而來(lái),那么對(duì)象實(shí)例是不是也應(yīng)該有一個(gè) constructor 呢?
function Person() {}
const person = new Person();
person.constructor === Person // true
但事實(shí)上,對(duì)象實(shí)例本身并沒(méi)有 constructor 屬性,對(duì)象實(shí)例的 constructor 屬性來(lái)自于引用了原型對(duì)象的 constructor 屬性
person.constructor === Person.prototype.constructor // true

3. 原型鏈頂層:Object.proto** == null**
既然 JS 通過(guò)原型鏈查找屬性,那么鏈的頂層是什么呢,答案就是 Object 對(duì)象,Object 對(duì)象其實(shí)也有 __proto__屬性,比較特殊的是 Object.__proto__ 指向 null, 也就是空。
Object.prototype.__proto__ === null

我們回過(guò)頭來(lái)看函數(shù)對(duì)象:
所有函數(shù)對(duì)象的proto都指向Function.prototype,它是一個(gè)空函數(shù)(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
// 所有的構(gòu)造器都來(lái)自于Function.prototype,甚至包括根構(gòu)造器Object及Function自身
Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
// 所有的構(gòu)造器都來(lái)自于Function.prototype,甚至包括根構(gòu)造器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
所有的構(gòu)造器都來(lái)自于 Function.prototype,甚至包括根構(gòu)造器Object及Function自身。所有構(gòu)造器都繼承了·Function.prototype·的屬性及方法。如length、call、apply、bind
以圖會(huì)友,這就是網(wǎng)上經(jīng)??吹降?JS 原型和原型鏈關(guān)系圖:

對(duì)于以上看似很復(fù)雜的關(guān)系圖,只需要理解 5 點(diǎn):
- 每個(gè)函數(shù)都有一個(gè)原型屬性 prototype 對(duì)象
- 普通對(duì)象的構(gòu)造函數(shù)是 Object(),所以
Person.prototype.__proto__ === Object.prototype - 函數(shù)對(duì)象都來(lái)自于 Function.prototype
- 函數(shù)對(duì)象也是對(duì)象,所有
Function.prototype.__proto__ === Object.prototype -
Object.__proto__是 null
總結(jié)
以上就是 JavaScript 中原型和原型鏈的知識(shí)。由于 JS 沒(méi)有'類(lèi)', 所以采用了原型的方式實(shí)現(xiàn)繼承,正確的說(shuō)法是引用或者委托,因?yàn)閷?duì)象之間的關(guān)系不是復(fù)制,而是委托。在查找屬性的時(shí)候,引用(委托)原型對(duì)象的屬性,也就是我們常說(shuō)的原型繼承。