《前端之路》之 JavaScript原型及原型鏈詳解

05:JS 原型鏈

在 JavaScript 的世界中,萬物皆對象! 可是這各類各樣的對象其實具體來劃分的話就 2 種。 一種是 函數對象,剩下的就是 普通對象。其中 FunctionObject 爲JS自帶的 函數對象。(哎? 等等, Function 爲 函數對象 能夠理解,爲何 Object也是函數對象呢?帶着疑問咱們繼續往下看。 )javascript

Function 和 Object 爲什麼都是 函數對象呢?css

一. 普通對象 和 函數對象

  1. 函數對象 的疑惑 🤔
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
這裏咱們看到了一些區別,可是這又是爲何呢?

下面咱們看完普通對象再縱向的進行一下對比
  1. 什麼是普通對象? 🤔

咱們先想一想一下,建立對象的方式?html

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
  1. 直接縱向對比這 兩類 對象
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()建立的。html5

  • f1,f2,歸根到底都是經過 new Function()的方式進行建立的。java

    這麼來理解這句話呢?css3

function fns(a, b) {
    return a + b
}

// 等價於

var fns = new Function('a, b', 'return a + b')
  • 上面的代碼作了一個同目標的不一樣實現實現方法,那麼爲何兩種方案的結果都是相同的呢?git

    講到這裏就須要對 JavaScript 這門語言進行 分析了。github

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屬性,用來引用它的函數對象。這是一種循環引用

一、Person.prototype.constructor

ƒ (name) {
    this.name = name
    this.getName = function() {
        return this.name
    }
}

二、Function.prototype.constructor

ƒ Function() { [native code] }

三、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 有一個 深入的理解。

Github地址,歡迎 Star

相關文章
相關標籤/搜索