05:JS 原型鏈
在 JavaScript 的世界中,萬物皆對象! 但是這各種各樣的對象其實具體來划分的話就 2 種。 一種是 函數對象
,剩下的就是 普通對象
。其中 Function
和 Object
為JS自帶的 函數對象
。(哎? 等等, Function 為 函數對象 可以理解,為什么 Object
也是函數對象呢?帶着疑問我們繼續往下看。 )
Function 和 Object 為何都是 函數對象呢?
一. 普通對象 和 函數對象
- 函數對象 的疑惑 🤔
var f1 = new Function('arg', 'console.log(arg)')
var test1 = new Object
var f3 = new Object()
console.log(typeof f1) // function
console.log(typeof test1) // object
console.log(typeof f3) // object
經過 new 實例化過后的 test1 和 f3 均為 Object 即為 普通對象。
那如果我們直接 打印出來 未實例化的對象 的類型 呢?
var test2 = Object
var test3 = Object()
console.log(typeof test2) // function
console.log(typeof test3) // object
這里我們看到了一些區別,但是這又是為什么呢?
下面我們看完普通對象再縱向的進行一下對比
- 什么是普通對象? 🤔
我們先想想一下,創建對象的方式?
var F1 = function() {}
var o1 = {}
var o2 = new F1()
var o3 = new Object()
console.log(typeof o1) // object
console.log(typeof o2) // object
console.log(typeof o3) // object
- 直接縱向對比這 兩類 對象
function f1() {}
var f2 = function() {}
var f3 = new Function('arg', 'console.log(arg)')
var o1 = {}
var o2 = new Object()
var o3 = new f1()
console.log(typeof f1) // function
console.log(typeof f2) // function
console.log(typeof f3) // function
console.log(typeof o1) // object
console.log(typeof o2) // object
console.log(typeof o3) // object
// 對比前文中的 Function 和 Object
console.log(typeof Function) // function
console.log(typeof Object) // function
在上面的例子中 o1 o2 o3 為普通對象
,f1 f2 f3 為函數對象
。怎么區分,其實很簡單,凡是通過 new Function()
創建的對象都是函數對象
,其他的都是普通對象
。f1,f2,歸根到底
都是通過 new Function()的方式進行創建的。Function Object 也都是通過 New Function()創建的。
-
f1,f2,歸根到底都是通過 new Function()的方式進行創建的。
這么來理解這句話呢?
function fns(a, b) {
return a + b
}
// 等價於
var fns = new Function('a, b', 'return a + b')
-
上面的代碼做了一個同目標的不同實現實現方法,那么為什么兩種方案的結果都是相同的呢?
講到這里就需要對 JavaScript 這門語言進行 分析了。
JavaScript 是一門解釋型的語言
什么是 解釋型 語言?
在客戶端的瀏覽器中存在一個 可以解釋 JS 的`引擎`。 這里JavaScript的引擎 就是 谷歌的 V8 引擎 和 其他瀏覽器引擎。
而我們常常聽說的 ES5 ES6 什么的,往往指的是當前 JS 語言的版本。或者說當前的 JS 語言的標准。 然后所謂的瀏覽器兼容這些新的特性其實就是瀏覽器的 JS 引擎的升級,去適配這些新版本的 JS 的新特性。 不兼容,往往就是 引擎 不支持這個新特性。
(所以,我們會發現寫一個 瀏覽器 應用 還是很難的。 因為你需要去兼容這么的東西,最新版本的JS,css3 最新的特性 , html5 的新標簽,等等)
那么我們回到 解釋型語言 上來,有了能解釋 JS 語句的引擎了,那么上面就一定會有一定的規則了,不然的話,如果你亂寫都能被 引擎 讀懂的話,要這 引擎 何用。
好,上段 中介紹到了 規則的問題, 那么 JS 語言本身肯定也是隱藏了一些 我們在 ES 系列上看不到的 規則。 那是什么呢?
我們一起來看下,
這就是本小節將要介紹的函數對象(Function Object)。
函數對象與其它用戶所定義的對象有着本質的區別,這一類對象被稱之為內部對象,例如日期對象(Date)、數組對象(Array)、字符串對象(String)都是屬於內部對象。換句話說,這些內置對象的構造器是由JavaScript本身所定義的:通過執行new Array()這樣的語句返回一個對象,JavaScript 內部有一套機制來初始化返回的對象,而不是由用戶來指定對象的構造方式。
這些內置對象的構造器是由JavaScript本身所定義的
所以: new Fucntion('arg', console.log(arg))
new Array()
new Date()
new String()
都會返回對應的 對象。 所以,當我們在用 字面量 去創建一個 函數的時候,JS 解釋器就會 用這些 內置的對象構造器 Function 去 實例化 並返回一個 函數對象。 那么我們可以想象一下,是不是 我們自己在寫函數的時候直接 new Function 的方法來寫 會不會執行效率更高。
同樣 問題就來了, var fn = new Function('a', 'return a')
這樣寫的話,參數還好,但是 函數體 如果很長 很多的話就很難受了,所以~
面前我們所用的創建 函數對象的方法 即為 最方便的方法。
二. 原型對象
什么是原型對象?
在 JavaScript 中,每當定義一個對象的時候,對象中都會包含一些預定義的屬性
。 其中 函數對象
的一個屬性就是 prototype
。 (上文介紹到的 普通對象沒有 prototype,但是有 __proto__
) (但是 函數對象也有 proto 這里需要注重理解下,不然容易出錯。)
所以 經過上面的解釋 是不是就清楚了,原型對象
也是一種 普通對象。但是只有 函數對象
擁有。
但是 有且僅有一個特殊的 案例 需要注意下。
eg:
function f1(){}
console.log(f1.prototype) // {...}
console.log( typeof f1.prototype) // object
console.log(typeof Function.prototype) // Function
console.log(typeof Object.prototype) // Object
console.log(typeof Function.prototype.prototype) //undefined
原型對象其實就是普通對象(Function.prototype除外,它是函數對象,但它很特殊,他沒有prototype屬性(前面說道函數對象都有prototype屬性))
為什么?
從這句console.log(f1. prototype) //f1 {}
的輸出就結果可以看出,f1.prototype 就是 f1 的一個實例對象(這里結合上面講到了 實例話 函數對象的過程)。就是在f1 函數 在創建的時候,創建了一個它的實例對象 (var temp = new Function('','') ) 並賦值給它的prototype (f1.prototype = temp)
console.log(typeof Function.prototype.prototype) //undefined
唯一一個特殊的 函數對象沒有 prototype 屬性的。 為什么? 就是根據上面在 控制台打印出來的結果。它是個特例,需要特殊記憶!
原型對象有什么作用?
主要是用來繼承
eg:
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
Person.prototype.changeName = function(name) {
this.name = name
}
Person.prototype.getFirstName = function(name) {
return this.name
}
var zhang = new Person('zhang')
var res0 = zhang.getName()
var res1 = zhang.getFirstName()
console.log(res0) // zhang
console.log(res1,'xxx') // zhang xxx
zhang.changeName('ge')
var res2 = zhang.getName()
console.log(res2) // ge
通過這個例子我們可以看出來, 我們通過給 構造函數
的 prototype
屬性添加 方法(getName)。
那么它 所有實例化出來的 函數對象都會帶有這個方法(getName),同樣添加屬性也是一樣。
那么為什么 能夠 實現繼承呢? 下面我們就講到了 原型鏈
。
三.原型鏈
JS 在創建對象(不論普通對象還是函數對象)的時候,都有一個叫做__proto__
對內置屬性,用於指向創建它對函數對象的原型對象 prototype
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
var zhang = new Person('zhang')
zhang.__proto__ === zhang.prototype // true
同樣,zhang.prototype
對象也有 __proto__
屬性,它指向創建它的函數對象(Object)的prototype
zhang.prototype.__proto__ === Object.prototype // true
繼續,Object.prototype
對象也有 __proto__
屬性,但它比較特殊,為null
console.log(Object.prototype.__proto__) //null
我們把這個有__proto__
串起來的直到Object.prototype.__proto__
為null
的鏈
叫做原型鏈
按照我們上面說的例子來展示下這個原型鏈
var Person = function(name) {
this.name = name
this.getName = function() {
return this.name
}
}
var obj = new Person('zhang')
// 其中 obj.__proto__ 指向了 Person.prototype(即為 Person 的實例)
// Person.prototype 的 __proto__ 指向了 Object.prototype
// Object.prototype 的 __proto__ 指向了 null
通過 __proto__ 串起來的直到Object.prototype.__proto__為null的鏈叫做原型鏈
四.constructor
原型對象prototype
中都有個預定義的constructor
屬性,用來引用它的函數對象。這是一種循環引用
**1、Person.prototype.constructor **
ƒ (name) {
this.name = name
this.getName = function() {
return this.name
}
}
2、Function.prototype.constructor
ƒ Function() { [native code] }
**3、Object.prototype.constructor **
ƒ Object() { [native code] }
person.prototype. constructor === person // true
Function.prototype.constructor === Function // true
Object.prototype.constructor === Object // true
六.總結
最后打個比喻,雖然不是很確切,但可能對原型
的理解有些幫助
父親(函數對象),先生了一個大兒子( prototype
),也就是你大哥,父親給你大哥買了好多的玩具,當你出生的時候,你們之間的親情紐帶(__proto__
)會讓你自然而然的擁有了你大哥的玩具。
同樣,你也先生個大兒子,又給他買了好多的玩具,當你再生兒子的時候,你的小兒子會自然擁有你大兒子的所有玩具。至於他們會不會打架,這不是我們的事了。
所以說,你是從你大哥那繼承的,印證了那句“長兄如父”啊!
能夠對上圖有所理解的話,原型 、原型鏈 等等都有一個很好的理解了,
當然也需要有大量的 OOP 相關的開發,才能對 JS 的 OOP 有一個 深刻的理解。