最近組內(nèi)的童鞋和我說(shuō)的自己一直搞不太清楚js原型這一塊的東西,我想了想覺(jué)得這東西也不是一兩句話就能解釋清楚的,所以我決定來(lái)解釋解釋js中的原型機(jī)制,希望也能幫到你。
本文將會(huì)介紹面向?qū)ο?,繼承,原型等相關(guān)知識(shí),涉及的知識(shí)點(diǎn)如下:
最近學(xué)習(xí)了下python,還寫了篇博文《重拾編程樂(lè)趣——我的Python筆記》,加深了我對(duì)面向?qū)ο蟮囊恍├斫狻?/p>
我們會(huì)對(duì)我們寫的程序進(jìn)行抽象,而不同的語(yǔ)言都提供了不同的抽象工具,比如各種語(yǔ)言里面的數(shù)組,集合(鍵值數(shù)組,哈希表,字典等)等提供了對(duì)數(shù)據(jù)的抽象;而VB里面的子程序,類C語(yǔ)言里面的函數(shù),提供了抽象代碼段的能力。
有時(shí)我們希望將數(shù)據(jù)和對(duì)數(shù)據(jù)的操作封裝到一起,這被稱作對(duì)象,是一種更高唯獨(dú)的抽象工具,而這種抽象工具——對(duì)象可以對(duì)現(xiàn)實(shí)世界進(jìn)行建模。
現(xiàn)實(shí)世界中的事物都有一些聯(lián)系,比如我們可以抽象出來(lái)貓,然后又公貓,母貓,顯然公貓應(yīng)該擁有貓的特性,這也就是繼承,細(xì)分的事物應(yīng)該有高緯度事物的特點(diǎn)。
想要實(shí)現(xiàn)對(duì)象和繼承這套思維,目前有兩種實(shí)現(xiàn)方法,分別是CEOC和OLOO。
CEOC(class extend other class)是一套基于類和實(shí)例的實(shí)現(xiàn)方式,這種方式實(shí)用的比較廣泛,大部分流星的面向?qū)ο笳Z(yǔ)言都在使用,比如java,python等。
類作為對(duì)象的抽象描述,對(duì)象是類的實(shí)例,也稱作類的泛化,泛化和抽象對(duì)應(yīng)。最恰當(dāng)?shù)念惐染褪巧w房子了,類就相當(dāng)于房子的圖紙,圖紙是對(duì)房子屬性和功能的描述,根據(jù)圖紙可以蓋很多個(gè)類似的房子;另一種類比就是磨具和零件,磨具相當(dāng)于類,零件相當(dāng)于對(duì)象,通過(guò)磨具我們可以造出來(lái)很多零件。
其實(shí)與其說(shuō)是面向?qū)ο筮€不如說(shuō)是面向類編程,在這種機(jī)制中解決上面提到的繼承邏輯時(shí),是在類上實(shí)現(xiàn)的,子類可以繼承父類。
在類的機(jī)制中更像是對(duì)工廠里通過(guò)磨具造零件機(jī)制的模擬,而非動(dòng)物世界這種繁衍繼承機(jī)制的模擬,下面介紹的OLOO則是對(duì)世界更好的模擬。
OLOO(object link other object)是一套基于對(duì)象和原型的實(shí)現(xiàn)方式,這種方式唯一實(shí)現(xiàn)廣泛語(yǔ)言就是js了,熟悉類機(jī)制的同學(xué),初次接觸這個(gè),可能會(huì)覺(jué)得不是很好理解,而很多前端同學(xué)理解的也不是很明白,之前寫過(guò)一篇原型的文章,推薦大家閱讀《JavaScript原型之路》。
其實(shí)面向類的機(jī)制有些多此一舉了,因?yàn)樽詈笫褂玫氖菍?duì)象,而不是類,那么我們直接讓對(duì)象可以集成對(duì)象不就行了,在這種機(jī)制中可以通過(guò)某種操作讓對(duì)象和對(duì)象之間可以建立繼承的關(guān)系,當(dāng)繼承的對(duì)象(子對(duì)象)中沒(méi)有某些屬性時(shí)可以去被繼承的對(duì)象(父對(duì)象)中去查找。
在OLOO中,父對(duì)象也可以成為子對(duì)象的原型,這有些類似我們的人類的繁衍,每一個(gè)人都是一個(gè)對(duì)象,都是父母所生,而不是用模版建造出來(lái)的,每一個(gè)人都有一個(gè)父親,孩子和父親之間有一種特殊的關(guān)系,成為父子。
來(lái)說(shuō)說(shuō)JS中的對(duì)象機(jī)制,JS中的對(duì)象顯得有些臃腫,JS中的對(duì)象承接了兩個(gè)功能,一是面向?qū)ο髾C(jī)制中的對(duì)象,另一個(gè)是數(shù)據(jù)抽象中的集合——其他語(yǔ)言中稱為鍵值數(shù)組,哈希表或字典。
其實(shí)在其它語(yǔ)言中的對(duì)象都不耦合集合的功能,比如python中有字典,php中有鍵值數(shù)組,好在es2015中引入新的map類型,來(lái)把這個(gè)功能解耦出去,但我相信很多人都習(xí)慣這么使用了o(╯□╰)o
而本文所說(shuō)的對(duì)象不包括后面的這一部分功能,特指js中面向?qū)ο髾C(jī)制中的對(duì)象。
js語(yǔ)言是基于原型的語(yǔ)言,在js中幾乎一切都是對(duì)象,每個(gè)對(duì)象都有原型,而原型也是一個(gè)對(duì)象,也有自己的原型,從而形成原型鏈。
當(dāng)試圖訪問(wèn)一個(gè)對(duì)象的屬性時(shí),它不僅僅在該對(duì)象上搜尋,還會(huì)搜尋該對(duì)象的原型,以及該對(duì)象的原型的原型,依此層層向上搜索,直到找到一個(gè)名字匹配的屬性或到達(dá)原型鏈的末尾。
js中提到原型可能有不同的意思,我們得先區(qū)分清楚,需要清除到底是哪個(gè)原型:
第一點(diǎn)中的prototype是一個(gè)概念,一種機(jī)制,而不是具體的什么東西。
第二點(diǎn)中的prototype是說(shuō)每個(gè)對(duì)象都有自己的原型對(duì)象(父對(duì)象),下面我們用[[Prototype]]來(lái)指代這個(gè)原型對(duì)象。
第三點(diǎn)和第二點(diǎn)很容易混淆,每個(gè)函數(shù)都有一個(gè)原型屬性,我們用prototype來(lái)指代。
es5帶來(lái)了查看對(duì)象原型的方法——Object.getPrototypeOf,該方法返回指定對(duì)象的原型(也就是該對(duì)象內(nèi)部屬性[[Prototype]]的值)。
console.log(Object.getPrototypeOf({}))
>>> Object.prototype
es6帶來(lái)了另一種查看對(duì)象原型的方法——Object.prototype.__proto__,一個(gè)對(duì)象的__proto__ 屬性和自己的內(nèi)部屬性[[Prototype]]指向一個(gè)相同的值 (通常稱這個(gè)值為原型),原型的值可以是一個(gè)對(duì)象值也可以是null(比如說(shuō)Object.prototype.__proto__的值就是null)。
({}).__proto__
>>> Object.prototype
下面舉個(gè)例子來(lái)說(shuō)說(shuō)原型與原型鏈,以及訪問(wèn)對(duì)象屬性的時(shí)候會(huì)發(fā)生的行為:
// a ---> b 代表b是a的原型
在js中創(chuàng)建和修改原型的方法有很多,下面一一列舉出來(lái)。
在下面的例子中我們將對(duì)象a的[[Prototype]]指向b。
// a ---> b
這是最容易被大家忽略的方法,在js中你是繞不過(guò)原型的,不經(jīng)意間就創(chuàng)建了原型
var o = {a: 1};
// o ---> Object.prototype ---> null
var a = [];
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){}
// f ---> Function.prototype ---> Object.prototype ---> null
這種方法無(wú)法讓a的[[Prototype]]指向b。
構(gòu)造函數(shù)就是一個(gè)普通的函數(shù),只不過(guò)這次不是直接調(diào)用函數(shù),而是在函數(shù)前加上new關(guān)鍵字。
每個(gè)函數(shù)都有一個(gè)prototype屬性,通過(guò)new關(guān)鍵字新建的對(duì)象的原型會(huì)指向構(gòu)造函數(shù)的prototype屬性,所以我們可以修改構(gòu)造函數(shù)的prototype屬性從而達(dá)到操作對(duì)象原型的目的。
為了讓b繼承a,需要有一個(gè)構(gòu)造函數(shù)A
var b = {};
function A() {};
A.prototype = b;
var a = new A();
Object.getPrototypeOf(a) === b;
// true
// a ---> A.prototype === b
ES5帶來(lái)了Object.create接口,可以讓我們直接設(shè)置一個(gè)對(duì)象原型
var b = {};
var a = Object.create(b);
Object.getPrototypeOf(a) === b;
// true
// a ---> b
ES6帶來(lái)了另一個(gè)接口,可以繞過(guò)創(chuàng)建對(duì)象的過(guò)程,直接操作原型
var a = {};
var b = {};
Object.setPrototypeOf(a, b);
Object.getPrototypeOf(a) === b;
// true
// a ---> b
ES6還帶來(lái)了一個(gè)屬性,通過(guò)這個(gè)屬性也可以直接操作原型
var a = {};
var b = {};
a.__proto__ = b;
Object.getPrototypeOf(a) === b;
// true
// a ---> b
注意這個(gè)屬性在ES6規(guī)范的附錄中,也就意味著不是所有的環(huán)境都會(huì)有這個(gè)屬性。
ES6引入了以class語(yǔ)法糖,通過(guò)extends關(guān)鍵字我們也可以實(shí)現(xiàn)繼承,但是無(wú)法直接操作對(duì)象的原型,而是要借助“類”,其實(shí)就是構(gòu)造函數(shù)和函數(shù)的prototype屬性。
class B {}
class A extends B {}
var a = new A();
Object.getPrototypeOf(a) === A.prototype;
// true
// a ---> A.prototype === B的實(shí)例
不知道看完文章你理解原型了嗎?如果還有疑惑建議你閱讀下面的文章。
更多建議: