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