国产日韩欧美一区二区三区三州_亚洲少妇熟女av_久久久久亚洲av国产精品_波多野结衣网站一区二区_亚洲欧美色片在线91_国产亚洲精品精品国产优播av_日本一区二区三区波多野结衣 _久久国产av不卡

?

JavaScript繼承機(jī)制探討及其應(yīng)用

2017-11-13 03:03:12遠(yuǎn)
關(guān)鍵詞:指向原型實(shí)例

呂 遠(yuǎn)

(南京工業(yè)大學(xué) 信息服務(wù)部,南京 211800)

JavaScript繼承機(jī)制探討及其應(yīng)用

呂 遠(yuǎn)

(南京工業(yè)大學(xué) 信息服務(wù)部,南京 211800)

JavaScript語言作為一門弱類型語言,由于其函數(shù)的第一類對象(First Class Object)性,無論是采用函數(shù)式編程還是面向?qū)ο缶幊淌褂闷饋矶挤浅l`活。隨著近幾年JavaScript生態(tài)圈發(fā)展的火熱和不斷成熟,各種項(xiàng)目的編碼量和復(fù)雜度都在呈幾何級數(shù)增長,而JavaScript面向?qū)ο缶幊讨械睦^承機(jī)制在其中扮演的作用也越來越重要,但使用現(xiàn)狀卻非常不規(guī)范。詳細(xì)分析JavaScript的繼承核心機(jī)制,然后提供幾種繼承的實(shí)現(xiàn)方式,并分析各自相應(yīng)的優(yōu)缺點(diǎn)和使用場景;同時(shí)通過分析John Resig發(fā)布的其著名開源框架中的一段核心源碼,展示繼承機(jī)制在JavaScript框架級項(xiàng)目開發(fā)中的應(yīng)用。

JavaScript;繼承;原型鏈;解耦

0 引言

JavaScript做為一門弱類型語言,有些人認(rèn)為其并不是一門真正的面向?qū)ο笳Z言。這種說法的原因一般都是覺得JavaScript與類似Java或C#之類的強(qiáng)型語言的繼承方式有很大的區(qū)別,因而默認(rèn)它就是非主流的面向?qū)ο蠓绞?,甚至竟有很多書也將其描述為“非完全面向?qū)ο蟆闭Z言。但若深入研究一下其繼承機(jī)制便會發(fā)現(xiàn)JavaScript不僅是面向?qū)ο笳Z言的,而且其繼承方式也有著多種實(shí)現(xiàn)方式,相當(dāng)靈活。特別在ES6[1]規(guī)范發(fā)布之后,JavaScript作為面向?qū)ο蟮囊婚T語言的特征更加明顯,編碼習(xí)慣與傳統(tǒng)高級面向?qū)ο笳Z言Java、C++更加接近。

2007年Atwood在Blog中發(fā)文“The Principle of Least Power”,提出了著名的阿特伍德定律——所有能用JavaScript實(shí)現(xiàn)的終將會被JavaScript實(shí)現(xiàn)[2]。時(shí)至今日,該定律依然是正確的。當(dāng)下JavaScript語言已經(jīng)被應(yīng)用到了幾乎所有可以應(yīng)用的領(lǐng)域中,在web開發(fā)中的發(fā)展更是如日中天,圍繞JavaScript的技術(shù)生態(tài)圈也在不斷成熟和完善——NodeJs、Angular、React Native、MEAN[3]等等。此功能來看,JavaScript語言從最初簡單的動態(tài)網(wǎng)頁制作,到現(xiàn)在已經(jīng)可以拿來實(shí)現(xiàn)WebVR[4]、大數(shù)據(jù)分析、深度學(xué)習(xí)[5]等功能,甚至可以開發(fā)桌面級或系統(tǒng)級的應(yīng)用或App,其發(fā)展速度相當(dāng)之快。相應(yīng)地,關(guān)于JavaScript的繼承機(jī)制的探討,著名工程師和專家學(xué)者也一直在研究。2006年Douglas Crockford 提出了一種原型式繼承的方法[6],通過借助原型基于已有對象創(chuàng)建新對象,同時(shí)還不必創(chuàng)建自定義類型。2013年John Resig提出了另一種繼承方式[7],通過基于原型鏈并借助閉包實(shí)現(xiàn)了對象的繼承,并且可以通過super屬性直接與父級通信。2015年,ECMA International發(fā)布的ES5規(guī)范中采納了Crockford的方法,并將其進(jìn)一步規(guī)范化。在2016年發(fā)布的ES6規(guī)范中則進(jìn)一步采納了John Resig的方案,封裝了extend、super等關(guān)鍵字,使JavaScript的繼承機(jī)制更加完善。

在此背景下,JavaScript如今的角色已經(jīng)絕不僅僅是做一些簡單的交互、發(fā)送一些請求或者操作一些DOM,它更多的時(shí)候需要擔(dān)任類似于路由層和業(yè)務(wù)層的角色,邏輯和代碼的復(fù)雜度相應(yīng)也成指數(shù)級增長,比如現(xiàn)在僅僅一個(gè)很普通的單頁面應(yīng)用(SPA)的JavaScript代碼量也已經(jīng)超過萬行。同時(shí),對于桌面級應(yīng)用來說,JavaScript還需要做大量的邏輯性任務(wù),這里面就包括數(shù)據(jù)的高度抽象,而只有運(yùn)用面向?qū)ο蟮乃季S才能很好的對抽離數(shù)據(jù)進(jìn)行處理和維護(hù),JavaScript繼承機(jī)制的作用在這里就顯得舉足輕重。然而由于JavaScript的繼承相對其他語言比較特殊,實(shí)現(xiàn)起來方式也多樣,這就造成了不同的人在項(xiàng)目中對JavaScript繼承的不規(guī)范使用,這樣的后果在以前可能沒太大問題,但在當(dāng)下各種項(xiàng)目的JavaScript邏輯和代碼量都急劇增長的背景下,可能會造成很嚴(yán)重的Bug,帶來不可估量的損失。

因此針對這一問題,本文對JavaScript的繼承機(jī)制和實(shí)現(xiàn)方式都做了詳細(xì)闡述,并針對每一種實(shí)現(xiàn)方式指出其利弊所在,爭取對JavaScript的繼承機(jī)制做一個(gè)相對清晰的梳理。

1 JavaScript的繼承原理介紹

在Javascript中實(shí)現(xiàn)繼承的核心機(jī)制是原型鏈[8](Prototype Chain)。

一個(gè)構(gòu)造函數(shù)由構(gòu)造函數(shù)本身和構(gòu)造函數(shù)的原型對象(Prototype)兩部分組成,如圖1所示,每一個(gè)構(gòu)造函數(shù)還有一個(gè)隱藏的屬性_proto_,指向構(gòu)造函數(shù)的原型對象[9]。當(dāng)構(gòu)造函數(shù)被實(shí)例化時(shí),把構(gòu)造函數(shù)中的所有屬性和方法拷貝到實(shí)例當(dāng)中,同時(shí),這個(gè)_proto_屬性也會被拷貝,當(dāng)訪問實(shí)例對象當(dāng)中的屬性或方法時(shí),會先從實(shí)例本身找,如果找到了,就會使用實(shí)例中的這個(gè)屬性或方法;如果沒有找到,就會通過_proto_屬性中保存的地址找到原型對象,再從原型對象里找這個(gè)屬性或方法。

圖1構(gòu)造函數(shù)的構(gòu)成

當(dāng)給一個(gè)實(shí)例對象設(shè)置屬性值時(shí),會在實(shí)例對象中找有沒有這個(gè)屬性,如果找到,就直接把值賦給這個(gè)屬性;如果沒有的話,不會再去原型對象中找該屬性,因?yàn)镴avaScript語言是一種動態(tài)語言,這時(shí)會直接給這個(gè)實(shí)例對象添加一個(gè)新的屬性,即使原型對象中有這個(gè)屬性,也不會改變原型對象中的這個(gè)值。

當(dāng)實(shí)例對象中和原型對象中有一個(gè)同名的屬性和方法時(shí),此時(shí)只能訪問到實(shí)例對象中的屬性或方法(就近原則),如果一定要訪問,可以delete掉實(shí)例中的屬性或方法,或都直接加上_proto_屬性訪問。

以上就是原型鏈的核心原理和屬性搜索機(jī)制。當(dāng)JavaScript繼承時(shí),就是依賴對象的原型鏈一級級的追溯,通過將父一級的原型鏈嫁接到子一級的原型鏈,達(dá)到屬性和方法的繼承,具體示例如圖2所示。

標(biāo)注說明:

> Function.prototype是所有函數(shù)對象的原型(1.1標(biāo)記的箭頭)

> Object.prototype是所有原型對象的原型(1.2標(biāo)記的箭頭)

> 函數(shù)對象對應(yīng)的原型對象是其實(shí)例對象的原型(1.3標(biāo)記的箭頭)

> 任何函數(shù)對象都有一個(gè)prototype屬性指向?qū)?yīng)的原型對象,表示其實(shí)例對象的父對象(2標(biāo)記的箭頭)

> 任何原型對象都有一個(gè)constructor屬性指向?qū)?yīng)的函數(shù)對象(3標(biāo)記的箭頭)

這個(gè)過程有多種實(shí)現(xiàn)方式,各有利弊,下一節(jié)會詳細(xì)介紹。

圖2 JavaScript原型鏈層級示例

2 JavaScript繼承的實(shí)現(xiàn)方式

一般而言,可以將JavaScript的繼承可以分為兩大類:基于對象的繼承(Object Based),基于類型的繼承(Protype Based)。

2.1基于對象的繼承

基于對象的繼承也叫原型繼承。通過JavaScript字面量創(chuàng)建的對象都會連接到Object.prototype,因此可以用Object.prototype來實(shí)現(xiàn)繼承。在ES5[10]規(guī)范中可以用Object.create(),直接讓新對象繼承舊對象的屬性。例如:

var parent = {

name: "Adam",

say: function () { return this.name; }}

var Child= Object.create(parent);

console.log(Child.say()); // Adam

代碼很簡單,Parent有一個(gè)屬性和一個(gè)方法。對象Child通過Object.create()來繼承,其第一個(gè)參數(shù)prototype指向Parent的prototype,這樣對象Child就繼承了Parent的屬性和方法。

用Object.create()相當(dāng)于創(chuàng)建了一個(gè)全新的對象,可以給該對象任意新增,重載父類的屬性和方法:

var Parent = {

name: " Adam",

say: function () { return this.name; }}

var Child= Object.create(Parent);

Child.name = “Patrick”;

Child.say = function(){ return “Hi ” + this.name; };

console.log(Child.say()); // Hi Patrick

2.2基于類型的繼承

基于類型的繼承是通過構(gòu)造函數(shù)依賴于原型的繼承,而非依賴于對象。簡單來說,繼承方式如圖3所示。

圖3 JavaScript類型繼承

上圖中,A、B、C分別代表3個(gè)構(gòu)造函數(shù)的原型對象,藍(lán)色箭頭串接起來的所有對象就構(gòu)成了對象C的原型鏈,其中C的__proto__屬性指向B,B的__proto__屬性指向A,A的__proto__屬性可能指向更高層的對象,也可能指向null(表示A不繼承任何對象的屬性和方法)。這樣依據(jù)原型鏈的屬性搜索機(jī)制,A、B、C就可以逐級共享屬性和方法,從繼承的角度來看,A成為B的父類,B為C的父類。

該種繼承方式具體有以下幾種實(shí)現(xiàn)形式:

2.2.1傳統(tǒng)繼承方式

圖4 傳統(tǒng)繼承方式原型鏈

function Parent(n) {

this.name = n || 'Adam';}

Parent.prototype.say = function() {

return this.name;}

function Child(n) {}

Child.prototype = new Parent();

var c1 = new Child("Patrick");

console.log(c1.name); //Adam

c1.say(); // Adam

圖4為原型鏈,程序運(yùn)行結(jié)果均為Adam。這就是該模式的缺點(diǎn),即無法將子構(gòu)造函數(shù)的參數(shù)給父構(gòu)造函數(shù)。這個(gè)缺點(diǎn)很致命,因此通常我們不用該模式。

2.2.2借用構(gòu)造函數(shù)繼承方式

該方法解決了2.2.1中無法通過子構(gòu)造函數(shù)傳遞參數(shù)給父構(gòu)造函數(shù)的問題。

function Parent(n) {

this.name = n || 'Adam';}

Parent.prototype.say = function() {

return this.name;}

function Child(n) {

Parent.apply(this, arguments);}

var c2 = new Child("Patrick");

console.log(c2.name); // Patrick

c2.say(); //error

圖5 借用構(gòu)造函數(shù)繼承方式原型鏈

結(jié)果看出Child的參數(shù)順利傳入了,但say方法會報(bào)未定義的錯(cuò)。原因就是該模式并沒有將prototype指向Parent,只不過借用了一下Parent的實(shí)現(xiàn)。因此看似是繼承,其實(shí)不然,從原型鏈角度來看,兩者毫無關(guān)系。Child的實(shí)例對象里自然就沒有Parent原型中的say方法。

原型鏈關(guān)系如圖5所示。

2.2.3組合繼承方式

本模式是上面兩種方式的結(jié)合體,借鑒了它們的特點(diǎn):

function Parent(n) {

this.name = n || 'Adam';}

Parent.prototype.say = function() {

return this.name;}

function Child(name) {

Parent.apply(this, arguments);}

Child.prototype = new Parent();

var c4 = new Child("Patrick");

console.log(c4.name); // Patrick

console.log(c4.say()); // Patrick

delete c4.name;

console.log(c4.say()); // Adam

圖6 組合繼承方式原型鏈

該方式原型鏈如圖6所示。該模式通常用用就可以了,但不是完美的。缺點(diǎn)和模式二的缺點(diǎn)二一樣,多個(gè)子對象都會重復(fù)地創(chuàng)建父對象,效率不高。另外從例子的結(jié)果和圖中都可以看出,有兩個(gè)name屬性,一個(gè)在對象中,一個(gè)在原型對象中。當(dāng)delete子對象中的name后,原型對象中的name會顯現(xiàn)出來,這可能會出bug。

2.2.4寄生組合繼承方式

該種方式可以有效解決上面幾種繼承方式存在的不足。

function Parent(n) {

this.name = n || 'Adam';}

Parent.prototype.say = function() {

return this.name;}

function Child(n) {

Parent.apply(this,arguments);}

Child.prototype = Object.create(Parent.prototype);

var c6 = new Child("Patrick");

console.log(c6.name); // Patrick

console.log(c6.say()); // Patrick

delete c6.name;

console.log(c6.say()); //error

圖7 寄生組合繼承方式原型鏈

圖7所示的繼承方式比較好,既有效率,還能實(shí)現(xiàn)父子解耦,是目前項(xiàng)目生產(chǎn)環(huán)境和各大著名開源框架(Nodejs、Angularjs等)中使用最廣泛的一種繼承方式。由于Child對象的原型對象被重寫,所以其constructor屬性現(xiàn)在指向了Parent,所以需要對其進(jìn)行修正,又考慮到constructor屬性不可枚舉的特性,在兼容 ECMAScript 5 的 JavaScript 引擎中,可以使用defineProperty方法,即:

Object.defineProperty(Child.prototype, "constructor", {

enumerable: false,

value: Child});

這樣代碼就比較完美了。

2.2.5 ECMAScript 6繼承方式

class Parent {

constructor(n)?{

this.name = n || 'Adam';}

say(){

return this.name;}}

Class Child extends Parent {

constructor(n) {

super(n);}

say() {

return 'Hi ' + super.say();}}

var c7 = new Child("Patrick");

console.log(c7.name); // Patrick

console.log(c7.say()); //Hi Patrick

可以看出,ES6規(guī)范中默認(rèn)封裝了extends、class、super等關(guān)鍵字,其內(nèi)部的實(shí)現(xiàn)原理其實(shí)就是原型鏈繼承,不過用這種繼承方式更加直觀。在大型項(xiàng)目中強(qiáng)烈推薦使用該繼承方式,但鑒于瀏覽器兼容性問題,需要在使用過程中配合利用Babel和Gulp插件向前兼容,以便將ES6代碼轉(zhuǎn)換為可兼容的代碼。

3 應(yīng)用舉例

實(shí)際上,在項(xiàng)目生產(chǎn)環(huán)境中用到JavaScript繼承機(jī)制的地方非常多,無論是在傳統(tǒng)客戶端(瀏覽器或Hybrid App)開發(fā)中,還是在服務(wù)器端進(jìn)行模塊式開發(fā)的時(shí)候,它都幾乎無處不在。尤其是在工程量比較大的時(shí)候,JavaScript的繼承機(jī)制對于代碼的可維護(hù)性和高復(fù)用性方面更顯得尤為重要。

這里舉一個(gè)著名開源框架JQuery 之父John Resig寫的實(shí)現(xiàn)JavaScript模擬class繼承功能的一段代碼7,雖然利用當(dāng)下ES6規(guī)范可以寫的更簡潔,但我們著重看一下其中關(guān)于繼承的實(shí)現(xiàn)思路。

(function(){

var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /_super/ : /.*/;

this.Class = function(){};

Class.extend = function(prop) {

var _super = this.prototype;

initializing = true; //A

var prototype = new this();

initializing = false;

for (var name in prop) {

prototype[name] = typeof prop[name] == "function" &&

typeof _super[name] == "function" && fnTest.test(prop[name]) ?

(function(name, fn){

return function() {

var tmp = this._super; //B

this._super = _super[name]; //C

var ret = fn.apply(this, arguments);

this._super = tmp;

return ret;

};

})(name, prop[name]) :

prop[name];

}

function Class() {

if ( !initializing && this.init )

this.init.apply(this, arguments);

}

Class.prototype = prototype; //D

Class.prototype.constructor = Class; //E

Class.extend = arguments.callee; //F

return Class;

};

})();

這段代碼的實(shí)現(xiàn)過程非常巧妙,核心在其粗體部分。 其中D、E、F部分即是我們前面提到的原型鏈繼承的實(shí)現(xiàn),只不過將其封裝在extend函數(shù)中;B、C部分John Resig為每一個(gè)實(shí)例對象添加了一個(gè)super屬性,可以直接訪問父級對應(yīng)的屬性或方法;而A之所以增加initializing這個(gè)開關(guān)變量,其實(shí)是為了控制實(shí)例對象的初始化邏輯,將直接實(shí)例化過程和調(diào)用extend方法的過程區(qū)別對待。這樣一方面完全實(shí)現(xiàn)了對象繼承的功能,另外通過引入super屬性減少了屬性在原型鏈中的追溯查找過程,提高了性能,同時(shí)使用起來編碼習(xí)慣與其他高級編程語言更接近。

以下是其使用方法:

var Parent = Class.extend({

init: function(name){

this.name = name;

},

Say: function(name){

return this.name;

}

});

var Child = Parent.extend({

init: function(name){

this._super(name);

},

Say: function(name){

return ‘Hi ’ + this.name;

}

});

var Parent = new Person('Adam');

Parent.Say(); //'Adam'

var Child = new Child('Patrick');

Parent.Say(); //'Hi Patrick'

4 結(jié)論

以上詳細(xì)闡述了JavaScript按對象繼承和按類型繼承的幾種繼承的實(shí)現(xiàn)方式,并分析了John Resig開源框架中的一段核心代碼,可以看出JavaScript中實(shí)現(xiàn)繼承是十分靈活多樣的,特別是在框架級項(xiàng)目的開發(fā)過程中是必不可少的,其對于代碼的優(yōu)化、性能的提升和后期維護(hù)都有重要作用。

從本質(zhì)上講,JavaScript的各種繼承方式并沒有好壞之分,只是它們分別適用于不同的場景之中,需要使用者揚(yáng)長避短,根據(jù)特定的需求采用相應(yīng)的實(shí)現(xiàn)形式即可。而更最重要的是要求使用者深入理解JavaScript中實(shí)現(xiàn)繼承的機(jī)制和原理,也就是原型(Protype)和原型鏈(Protype Chain)等問題,并且知道各種繼承方式的優(yōu)缺點(diǎn),只有這樣才能在項(xiàng)目中將JavaScript的繼承機(jī)制發(fā)揮到最大效用。

[1] Allen Wirfs-Brock, Brian Terlson. [EB/OL].[2016-06-10].https://tc39.github.io/ecma262/.

[2] Jeff Atwood. [EB/OL].[2007-07-17].https://blog.codinghorror.com/the-principle-of-least-power/.

[3] Valeri Karpov.[EB/OL].[2013-04-30].https://www.mongodb.com/blog/post/the-mean-stack-mongodb-expressjs-angularjs-and.

[4] Mozilla Foundation.[EB/OL].[2017-08-07].https://developer.mozilla.org/en-US/docs/Web/API/WebVR_API.

[5] Andrej.[EB/OL]. [2016-12-01]. http://cs.stanford.edu/people/karpathy/convnetjs/docs.html.

[6] Douglas Crockford. JavaScript The Good Parts [M].南京:東南大學(xué)出版社,2009.

[7] John Resig. Secrets of the JavaScript Ninja [M].New York: Manning Publications,2013.

[8] Nicholas C.Zakas. JavaScript高級程序設(shè)計(jì)[M].李 松峰,曹力,譯.北京:人民郵電出版社,2006.

[9] David Flanagan. JavaScript權(quán)威指南(第6版)[M].李強(qiáng),譯.北京:機(jī)械工業(yè)出版社,2007.

[10] Ecma International. [EB/OL].[2011-06-13].http://www.ecma-international.org/ecma-262/5.1/index.html.

AnalysisandApplicationofObject-OrientedInheritanceofJavaScript

LV Yuan

(Department of Information Service, Nanjing Tech University, Nanjing 211800, China)

JavaScript is a weak typed language. With the rapid development of the JavaScript ecosphere in recent years, it has become more and more mature, both the JavaScript coding amount and the complexity of the project increase exponentially, the inheritance mechanism of JavaScript object-oriented programming is playing a more and more important role in it, and however, it is still used very casually. This paper analyzes the core mechanism of JavaScript inheritance and reveals several ways of inheritance implementation as well as the corresponding advantages and disadvantages. The practical effect is eventually demonstrated through the analysis of the core source code of the well-known open source JavaScript framework developed by John Resig.

JavaScript; inheritance; prototype chain; decouple

TP312JA

A

1009-7961(2017)05-0013-06

2017-03-25

呂遠(yuǎn)(1988-),男,河南漯河人,助理館員,碩士,主要從事web開發(fā)、大數(shù)據(jù)技術(shù)以及數(shù)字資源開發(fā)與建設(shè)研究。

(責(zé)任編輯:孫文彬)

猜你喜歡
指向原型實(shí)例
科學(xué)備考新指向——不等式選講篇
包裹的一切
《哈姆雷特》的《圣經(jīng)》敘事原型考證
把準(zhǔn)方向盤 握緊指向燈 走好創(chuàng)新路
傳媒評論(2017年8期)2017-11-08 01:47:36
論《西藏隱秘歲月》的原型復(fù)現(xiàn)
原型理論分析“門”
人間(2015年20期)2016-01-04 12:47:08
完形填空Ⅱ
完形填空Ⅰ
閱讀(中年級)(2006年1期)2006-01-17 08:29:56
达日县| 贞丰县| 卓资县| 通渭县| 昌都县| 海阳市| 鹰潭市| 象山县| 棋牌| 大英县| 五原县| 德庆县| 新野县| 牡丹江市| 西平县| 宁乡县| 西畴县| 腾冲县| 眉山市| 南漳县| 德兴市| 白山市| 平山县| 比如县| 竹山县| 库伦旗| 江川县| 平原县| 左权县| 南阳市| 科技| 阳山县| 涞源县| 元谋县| 张家口市| 崇阳县| 合阳县| 延庆县| 体育| 江川县| 东平县|