捕風(fēng)之巢

          統(tǒng)計(jì)

          留言簿(3)

          java友情鏈接

          閱讀排行榜

          評(píng)論排行榜

          javascript中子類調(diào)用父類方法解決方案

          幾乎每位在開(kāi)發(fā)JavaScript時(shí)嘗試應(yīng)用面向?qū)ο蠹夹g(shù)的開(kāi)發(fā)者,或多或少都會(huì)問(wèn)自己一個(gè)問(wèn)題:“如何調(diào)用父類(super class)的方法?”在Ajax技術(shù)還沒(méi)有目前這樣炙手可熱之前,這種問(wèn)題很少出現(xiàn),因?yàn)榇蠖鄶?shù)開(kāi)發(fā)者僅在進(jìn)行客戶端form驗(yàn)證或者簡(jiǎn)單的 DHTML/DOM操作時(shí)使用JavaScript。在那些簡(jiǎn)單的解決方案中,函數(shù)式編程(functional programming)是很有意義的,面向?qū)ο缶幊虅t處在次之重要的位置。現(xiàn)在,Ajax技術(shù)發(fā)展勢(shì)頭迅猛,開(kāi)發(fā)者已經(jīng)建立了一個(gè)調(diào)用大量客戶端JavaScript、不斷增長(zhǎng)的、復(fù)雜的系統(tǒng)。因此,在JavaScript上嘗試OO技術(shù)便成為了管理復(fù)雜性的一種手段。在此過(guò)程中,多數(shù)開(kāi)發(fā)者很快便認(rèn)識(shí)到:JavaScript 是一種原型化的(prototypical)語(yǔ)言,它缺少OO自身帶來(lái)的多種便利。

          ????????OO設(shè)計(jì)的主旨和關(guān)于它的一些話題談起來(lái)很大,但只著眼于Class的定義方式,我認(rèn)為它是JavaScript開(kāi)發(fā)者嘗試解決問(wèn)題的首選。因此,你可以在互聯(lián)網(wǎng)上找到許多不同的問(wèn)題解決案例,但在我看過(guò)它們后不免有些失望——這些案例都是在某個(gè)場(chǎng)合下適用,而不是放之四海而皆準(zhǔn)的通法。而我對(duì)這個(gè)話題的興趣來(lái)自于我的team在開(kāi)發(fā)ThinWire Ajax Framework的影響。由于這個(gè)框架生成出對(duì)客戶端代碼的需求,才使我們“被迫”去實(shí)現(xiàn)可靠的、支持父類方法調(diào)用的OO模式。通過(guò)父類調(diào)用,你可以進(jìn)一步依靠類的繼承特性來(lái)核心化通用代碼,從而更易于減少重復(fù)代碼,去掉客戶端代碼的壞味道。

          ????????下面羅列出了一些在我的研究過(guò)程中遇到的解決方式。最終,我沒(méi)有從中找出一個(gè)可以接收的解決方案,于是我不得不實(shí)現(xiàn)一個(gè)自己的解決方案,你將在本文的結(jié)尾部分看到這個(gè)方案。

          ????????然而父類調(diào)用在這里是最重要的OO機(jī)制,因此我需要一個(gè)相應(yīng)的工作模式,也正是因?yàn)樵谖业挠^點(diǎn)中原型化方式是丑陋的,所以我更需要一種更加自然地使用JavaScript定義類的方法。

          More Solutions:

          ????????好吧,讓我們進(jìn)入討論。正如開(kāi)發(fā)者所察覺(jué)的那樣,在JS中實(shí)現(xiàn)基本的繼承是很容易的事,事實(shí)上有一些眾所周知的方法:

          丑陋的Solution:

          ????????沒(méi)有進(jìn)行父類調(diào)用的簡(jiǎn)單繼承:

          // 提前寫好的JavaScript Class定義和繼承
          // 當(dāng)然,這種代碼很丑陋,散發(fā)著代碼的壞味道。
          function BaseClass() {
          ????//BaseClass constructor code goes here
          }

          BaseClass.prototype.getName = function() {
          ????return "BaseClass";
          }

          function SubClass() {
          ????//SubClass constructor code goes here
          }

          //Inherit the methods of BaseClass
          SubClass.prototype = new BaseClass();

          //Override the parent's getName method
          SubClass.prototype.getName = function() {
          ????return "SubClass";
          }

          //Alerts "SubClass"
          alert(new SubClass().getName());


          導(dǎo)致IE內(nèi)存泄露的Solution:

          ????????這種實(shí)現(xiàn)方式能夠?qū)е略贗E中的內(nèi)存泄漏,你應(yīng)該盡量避免:

          // 運(yùn)行時(shí)的JavaScript Class 定義和繼承
          // 看上去很傳統(tǒng),但這些腳本會(huì)導(dǎo)致在Internet Explorer中的內(nèi)存泄漏.
          function BaseClass() {
          ????this.getName = function() {
          ????????return "BaseClass";
          ????};????

          ????//BaseClass constructor code goes here
          }

          function SubClass() {
          ????//在對(duì)象實(shí)例建立時(shí)重載父類的getName方法
          ????this.getName = function() {
          ????????return "SubClass";
          ????}

          ????//SubClass constructor code goes here
          }

          //Inherit the methods of BaseClass
          SubClass.prototype = new BaseClass();

          //Alerts "SubClass"
          alert(new SubClass().getName());
          ????????

          ????????就像我在第一個(gè)實(shí)現(xiàn)方法中所注釋的那樣,第一個(gè)實(shí)現(xiàn)方法有些丑陋,但它相比引起內(nèi)存泄漏的第二種方式便是首選了。

          ????????我把這兩種方法放在這里的目的是指出你不應(yīng)該使用它們。

          硬性編碼的Solution:

          ????????讓我們看一下第一個(gè)例子,它采用了標(biāo)準(zhǔn)的原型化方式,但問(wèn)題是:它的子類方法如何調(diào)用父類(基類)方法?下面是一些開(kāi)發(fā)者嘗試并采用的方式:

          ????????一種企圖進(jìn)行父類調(diào)用的“通病”:

          function BaseClass() { }
          BaseClass.prototype.getName = function() {
          ????return "BaseClass(" + this.getId() + ")";
          }

          BaseClass.prototype.getId = function() {
          ????return 1;
          }

          function SubClass() {}
          SubClass.prototype = new BaseClass();
          SubClass.prototype.getName = function() {
          ????//調(diào)用父類的getName()方法
          ????//哈哈,這是對(duì)父類調(diào)用的直接引用嗎?
          ????return "SubClass(" + this.getId() + ") extends " +
          ????????BaseClass.prototype.getName();
          }

          SubClass.prototype.getId = function() {
          ????return 2;
          }

          //輸出結(jié)果:"SubClass(2) extends BaseClass(1)";
          //這是正確的輸出嗎?
          alert(new SubClass().getName());


          ???????? 上面的代碼是對(duì)第一段腳步進(jìn)行修改后的版本,我去掉了一些注釋和空格,使你能注意到新的getId()方法和對(duì)父類的調(diào)用。你一定急于知道通過(guò)這樣對(duì) BaseClass的硬性編碼引用(hard coded reference),它是否能進(jìn)行正確地調(diào)用BaseClass的方法?

          ???????? 一個(gè)正確的、多態(tài)的父類調(diào)用必做的事情是保證“this”引用指向當(dāng)前對(duì)象實(shí)例和類方法。在這里,看上去和它應(yīng)該輸出的結(jié)果非常接近,看上去好像在 SubClass中調(diào)用了BaseClass的getName()方法。你發(fā)現(xiàn)問(wèn)題了嗎?這個(gè)問(wèn)題是非常細(xì)小的,但卻很重要決不能忽視。通過(guò)使用上面的父類調(diào)用語(yǔ)法,BaseClass的getName()方法被調(diào)用,它返回一個(gè)字符串:包括類名和“this.getId()”的返回值。問(wèn)題在于 “this.getId()”應(yīng)該返回2,而不是1。如果這和你所想的不同,你可以查看Java或者C#這類OO語(yǔ)言的多態(tài)性。

          改進(jìn)后的硬性編碼Solution:

          ????????你可以通過(guò)一個(gè)微小的改動(dòng)來(lái)解決這個(gè)問(wèn)題。

          靜態(tài)(硬編碼)父類調(diào)用:

          function BaseClass() { }
          BaseClass.prototype.getName = function() {
          ????return "BaseClass(" + this.getId() + ")";
          }

          BaseClass.prototype.getId = function() {
          ????return 1;
          }

          function SubClass() {}
          SubClass.prototype = new BaseClass();
          SubClass.prototype.getName = function() {
          ????//一點(diǎn)魔法加上多態(tài)性!
          ????//但很明顯,這還是一個(gè)直接引用!????
          ????return "SubClass(" + this.getId() + ") extends " +
          ????????BaseClass.prototype.getName.call(this);
          }

          SubClass.prototype.getId = function() {
          ????return 2;
          }

          //輸出結(jié)果:"SubClass(2) extends BaseClass(2)";
          //Hey, 我們得到了正確的輸出!
          alert(new SubClass().getName());


          ????????在ECMA-262 JavaScript/EcmaScript標(biāo)準(zhǔn)中, Call()方法是所有Function實(shí)例的一個(gè)成員方法,這已經(jīng)被所有的主流瀏覽器所支持。JavaScript把所有的function看作對(duì)象,因此每個(gè)function都具有方法和附著其上的屬性。Call()方法允許你調(diào)用某個(gè)function,并在function的調(diào)用過(guò)程中確定 “this”變量應(yīng)該是什么。JavaScript的function沒(méi)有被緊緊地綁定到它所在的對(duì)象上,所以如果你沒(méi)有顯式地使用call()方法, “this”變量將成為function所在的對(duì)象。

          ????????另外一種方法是使用apply方法,它和call()方法類似,只在參數(shù)上存在不同:apply()方法接受參數(shù)的數(shù)組,而call()方法接受單個(gè)參數(shù)。

          Douglas Crockford的Solution:

          ????????現(xiàn)在回溯到上面的示例,在這個(gè)示例中唯一的問(wèn)題就是父類引用是直接的、硬性編寫的。它可以適用于小型的類繼承環(huán)境,但對(duì)于具有較深層次的大型繼承來(lái)講,這些直接引用非常難于維護(hù)。

          ????????那么,有解決方法嗎?不幸的是這里沒(méi)有簡(jiǎn)單的解決方案。

          ????????JavaScript沒(méi)有提供對(duì)通過(guò)“隱性引用”方式調(diào)用父類方法的支持,這里也沒(méi)有在其它OO語(yǔ)言中使用的“super”變量的等價(jià)物。于是,一些開(kāi)發(fā)者做出了自己的解決方案,但就像我前面提到的那樣,每個(gè)解決方案都存在某種缺點(diǎn)。

          ????????例如,下面列出的眾多著名方法之一:JavaScript大師[ur=http://en.wikipedia.org/wiki/Douglas_Crockford]Douglas Crockford[/url]在他的《Classical Inheritance in JavaScript》中提出的方法。

          ????????Douglas Crockford的方法在多數(shù)情況下可以正常工作:

          一次性支持代碼:

          //Crockford的方法:給所有的function都增加'inherits' 方法、
          //每個(gè)類都增加了'uber'方法來(lái)調(diào)用父類方法
          Function.prototype.inherits = function(parent) {
          ????var d = 0, p = (this.prototype = new parent());
          ????
          ????this.prototype.uber = function(name) {
          ????????var f, r, t = d, v = parent.prototype;
          ????????if (t) {
          ????????????while (t) {
          ????????????????v = v.constructor.prototype;
          ????????????????t -= 1;
          ????????????}
          ????????????f = v[name];
          ????????} else {
          ????????????f = p[name];
          ????????????if (f == this[name]) {
          ????????????????f = v[name];
          ????????????}
          ????????}
          ????????d += 1;
          ????????r = f.apply(this, Array.prototype.slice.apply(arguments, [1]));
          ????????d -= 1;
          ????????return r;
          ????};
          };


          運(yùn)行示例:

          function BaseClass() { }
          BaseClass.prototype.getName = function() {
          ????return "BaseClass(" + this.getId() + ")";
          }

          BaseClass.prototype.getId = function() {
          ????return 1;
          }

          function SubClass() {}
          SubClass.inherits(BaseClass);
          SubClass.prototype.getName = function() {
          ????//這里看上去非常的清晰,它調(diào)用了BaseClass的getName()方法
          return "SubClass(" + this.getId() + ") extends " +
          ????????this.uber("getName");
          }

          SubClass.prototype.getId = function() {
          ????return 2;
          }

          function TopClass() {}
          TopClass.inherits(SubClass);
          TopClass.prototype.getName = function() {
          ????//這里看上去非常的清晰,它調(diào)用了SubClass的getName()方法
          ????return "TopClass(" + this.getId() + ") extends " +
          ????????this.uber("getName");
          }

          TopClass.prototype.getId = function() {
          ????//Ok, 因此this.getId()應(yīng)該總是
          //返回調(diào)用SubClass的getId()方法的返回值(2)。
          ????return this.uber("getId");
          }

          //輸出結(jié)果:"TopClass(2) extends SubClass(1) extends BaseClass(1)"
          //嗯?后面的兩次this.getId()調(diào)用都沒(méi)有返回2.
          //發(fā)生了什么?
          alert(new TopClass().getName());
          ????????
          ???????? 上面代碼的第一部分包括了Crockford的“inherit”和“uber”方法代碼。第二部分看上去和前面的示例很類似,除了我添加了用來(lái)演示 Crockford方式所存在問(wèn)題的第三層繼承關(guān)系。誠(chéng)然,Crockford這位JavaScript大師的方法是我所找到的最可靠的方法之一,我很敬佩他在JavaScript編程方面做出的貢獻(xiàn)。但是,如果你使用三個(gè)依次繼承的類來(lái)考核他的代碼,你將從輸出中發(fā)現(xiàn)這里存在著細(xì)微的問(wèn)題。

          ???????? 從輸出結(jié)果看,第一次調(diào)用的this.getId()返回了TopClass當(dāng)前的id值“2”,但在調(diào)用SubClass和BaseClass的 getName()方法時(shí)返回了“1”而不是“2”。從代碼上看,在getName()方法中的父類調(diào)用行為是正確的,三個(gè)類的名字都被正確地顯示出來(lái)。唯一的問(wèn)題出現(xiàn)在this.uber("getId")這個(gè)父類調(diào)用被放入調(diào)用堆棧(call stack)時(shí)。因?yàn)榇藭r(shí)當(dāng)前對(duì)象是一個(gè)TopClass實(shí)例,而每次調(diào)用在調(diào)用堆棧中的this.getId()都應(yīng)該返回調(diào)用TopClass的 getId()方法后的返回值。

          ????????而問(wèn)題是TopClass的this.getId()方法通過(guò)this.uber ("getId")執(zhí)行了父類調(diào)用,這三次this.getId()調(diào)用中的后兩次錯(cuò)誤地調(diào)用了BaseClass的getId()方法,這樣便在輸出結(jié)果中顯示了兩次“1”。正確的行為應(yīng)該是調(diào)用三次SubClass的getId()方法,在輸出結(jié)果中顯示三次“2”。大家可以通過(guò)FireFox的FireBug插件進(jìn)行代碼debug進(jìn)行觀察。

          ????????這是十分難以描述的現(xiàn)象,我不能保證我能把它解釋清楚。但是至少?gòu)纳厦娴倪\(yùn)行結(jié)果中可以看出它是錯(cuò)誤的。

          ????????另外,Crockford的方法和其它一些方法的劣勢(shì)在于每個(gè)父類調(diào)用都需要一個(gè)額外的方法調(diào)用和額外的某種處理。這是否成為你所面臨的問(wèn)題,取決于你所使用的父類調(diào)用深度。在ThinWire項(xiàng)目的客戶端代碼中使用了大量的父類調(diào)用,因此父類調(diào)用的可靠性和快速性在項(xiàng)目中是很重要的。

          我的初級(jí)Solution:

          ????????面對(duì)這樣的窘境——Crockford的方法出現(xiàn)問(wèn)題、在互聯(lián)網(wǎng)上沒(méi)有找到符合要求的方法,我決定看看我自己是否可以發(fā)明一種可以滿足要求的方法。這花掉了我近一周的時(shí)間來(lái)使代碼工作并滿足各種情況,但我對(duì)它的工作情況很有信心,并且很快把它與framework集成在一起,TinWire的beta和beta2兩個(gè)版本中都使用了這些“初級(jí)設(shè)計(jì)”的代碼。

          ????????動(dòng)態(tài)父類調(diào)用:

          一次性支持代碼:

          //定義最頂級(jí)類
          function Class() { }
          Class.prototype.construct = function() {};
          Class.__asMethod__ = function(func, superClass) {????
          ????return function() {
          ????????var currentSuperClass = this.$;
          ????????this.$ = superClass;
          ????????var ret = func.apply(this, arguments);????????
          ????????this.$ = currentSuperClass;
          ????????return ret;
          ????};
          };

          Class.extend = function(def) {
          ????var classDef = function() {
          ????????if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
          ????};
          ????
          ????var proto = new this(Class);
          ????var superClass = this.prototype;
          ????
          ????for (var n in def) {
          ????????var item = def[n];????????????????????????
          ????????
          ????????if (item instanceof Function) {
          ????????????item = Class.__asMethod__(item, superClass);
          ????????}
          ????????
          ????????proto[n] = item;
          ????}

          ????proto.$ = superClass;
          ????classDef.prototype = proto;
          ????
          ????//賦給這個(gè)新的子類同樣的靜態(tài)extend方法
          ????classDef.extend = this.extend;????????
          ????return classDef;
          };


          運(yùn)行示例:

          //Hey, 注意一下這個(gè)類的定義方式
          //看上去比其它方式要清楚些
          var BaseClass = Class.extend({
          ????construct: function() { /* optional constructor method */ },
          ????
          ????getName: function() {
          ????????return "BaseClass(" + this.getId() + ")";
          ????},
          ????
          ????getId: function() {
          ????????return 1;
          ????}
          });

          var SubClass = BaseClass.extend({
          ????getName: function() {
          ????????//調(diào)用BaseClass的getName()方法
          ????????return "SubClass(" + this.getId() + ") extends " +
          ????????????this.$.getName.call(this);
          ????},
          ????
          ????getId: function() {
          ????????return 2;
          ????}
          });

          var TopClass = SubClass.extend({
          ????getName: function() {
          ????????//調(diào)用SubClass的getName()方法
          ????????return "TopClass(" + this.getId() + ") extends " +
          ????????????this.$.getName.call(this);
          ????},
          ????
          ????getId: function() {
          ????????//this.getId()總是返回調(diào)用父類的getId()方法的返回值(2)
          ????????return this.$.getId.call(this);
          ????}
          });

          //輸出結(jié)果:"TopClass(2) extends SubClass(2) extends BaseClass(2)"
          //一切都正確!
          alert(new TopClass().getName());

          ???????? 這里是前面示例的,但是目前這種方式包括了通過(guò)“extend”方法實(shí)現(xiàn)的十分清晰的類定義模式和正確的父類調(diào)用語(yǔ)義。尤其是“extend”方法通過(guò)一個(gè)中間function封裝了類定義中的每個(gè)方法,這個(gè)中間function在每次方法調(diào)用時(shí)首先把當(dāng)前父類引用“$” 與正確的父類引用相互交換,然后把這個(gè)正確的父類引用傳遞給apply()進(jìn)行方法調(diào)用,最后再將把當(dāng)前父類引用“$” 與正確的父類引用交換回來(lái)。這種方式唯一的問(wèn)題就是它需要一些中間function,它們會(huì)對(duì)性能產(chǎn)生不良影響。所以近來(lái)我重新審視了設(shè)計(jì)、完成了去掉了中間function了一種改良的方式。

          改良后的Solution:

          ????????動(dòng)態(tài)父類調(diào)用快速版本:

          一次性支持代碼

          //定義最頂級(jí)類
          function Class() { }
          Class.prototype.construct = function() {};
          Class.extend = function(def) {
          ????var classDef = function() {
          ????????if (arguments[0] !== Class) { this.construct.apply(this, arguments); }
          ????};
          ????
          ????var proto = new this(Class);
          ????var superClass = this.prototype;
          ????
          ????for (var n in def) {
          ????????var item = def[n];????????????????????????
          ????????if (item instanceof Function) item.$ = superClass;
          ????????proto[n] = item;
          ????}

          ????classDef.prototype = proto;
          ????
          ????//賦給這個(gè)新的子類同樣的靜態(tài)extend方法
          ????classDef.extend = this.extend;????????
          ????return classDef;
          };


          運(yùn)行示例:

          //Hey, 注意一下這個(gè)類的定義方式
          //看上去比其它方式要清楚些
          var BaseClass = Class.extend({
          ????construct: function() { /* optional constructor method */ },
          ????????
          ????getName: function() {
          ????????return "BaseClass(" + this.getId() + ")";
          ????},
          ????
          ????getId: function() {
          ????????return 1;
          ????}
          });

          var SubClass = BaseClass.extend({
          ????getName: function() {
          ????????//調(diào)用BaseClass的getName()方法
          ????????return "SubClass(" + this.getId() + ") extends " +
          ????????????arguments.callee.$.getName.call(this);
          ????},
          ????
          ????getId: function() {
          ????????return 2;
          ????}
          });

          var TopClass = SubClass.extend({
          ????getName: function() {
          ????????//調(diào)用SubClass的getName()方法
          ????????return "TopClass(" + this.getId() + ") extends " +
          ????????????arguments.callee.$.getName.call(this);
          ????},
          ????
          ????getId: function() {
          ????????// this.getId()總是返回調(diào)用父類的getId()方法的返回值(2)
          ????????return arguments.callee.$.getId.call(this);
          ????}
          });

          //輸出結(jié)果:"TopClass(2) extends SubClass(2) extends BaseClass(2)"
          //工作正常!而且沒(méi)有任何中間function
          alert(new TopClass().getName());

          ????????這是最后的設(shè)計(jì),它使用了JavaScript中一點(diǎn)鮮為人知的特性:callee。

          ???????? 在任何方法執(zhí)行過(guò)程中,你可以查看那些通過(guò)“arguments”數(shù)組傳入的參數(shù),這是眾所周知的,但很少有人知道“arguments”數(shù)組包含一個(gè)名為“callee”的屬性,它作為一個(gè)引用指向了當(dāng)前正在被執(zhí)行的function,而后通過(guò)“$”便可以方便的獲得當(dāng)前被執(zhí)行function所在類的父類。這是非常重要的,因?yàn)樗谦@得此引用的唯一途徑(通過(guò)“this”對(duì)象獲得的function引用總是指向被子類重載的function,而后者并非全是正在被執(zhí)行的function)。

          posted on 2006-12-31 15:55 捕風(fēng) 閱讀(5769) 評(píng)論(1)  編輯  收藏 所屬分類: web 頁(yè)面開(kāi)發(fā)

          評(píng)論

          # re: javascript中子類調(diào)用父類方法解決方案 2008-12-30 20:03 長(zhǎng)大不好玩

          我很欽佩你在這個(gè)問(wèn)題上研究的深度,給我了很大的幫助,不過(guò)你說(shuō)這句話時(shí)很讓我覺(jué)得惡心:
          "我很敬佩他在JavaScript編程方面做出的貢獻(xiàn)……"

            回復(fù)  更多評(píng)論   

          主站蜘蛛池模板: 白水县| 韩城市| 宁城县| 肥乡县| 泸西县| 枣强县| 东城区| 达拉特旗| 鹿泉市| 澜沧| 德格县| 冷水江市| 广水市| 苏尼特右旗| 本溪市| 余庆县| 廉江市| 左云县| 宣威市| 陵川县| 盱眙县| 林芝县| 囊谦县| 伊金霍洛旗| 蓝山县| 久治县| 灵石县| 泰来县| 县级市| 毕节市| 台江县| 邻水| 庄浪县| 鲁甸县| 泸西县| 红河县| 小金县| 镇江市| 龙岩市| 杨浦区| 汉阴县|