JS原型鏈、__proto__和prototype

不知道你有沒有想過這樣一個問題

  • 為什么我定義一個數組,它就有push、join、pop、shift等方法,我明明什么也沒寫啊?
  • 為什么我定義一個函數,它就有call、apply、length等屬性/方法,我也什么都沒有做呀?!
  • 為什么我定義一個對象,它就有toString、valueOf等方法,我更是什么都沒有做呀?!

1、我們先來說說對象。

當我們定義一個對象時

var obj = {}


我們發現obj下面有一個屬性名叫__proto__,(它是個對象)
obj.__proto__里面又有很多屬性,包括 valueOf、toString、constructor 等。當我們需要找obj.valueOf這個屬性時,發現obj本身沒有沒有,那么它就會去查找obj.__proto__是否有這個屬性,如果還沒有,它去找obj.__proto__.__proto__,直到找到這個屬性或null為止,在這個讀取屬性的過程中,是沿著__proto__組成的鏈子來搜索的,這個鏈子我們稱為原型鏈
如果obj自身定義了一個valueOf屬性,那么它找到自身的valueOf之后就不再沿著__proto__來找,因為已經找到了,沒有必要繼續找了,也就是說

  • 新增的屬性不會沿著__proto__查找
  • 讀取屬性會沿著__proto__,直到找到這個屬性,或者是null為止。

2、那么obj.__proto__到底是什么呢?

__proto__是一個簡單的訪問器屬性,它總是指向它的構造函數的prototype。即原型對象。

所有的對象都繼承了Object.prototype的屬性和方法,
它們可以被覆蓋(除了以null為原型的對象,如 Object.create(null))。
例如,新的構造函數的原型覆蓋原來的構造函數的原型,提供它們自己的 toString() 方法.。對象的原型的改變會傳播到所有對象上,除非這些屬性和方法被其他對原型鏈更里層的改動所覆蓋。

所有的對象會動態生成一個__proto__指向它構造函數的原型(prototype )
當我們去查找obj.valueOf這個屬性時,他會沿著原型鏈去查找obj.__proto__.valueOf,而obj.__proto__指向obj.constructor.prototype。即

obj.__proto__ === obj.constructor.prototype  // true
obj.__proto__.toString=== obj.constructor.prototype.toString  // true

我們知道obj的構造函數就是Object,那么我們也可以這么寫

obj.__proto__ === Object.prototype  // true
obj.__proto__.toString === Object.prototype.toString  // true

3、對于數組

以push方法為例,我們知道當我們定義一個空數組時,我們可以直接調用push方法,根據上面的解釋,他會沿著這個數組的__proto__去查找這個方法。

var array = []
array.push === array.__proto__.push // true

array.__proto__指向它構造函數的prototype,那么

array.__proto__.push === array.constructor.prototype.push // true
array.__proto__.push === Array.prototype.push  // true

終于找到push方法了。

問題來了,我們知道array也是對象,那么JS是怎么知道array也是對象的呢?

答:通過__proto__

我們先看Array.__proto__指向誰

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

你也許會納悶,怎么Array.__proto__指向的怎么是函數的原型對象呢?因為Array的構造函數就是函數,不信你console.log(Array.constructor)試試?而函數的原型對象的__proto__最終指向Object

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

或者我們也可以這樣寫

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

最終歸宿都是Object。

至此我們看下一個小小的array都經歷了什么

array.__proto__  ----> Array.prototype ----> Array.prototype.__proto__---->
Object.prototype

這樣也就不難理解為什么數組(函數)也是個對象了。

4、一切皆對象?

也許你會迷惑,既然Array.__proto__.__proto__ = Object.prototype,那么

Number.__proto__.__proto__ === Object.prototype   // true
Boolean.__proto__ .__proto__ === Object.prototype   // true
String.__proto__.__proto__ === Object.prototype   // true

為什么我們不可以說數字/布爾/字符串也是對象呢?
這要看這個數字/布爾/字符串是怎么創建的了。
以數字為例

var a = 1
var b = new Number(1)

我們知道基本類型是沒有屬性的,即便可以訪問到這個屬性,也是訪問的臨時對象的屬性,訪問完就銷毀了,即使你發現a.__proto__.__proto__指向是Object,也是new Number指向的,跟a沒有半毛錢關系,因為a就是個number。
b就不一樣了,b是構造函數Number構造出來的一個對象,只不過他的值是1,它可是有__proto__屬性的,那么b就可以愉快的指來指去了。

b.__proto__.__proto__ === Object.prototype  // true

所以a是一個number,而b是一個object。
同理字符串和布爾也是如此。

來跟我一起大聲念JS的七種數據類型:
number , string , boolean , undefined , null , object , symbol
說JS一切皆對象的,你們當其他類型是吃干飯的么?

參考

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

推薦閱讀更多精彩內容