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


05:JS 原型鏈

在 JavaScript 的世界中,萬物皆對象! 但是這各種各樣的對象其實具體來划分的話就 2 種。 一種是 函數對象,剩下的就是 普通對象。其中 FunctionObject 為JS自帶的 函數對象。(哎? 等等, Function 為 函數對象 可以理解,為什么 Object 也是函數對象呢?帶着疑問我們繼續往下看。 )

Function 和 Object 為何都是 函數對象呢?

一. 普通對象 和 函數對象

  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. 什么是普通對象? 🤔

我們先想想一下,創建對象的方式?

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()創建的。

  • 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 有一個 深刻的理解。

Github地址,歡迎 Star


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM