JavaScript 筆記二:原型鏈的深度剖析

先看一下下面的代碼,如果你很簡單就能理解,那么可以跳過這一篇文章了。

var x = new y();

// 1、所有的實例的原型都是由其構(gòu)造函數(shù)的原型對象:
x.__proto__ === y.prototype

// 2、所有的構(gòu)造函數(shù)都是 Function 的實例,有1可知:
y.__proto__ === Function.prototype

// 3、因為構(gòu)造函數(shù)的原型對象也是對象,所以由1可知:
y.prototype.__proto__ === Object.prototype

// 4、Object 是頂級對象,所以它不符合第3條規(guī)則:
Object.prototype.__proto__ // is null

沒看明白的我們繼續(xù)。。。


想要理解原型鏈,先知原型為何物

先了解一下原型模式吧:原型模式

在原型模式中我們可以利用過一個原型對象來指明我們所要創(chuàng)建對象的類型,然后通過復(fù)制這個對象的方法來獲得與該對象一模一樣的對象實例,所以:

  • 實例是由原型拷貝而來的。

再其次,我們都知道,JS中的聲明,實際是 new 了一個構(gòu)造函數(shù),比如:

var a = {};

// 實際是
var a = new Object();

可知,a 是一個對象實例,所以 new Object 構(gòu)造函數(shù)做了什么?

  • 構(gòu)造函數(shù)拷貝了相應(yīng)的對象并返回了這個拷貝,即拷貝原型返回了一個實例。
  • 構(gòu)造函數(shù)存在一個原型對象,這個原型對象就是構(gòu)造函數(shù)拷貝的對象。
  • 構(gòu)造函數(shù)的 prototype 字段所指向它要拷貝的原型對象,即“構(gòu)造函數(shù).prototype”

構(gòu)造函數(shù).prototype ---> 以 String 為例就是 String.prototype

由上述可以,實例的原型就是構(gòu)造函數(shù)上的原型對象,而JS中,為了實現(xiàn)繼承,實例的 __proto__ 字段所指向它的原型,,即“實例.__proto__”。

實例.__proto__ ---> 以 var a = {} 為例,就是 a.__proto__

構(gòu)造函數(shù).prototype 指向它要拷貝的原型對象,而 實例.__proto__ 指向它的原型,都指向同一個對象,故:

var x = new y();
x.__proto__ === y.prototype

現(xiàn)在應(yīng)該知道原型是什么了吧。

函數(shù)實例

其實構(gòu)造函數(shù)是一個函數(shù)實例,所以可以知道:

Object.__proto__ === Function.prototype // true
Array.__proto__ === Function.prototype // true
String.__proto__ === Function.prototype // true

// 特殊的 Function
Function.__proto__ === Function.prototype // true

萬物皆對象

再研究一下,因為函數(shù)的 prototype 一個對象實例,所以構(gòu)造函數(shù)的原型對象的原型屬于誰呢?
答案是:Object ,萬物皆對象。

Array.prototype.__proto__ === Object.prototype // true
String.prototype.__proto__ === Object.prototype // true
Function.prototype.__proto__ === Object.prototype // true

// 物皆對象,所以 Object 的原型對象已經(jīng)是頂級了
Object.prototype.__proto__ // null

所以,這里已經(jīng)解釋了最開始的三行代碼。

順帶提一下:constructor

以上就是原型和原型對象的概念,順便提一個原型對象上的屬性:constructor ,它指向創(chuàng)造這個原型對象的函數(shù),即:

// 函數(shù)的原型對象的構(gòu)造函數(shù)就是它自己
Array.prototype.constructor === Array // true
String.prototype.constructor === String // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true

// 實例的原型的構(gòu)造函數(shù)就是聲明的這個實例
var a = [], b = {}, c = '';
a.__proto__.constructor === Array // true
b.__proto__.constructor === Object // true
c.__proto__.constructor === String // true

現(xiàn)在來理解一下原型鏈。

何為原型鏈

其實上面有代碼已經(jīng)初現(xiàn)端倪了:

Array.prototype.__proto__ === Object.prototype // true

構(gòu)造函數(shù)的原型對象的原型,是不是有些拗口,這其實就是原型鏈,再來一個看幾個代碼:

var a = [];
a.__proto__ // = Array.prototype
a.__proto__.constructor // = Array
a.__proto__.constructor.prototype // = Array.prototype

有一萬中寫法來顯示一些東西,其實原型鏈的大致概念是:

  • 實例使用屬性和方法會依次檢索本身和其原型,而原型也屬實例,故也會檢索本身和其原型,所以會形成一級一級地向上檢測查找的鏈式結(jié)構(gòu),這種結(jié)構(gòu)就稱原型鏈。

所以上面的例子可以這樣簡寫:

var a = [], b = {}, c = '';
a.constructor === Array // true
b.constructor === Object // true
c.constructor === String // true

因為 a 沒有 constructor ,所以會去找原型上的 constructor ,so...

再看一個例子,因為原型鏈的存在,所以可以在構(gòu)造函數(shù)的原型對象上添加方法,這樣所有由此函數(shù)創(chuàng)建的實例都可以調(diào)用此方法:

function a(){}
a.prototype.say = function(x){ console.log(x) }

var b = new a();
var c = new a();

b.say(‘hello’); // hello
c.say(‘你好’); // 你好

簡單提一下 ES6 的類的定義

上面的寫法怎么看會有點奇怪,ES6 新增了 class 的聲明,可以這樣寫一個類:

// 定義類
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  sayHello() {
    console.log(`你好,我是${this.name},我今年${this.age}歲`);
  }
}

// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲

對比一下ES5代碼:

// 定義類
function User(name, age){
  this.name = name;
  this.age = age;
}
User.prototype.sayHello = function(){
  console.log(`你好,我是${this.name},我今年${this.age}歲`);
}

// 實例
var my = new User('jack', 22);
my.sayHello(); // 你好,我是jack,我今年22歲

Class 定義其本身是個語法糖,可以用ES5來實現(xiàn),具體參考此處

OK,說完了,原型鏈就這一點點。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。