學習使用過js的人一開始都會覺得js簡單,這是因為js語法簡單,學習過編程語言的人,很容易掌握js的基本語法并按要求寫出一些邏輯代碼實現頁面交互,另一方面是js有好多的庫函數及插件,比如jquery,極大方便了我們的使用,然而要想寫出高質量的js顯得又那么力不從心,即便是看別人寫的一些插件或封裝的組件時,就有些摸不住頭腦,原因是,js涉及到函數,對象等的使用,在不了解或者了解不到位的情況下,很難做好對對象的使用。
JavaScript 不包含傳統的類繼承模型,而是使用 prototypal 原型模型。
雖然這經常被當作是 JavaScript 的缺點被提及,其實基于原型的繼承模型比傳統的類繼承還要強大。實現傳統的類繼承模型是很簡單,但是實現 JavaScript 中的原型繼承則要困難的多。
由于 JavaScript 是唯一一個被廣泛使用的基于原型繼承的語言,所以理解兩種繼承模式的差異是需要一定時間的,今天我們就來了解一下原型和原型鏈。
了解一些概念
在解析之前,你應該去在自己的腦海問幾個問題:
- 1、什么是原型?
- 2、什么是原型鏈?
- 3、prototype與proto有什么不同,有什么聯系?
- 4、constructor與上面兩個有什么聯系,怎么用?
如果今天能把上面這四個問題都解決和理解了,那你就真正了解了JS的原型和原型鏈。接下來,咱們就一個一個問題去解決。
什么是原型?
JavaScript 中,萬物皆對象!但對象也是有區別的。分為普通對象和函數對象,Object ,Function 是JS自帶的函數對象。每個對象都有原型(null和undefined除外),你可以把它理解為對象的默認屬性和方法。
你可以把下面的代碼在瀏覽器打印出來看一下。
- Object:Object是一個函數對象,Object的原型就是一個Object對象,它里面存在著一些對象的方法和屬性,例如最常見的toString方法。
- 新建對象:用new Object或者{}建的對象是普通對象,它沒有prototype屬性,只有proto屬性,它指向Object.prototype。
- Array: Array也是一個函數對象,它的原型就是Array.prototype,它里面存在著一些數組的方法和屬性,例如常見的push,pop等方法。
- Function:Function也是一個函數對象,但它有點特殊,它的原型就是一個function空函數。
- 自定義函數:它的原型就是你給它指定的那個東西。如果你不指定,那它的原型就是一個Object.prototype。
什么是原型鏈?
在JavaScript 中,每個對象都有一個指向它的原型(prototype)對象的內部鏈接。這個原型對象又有自己的原型,直到某個對象的原型為 null 為止(也就是不再有原型指向),組成這條鏈的最后一環。這種一級一級的鏈結構就稱為原型鏈(prototype chain)。
JavaScript 對象是動態的屬性“包”(指其自己的屬性)。JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依此層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
當你用new Object或者直接定義一個對象時,它的原型鏈就是:
o ==》 Object.prototype ==》 null
但你訪問o上沒有的屬性或方法時,JS會往Object.prototype上尋找該屬性和方法。如果有則直接返回,如果沒有,方法則報錯,這個方法未定義,屬性則返回undefined。
當你用構造函數(構造函數我們一般首字母大寫)建立一個對象時,它的原型鏈就是:
tsrot ==》 Person.prototype ==》 Object.prototype ==》 null
如果沒有定義Person.prototype這一環,則直接跳到下一環。
來點更復雜的。
當你需要父類的屬性和方法時,你可以把它的原型指向父類的原型。此時的原型鏈就是:
child ==》 Parent.prototype ==》 Object.prototype ==》 null
數組也是一個對象,不過它是由Array構造函數new而來的,所以它的原型鏈就是:
arr ==》 Array.prototype ==》 Object.prototype ==》 null
fun是一個函數對象,它是由Function構造函數new而來的,所以它的原型鏈就是:
fun ==》 Function.prototype ==》 Object.prototype ==》 null
fun它沒有name屬性,但是Function它有,所以這個name就是Function原型上的。
prototype與proto
在Javascript中,每個函數都有一個原型屬性prototype指向自身的原型,而由這個函數創建的對象也有一個proto屬性指向這個原型,而函數的原型是一個對象(函數點prototype也是一個普通對象,Function.prototype除外,它是函數對象,但它很特殊,他沒有prototype屬性),所以這個對象也會有一個proto指向自己的原型,這樣逐層深入直到Object對象的原型,這樣就形成了原型鏈。普通對象沒有prototype,但有proto屬性。
JS在創建對象(不論是普通對象還是函數對象)的時候,都有一個叫做proto的內置屬性,用于指向創建它的函數對象的原型對象prototype。
普通對象的proto
var o = {name:"tsrot"};
console.log(o.proto);
//Object{}
console.log(o.prototype);
//undefined
console.log(o.proto === Object.prototype);
//true
構造對象的proto
function Parent{
this.name = "i am parent";
}
Parent.prototype = {age:24};
function Child{
this.name = "i am child";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
var child = new Child;
console.log(child.proto); //Object{}
console.log(Child.prototype); //Object{}
console.log(child.proto === Child.prototype);
//true
console.log(Parent.prototype.proto ===
Object.prototype); //true
數組的proto
var arr = [1,2,3];
console.log(arr.proto);
//[Symbol(Symbol.unscopables): Object]
console.log(Array.prototype);
//[Symbol(Symbol.unscopables): Object]
console.log(arr.proto === Array.prototype); //true
函數的proto
var fun = function{
var hello = "i am function"
}
fun.prototype = {name:"tsrot"};
console.log(fun.prototype);
//Object {name: "tsrot"}
console.log(fun.proto);
//function{}
console.log(fun.prototype === fun.proto);
//false
console.log(fun.proto === Function.prototype);
//true