JavaScript 是一門基於原型的語言,它沒有建立類,對象被建立是基於引用的,JavaScript 也是一種動態編程語言,這意味着能夠在實例化後輕鬆地在對象中添加或刪除屬性。html
在 Js 中沒有類的概念,在 Js 中所存在的是構造函數代替模擬類的存在,對象由某個構造函數構造出來,咱們稱之爲這個對象是 構造函數的實例, 即便是使用 var 聲明的一個對象,咱們也能夠看到他的
__proto__
中的 constructor 也是指向的 Object 。git
__proto__
咱們已知函數經過 new 關鍵字調用就是所謂的構造函數,獲得一個實例。在這個構造函數,或者說 每一個函數 上面,都會有一個 prototype 屬性,這個 prototype 屬性 指向了一個對象,暫且無論這個對象是什麼,只須要知道 函數 有一個 prototype 屬性,這個屬性能夠經過 點的方式訪問到。github
在構造函數所構造的實例上,或者說 Js 中的每個對象上都會有一個
__proto__
屬性(這個屬性雖然能夠看到,也能夠訪問到,但並不建議直接使用 修改 對象的原型;詳見)編程實例的
__proto__
也指向了一個對象,實例的__proto__
和構造函數的 prototype 指向的是同一個對象,咱們稱這個對象爲 原型對象segmentfault
function Fn() {
this.name = 'Zyc'
}
var f = new Fn()
console.log(Fn.prototype) // 一個 Object
console.log(f.__proto__) // 一個 Object
console.log('Fn & f', Fn.prototype === f.__proto__) // true
複製代碼
從位置上來講,確實如上所述,那麼 究竟什麼是所謂的 原型 呢,咱們須要再從 構造函數出發:bash
函數的 prototype 屬性指向了一個對象,這個對象正是調用該構造函數而建立的實例的原型,也就是 上述 實例 f 的原型。 而所謂的原型就是:每個 JavaScript 對象(null 除外) 在建立的時候就會與之關聯另外一個對象,這個對象就是咱們所說的原型,每個對象都會從 原型 "繼承" 屬性編程語言
在每一個 原型對象 中,都會有一個 constructor 屬性,這個屬性指向其關聯的構造函數函數
function Person() {
}
var person1 = new Person()
console.log('Person && person1', Person.prototype === person1.__proto__) // true
console.log(Person.prototype.constructor === Person) // true
複製代碼
其實在構造函數內部直接定義方法也是能夠的,問題在於,每一個構造函數均可能會調用屢次去構建實例,若是把方法定義在 構造函數內部,那麼在每次調用的時候,都會從新建立這個函數,伴隨着函數的建立,會在內存中開闢一塊空間存儲這個函數,可是每一個實例用到的方法內部代碼徹底一致,就形成了資源浪費。性能
若是是將方法定義在函數的 原型 上面,全部的實例都可以訪問這同一個方法,不會形成資源浪費,代碼更高效學習
還有一種解決方案,就是在構造函數外部預先定義好一個函數,在構造函數內部引用這個函數,那麼全部的實例將擁有同一個函數的地址,也能夠避免資源浪費
Ps:一個核心觀念就是,構造函數會被調用屢次建立實例,每一次調用就會伴隨着 函數內部代碼的執行,若是內部有函數的聲明,將會不斷的開闢內存空間,就會資源浪費,若是有方法實現公用一個內存地址,那麼就能夠避免掉性能浪費的問題
當咱們在獲取一個對象的屬性時,會優先在對象內部查找這個屬性,當在對象內部沒有找到這個屬性時,就會去對象的原型上面去找,若是在實例的原型上面也沒有找到,就會去原型的原型上面去找。
function Person() {
}
Person.prototype['hobby'] = ['唱', '跳', 'Rap']
var person1 = new Person() // 此時 Person 的實例 person1 中並無 hobby 這個屬性
console.log(person1.hobby); // ['唱', '跳', 'Rap']
person1['hobby'] = ['看書', '學習', '運動'] // 給 person1 實例添加了 hobby 屬性
console.log(person1.hobby); // ['唱', '跳', 'Rap']
複製代碼
已知 原型對象 也是一個對象,其實原型對象就是經過 Object 構造函數生成的,也就是說,一個實例的原型對象 是 Object 的實例, 那麼繼而能夠得出 原型對象 的
__proto__
和 Object 構造函數 的 prototype 指向同一個地址,這個地址也就是 Object 的 原型
Object.prototype['address'] = 'NJ'
function Person() {
}
Person.prototype['hobby'] = ['唱', '跳', 'Rap']
var person1 = new Person()
console.log(person1.hobby); // ['唱', '跳', 'Rap']
person1['hobby'] = ['看書', '學習', '運動']
console.log('hobby:', person1.hobby); // ['唱', '跳', 'Rap']
// 在 Person 的 原型 -> 原型 上定義的 address
console.log('address:', person1.address); // NJ
// 未在任何地方定義的屬性:
console.log('age:', person1.age); // undefined
複製代碼
引上述:對象屬性的查找規則,咱們 既沒有在 Person 的 prototype 上定義 address 也沒有在 實例內部定義 address,而是在 Object 的 prototype 上定義的 address,在打印
person1.address
時的確打印了出來數據。若是未在任何地方定義的數據 最終獲取 爲 undefined證:對象查找屬性規則 = 對象內部 > 原型 > 原型 > 原型 > undefined
截止到上面,應該能夠體會到一個如同鏈條的存在,所謂的原型鏈,就是 原型 => 原型 => 原型 原型串聯起來的鏈條,而在一個對象查找一個屬性時,就會在原型鏈上逐步往上查找,要麼找到,要麼沒找到也就是 undefined
Ps:
function Person() {
}
var person2 = new Person()
console.log(person2 instanceof Person); // true
console.log(person2 instanceof Object); // true
複製代碼
如上所述:JavaScript 判斷一個對象是否 instanceof 某個函數的依據,即對象 person2 的 原型鏈 上有沒有一個
__proto__
是這個函數的 prototype, 若是有,那麼 person 就是這個函數的 instance。因爲通常全部的原型鏈最終都會指向頂端的 Object.prototype,因此它們都是 Object 的 instance。一句話理解 instanceof 的運算規則爲: instanceof 檢測左側的
__proto__
原型鏈上,是否存在右側的 prototype 原型。
首先 原型鏈 必須也然有終點,若是沒有終點在查找對象的屬性時會無限查找下去 其次 原型鏈 的終點並非 Object.prototype; 亦能夠換種說法,原型鏈的終點就是 Object.prototype 而這個 Object.prototype 是一個特殊的 Object 它特殊在 它的
__proto__
指向 null。而我我的更偏向於考慮原型鏈 的終點爲 null。首先須要明確一點,原型鏈是指 對象 的原型鏈,因此原型鏈上的全部節點都是 對象, 不能是字符串、數字、布爾等原始類型。 另外規範要求原型鏈必須是 有限長度 的(從任一節點出發,通過有限的步驟後必須到達一個終點,顯然也不能有環) 那麼應該用什麼對象做爲終點呢? 很顯然應該用一個特殊的對象。 Object.prototype 確實是個特殊的對象,咱們先假設用它作終點,那麼考慮一下,當取它的原型時應該怎麼辦? 即:
Object.prototype.__proto__
應該返回什麼? 固然 JavaScript 已經給了咱們答案,先不考慮已知的答案 取一個對象的屬性時,可能發生三種狀況:
- 若是屬性值存在,那麼返回屬性的值
- 若是屬性不存在,那麼返回 undefined
- 無論屬性存在與否,有可能拋出異常
綜上所述, 咱們已經假設 Object.prototype 是終點了,哪裏還能獲取屬性,因此排除掉 1 。另外拋出異常也不可取,3 排除。 狀況 2,它不存在 原型屬性了,返回 undefined 呢? 也很差,由於 undefined 一種解釋是原型不存在,可是也至關於原型就是 undefined。這樣,在原型鏈上就會存在一個非對象的值。
因此,最佳選擇就是 null。一方面,你無法訪問 null 的屬性,因此起到了終止原型鏈的做用;另外一方面,null 在某種意義上也是一種對象,即空對象(JavaScript 中之因此存在 null, 就是爲了表示空對象的)。這樣一來,就不會違反 「原型鏈 上只有對象」的約定。
因此,「原型鏈的終點是 null」 雖然不是必須不可的,但確實最合理的。
在 JavaScript 裏任何東西都是對象,包括函數,能夠稱爲函數對象。因此 Foo 也是對象,既然是對象,必然有
__proto__
屬性,那麼函數對象 Foo 的__proto__
又指向了哪裏,又是誰的 instance ?JavaScript 裏定義了一個特殊的函數叫 Function,能夠稱做全部函數的爸爸,全部的函數都是它的實例,所以你能夠認爲,定義 Foo 的時候發生了這樣的事情:
var Foo = new Function(args, function_body);
因而:
function Foo() {
}
console.log('Function && Foo:', Function.prototype === Foo.__proto__); // true
console.log(Foo instanceof Function); // true
複製代碼
函數 Foo 由特殊的構造函數 Function 構建,因此 Function.prototype 與 Foo.
__proto__
指向同一個對象,也就是函數的 原型注意這裏的 Function.prototype,這也是 JavaScript 裏一個特殊的對象,Chrome 的 console 裏要是輸入 Function.prototype,根本什麼也打印不出來,什麼 native code,就是說它是 內部實現 的。
走到這一步,Function.prototype 並無走到頭,咱們繼續探索 Function.prototype.
__proto__
, 能夠發現 ··TMD·· 居然是 Object.prototype,因而:
Object.prototype['fooName'] = 'HAHAHA'
function Foo() {
}
console.log('Function 的原型的原型:', Function.prototype.__proto__ === Object.prototype); // true
console.log(Foo.fooName); // HAHAHA
複製代碼
那麼問題來了,Function 本身呢? 它其實也是個函數,也是個對象,它的
__proto__
指向誰? 答案是它本身的 prototype因而:
function Foo() {
}
console.log(Function.__proto__ === Function.prototype); // true
複製代碼
So:全部的函數都是 Function 的 instance, Function本身也是它本身的實例,不事後者嚴格來講並不許確,Function 並非它本身創造本身的,而應該看做 JavaScript 裏原生的一個函數對象,只不過它的
__proto__
指向了它本身的 prototype 而已。
咱們已知通常任何對象都是 Object 的 instance, 由於原型鏈的頂端都指向了 Object.prototype。 那麼 Object 自己是什麼? Object 也是個函數,而任何函數都是 Function 的實例對象,好比 Array, String,固然 Object 也包括在內,它也是 Function 的實例,因而:
console.log(Object.__proto__ === Function.prototype); // true
console.log(Object instanceof Function) // true
console.log(Function instanceof Object) // true
console.log(Object instanceof Object) // true
console.log(Function instanceof Function) // true
複製代碼
WDNMD ??
結合上文中所知的解讀上述代碼:
已知 Object 是全部對象的構造函數,正由於它是一個函數,因此它是 Function 的實例,故 Object instanceof Function
已知 Function.
__proto__
指向 Function.prototype, 指向同一個 原型 這個原型是一個對象且並非終點,繼續獲取 Function.prototype.__proto__
指向了 Object.prototype, 故 Function 的原型鏈的頂端亦是 Object.prototype,而 instance 的查找規則就是原型鏈上是否有一個 原型 是某個構造函數的 prototype, 因此 Function instanceof Object 成立已知 Object.
__proto__
=== Function.prototype , Function.prototype.__proto__
=== Object.prototype, 故:Object.__proto__
=> Function.prototype.__proto__
=> Object.prototype 故:Object instance Object 成立已知:Function.
__proto__
=== Function.prototype, 故:Function instanceof Function 成立
那麼問題來了,Function 和 Object 到底誰先誰後,誰主誰次?因而乎,就有了一個 JavaScript 裏常常說到的蛋雞問題:
Object instanceof Function === true > Function instanceof Object === true
借鑑網上的理解:
首先沒雞沒蛋,先有一個特殊對象 root_prototype,它是上帝。
接下來應該是先有 Function,而且定義它的 prototype 和proto,都連上了 root_prototype。
而後纔有了 Object,它是 Function 的 instance,繼承了 Function。這時候 Object 仍然只是個普通的函數。
而後規定 Object.prototype = root_prototype,這時候 Object 纔開始顯得特殊,成爲了原型鏈的頂端,不然它和其它函數根本沒什麼區別。
因而全部的東西,包括 Function,都成了 Object 的 instance 了。
這裏要強調 Object 和其它函數的不一樣之處。Object 之因此特殊,就是由於 Object 的 prototype 被設定爲了 root_prototype,僅此而已;而其它函數例如 foo,它的 prototype 只是一個普通的對象,這個對象的proto默認狀況下指向 root_prototype。至於爲何這樣設定,爲何 Object 會特殊化,大概只是由於 Object 這個名字起得好,而 foo,bar 沒那麼特殊。因此說白了 Object 函數只是一個盛放 root_prototype 的容器而已,從而使它晉升爲一個特殊的函數。
另外值得注意的是,obj instanceof function 並不意味着 obj 就是這個 function 建立出來的,只不過是 obj 的原型鏈上有 function.prototype 而已。
因此所謂的 Object instanceof Function 和 Function instanceof Object 的蛋雞問題,前者應該來講是天然而然、無可置疑的,能夠認爲 Object 函數是 Function 創造出來的;然後者說白了只是由於強行規定了 Object 函數的特殊性,而致使的一個推論,而 Function 並不是是 Object 建立的。
訝羽的博客 - JS 深刻系列 - 從原型到原型鏈 - github 地址
JavaScript 原型鏈以及 Object,Function 之間的關係
爲何原型鏈的終點是 null,而不是 Object.prototype?
JavaScript 自定義構造函數存在的問題(爲何要使用原型)