不知道你有沒有想過這樣一個問題
- 為什么我定義一個數組,它就有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一切皆對象的,你們當其他類型是吃干飯的么?