Qomolangma实现篇(四):基本特性增强与多投事件系统
================================================================================
Qomolangma OpenProject v1.0
類別??? :Rich Web Client
關鍵詞? :JS OOP,JS Framwork, Rich Web Client,RIA,Web Component,
????????? DOM,DTHML,CSS,JavaScript,JScript
項目發起:aimingoo (aim@263.net)
項目團隊:aimingoo, leon(pfzhou@gmail.com)
有貢獻者:JingYu(zjy@cnpack.org)
================================================================================
一、Qomolangma對JS基本特性的增強
~~~~~~~~~~~~~~~~~~
為了實現更為豐富的OOP特性,Qomo增強了JavaScript的一些基礎特性。這主要表現在:
? - 對JS基本類型系統(的方法)的增強
? - 支持多投事件
這其中,對基本類型系統的增強,將嚴格恪守一條原則:不修改Object()對象原型。
除了array.indexOf()、array.remove()、string.trim() 等常見的增強之外,Qomo有
幾項特性是與其它可能(可能)不一致的。這幾項內容隨后列一專題來講述:
? - Array.prototype.insert
? - String.prototype.format
? - Function.prototype.toString
此外,因為Qomo以后將提供與Altas相同的、基于vs.net的可視編輯特性,因此一些基
本的特性擴展參考或者拷貝了Altas的代碼。但這些代碼目前只是留在了JSEnhance.js
中而未被啟用。你可以不關注它們。
在Mozilla系列的瀏覽器環境中,提供了一個uneval()函數,這個函數用于序列化腳本
對象,在今后的開發中很有價值。但它被放在了Compat/common_ie6.js中。這里也只提
及它,而不分析它的實現。
二、JSEnhance.js中部分增強特性
~~~~~~~~~~~~~~~~~~
首先,請記住JSEnhance.js最主要的特性是“它可以脫離Qomo framework使用”。這個
單元不依賴于Qomo的任何特性。它使用自然的、原始的JavaScript方法來擴展JS特性。
因此它可以用于任何的Framework。
? Array.prototype.insert
? ----------
? Qomo中為array.insert()提供了更強大的能力,使得它可以向任意位置插入數組、單
個或多個元素。這與一些其它的框架不同:它們通常只提供插入單個元素的能力。
? String.prototype.format
? ----------
? Qomo中的string.format()是參考Delphi實現的。因此你會到匹配符是“%s”和“%n”。
這里的“s(大小寫均可)”用于指代一個被替換元,而“n(0..n)”用于指代第n個替換元。
由于在JS中沒有明確的類型,因此沒有"%d"之類的匹配符。
作為習慣,我提供了一個全局的函數:format()。
關于string.format()的使用,參見DOCUMENTs/TestCase/T_StringFormat.html。
? Function.prototype.toString
? ----------
? 在JavaScript中,匿名函數(立即值)聲明、函數對象構造、函數的標準語法聲明等都
可以聲明一個有效的函數。但這些函數的toString()并不一致。為了解決對函數名的依
賴性問題,并使得下面的語法總有確定的含義:
??? function func() { /* ... */ };?
??? foo = eval(func.toString());
Qomo復寫了function.toString()。使得它總是返回一個匿名函數的字符串。如(上例):
??? function () { /* ... */ };
三、JSEnhance.js中的多投事件系統
~~~~~~~~~~~~~~~~~~
首先,最重要的一點是:Qomo的多投事件系統對任何框架來說,是“完全透明”的!因
此,它可以在其它任何框架中,象一個普通的事件函數(響應句柄)一樣地加入被植入。
事實上,Qomo的多投事件與Qomo OOP框架完全地脫離開,不利用任何的OOP特性、框架特
性。——這種設計思路完整地體現了Qomo的目標與宗旨,以及,我們對OOP的認知。
下面的代碼展示Qomo中的多投事件系統的特性:
----------
e = new MuEvent();
document.writeln(typeof e, '<BR>');
for (i in e)
? document.writeln(' - ', i, '<BR>');
----------
輸出結果:
----------
function
?- add
?- addMethod
?- clear
?- reset
?- close
----------
這表明“多投事件對象,實際上是一個函數”,它提供“add()等五個方法”。
由于“多投事件對象是函數”,因此下面的代碼是成立的:
----------
func1 = func2 = function() { /* ... */ };
function MyObject() {
? this.OnExec = new MuEvent();
? this.run = function() {
??? // do somethings
??? this.OnExec();
? }
}
var obj = new MyObject();
obj.OnExec.add(func1);
obj.OnExec.addMethod(window, func2);
obj.run();
----------
這個例子用最簡的代碼演示了多投事件對象的使用。你看到我們最終仍然要通
過某種方式來使OnExec()被執行。只不過它被執行的時候,將同時觸發func1和
func2兩種行為。
? 1. add(), addMethod()
? ----------
? 在多投事件對象的方法中,addMethod()第一個不容易理解的東西。但我們需
要了解到:使用add()加入的func1,執行期拿到的this對象會是obj本身;而使
用addMethod()加入的func2,執行期拿到的this對象將是window對象。
這有什么意義呢?
例如setTimeout()這樣的函數在執行期只允許傳入函數,而不能傳入對象方法。
這就使得定時執行一個對象方法的代碼只能這樣寫:
----------
function doTimer() {
? obj1.call();
? obj2.call();
}
setTimeout(doTimer, 1000);
----------
在使用MuEvent()的情況下,上面的代碼就可以很簡單了:
----------
var e = new MuEvent();
e.addMethod(obj1, obj1.call);
e.addMethod(obj2, obj2.call);
setTimeout(e, 1000);
----------
addMethod()在Atlas里被稱為addAction()。這兩者的含義是一致的。成熟的多
投事件系統通常都會提供這種特性。
? 2. clear()與reset()
? ----------
? Qomo提供clear()方法來清除與該多投事件對象綁定的“事件句柄列表”。而
reset()則在清除之后再添加一個“事件句柄”。由于MuEvent對象也是函數,因
此下面的代碼也可以添加一個事件投送列表:
----------
var e1 = new MuEvent();
var e2 = new MuEvent();
// ...
// add somethings to e1
e2.add(func1);
e2.add(func2);
e2.add(func3);
// clear e1, and add a list(e2)
e1.reset(e2);
----------
? 3. close()
? ----------
? Qomo提供一種非常特殊的“關閉多投特性”的機制?!⒁膺@在其它的框架
上都沒有實現。
Qomo的多投事件對象是一個普通的函數,只不過它多了add()、addMethod()等等
方法。如果我們清除掉這些方法,那么該對象的外在表現就與一個普通函數完全
無異。這種情況下,一個第三方的框架根本無法識別這個“關閉多投特性的‘多
投事件對象’”,而當成一個普通函數處理。
因此Qomo的多投特性可以完全透明地嵌入一個第三方框架。甚至象DOM這樣的瀏覽
器基礎系統。例如下例:
----------
var loading = new MuEvent();
loading.add(loadPicture1);
loading.add(loadPicture2);
loading.add(loadPicture3);
// ...
loading.add(loadPicture1000);
loading.close();
window.onload = loading;
----------
這種情況下,瀏覽器的DOM框架完全感覺不到loading(作為一個函數)有什么不同。
Qomo提供的close()特性的作用遠不至此。事實上,close()特性真正的價值在于對
系統設計層面的考量。例如我們做一個TLabledEdit對象,也就是將一個Lable與一
個Edit綁在一起。那么我們發現,我們事實上對Lable.onclick的行為的理解,肯定
是“選中Edit并置輸入焦點”。這種行為特征在設計之初就被確定了,根本不應該
被更改。——當然,如果你的設計就是要更改,那另論。
而原始的TLable的設計中,TLable.onclick是一個公開的方法,并且是多投事件。
那么即使我們寫下下面的代碼:
----------
FLabled.onclick.addMethod(FEdit, FEdit.onclick);
----------
在其后的、用戶的代碼中仍然可以改變FLabled.onclick的行為。例如add/clear()。
這顯然是這個TLabledEdit組件的原始設計者所不希望的。因此,在提供了close()
特性的情況下,它就可以在上面的代碼中這樣寫:
----------
// 當創建結束調用
this.DoCreate = function() {
? FLabled.onclick.addMethod(FEdit, FEdit.onclick);
? FLabled.onclick.close();
}
----------
這樣就可以保證onclick()的特性不被變更。而且,如果FEdit.onclick被變量(例
如add/reset),FLabled.onclick可以正常地感知到。
? 4. 為什么不提供del()
? ----------
? Qomo的多投事件不提供del()特性?;趦蓚€原因:
??? - del()可能導致事件的激活順序被破壞
??? - del()需要執有內部“事件句柄列表”中的事件方法的引用,這破壞了封裝性
因此,(在目前的版本中,)作為一項框架設計層面上的考量,Qomo不提供del()。但
是,由于atlas的多投事件有del()方法,因此在將來實現嵌入vs.net的代碼時,Qomo
是可能會提供del()方法的。
四、多投事件系統的實現分析
~~~~~~~~~~~~~~~~~~
? 1. 基本的多投事件系統
? ----------
? 最基本的多投事件系統實現方法是這樣:
----------
function MuEvent() {
? // this is a new obj instance
? var all = this;
? all.length = 0;
? function add(foo) { /* ... */ }
? function addMethod(obj, foo) { /* ... */ }
? function clear() { /* ... */ }
? function reset(foo) { /* ... */ }
? function run() { /* ... */ }
? var e = function() { return run.call(this, arguments) }
? e.add = add;
? e.addMethod = addMethod;
? e.clear = clear;
? e.reset = reset;
? return e;
}
----------
這樣實現的看起來很簡單、自然。而且由new()關鍵字構造的對象實例this已經被
內部變量all執有了一個引用,用以建立事件列表。避免了不必要的開銷??雌饋?br />是不錯的。——自然close()方法的實現也很容易,不成問題。
但是這種情況下,我們對比多個事件對象,會發現一個不可接受的事實:
----------
var e1 = new MuEvent();
var e2 = new MuEvent();
alert(e1.add === e2.add)
----------
你會發現結果是false,也就是說:有多少個事件對象,就會有多少個add、clear
方法。其開銷極其巨大:n * 5。
? 2. Qomo中多投事件系統的實現基礎
? ----------
? 在Qomo里,這一切被巧妙地避免了。我為每一個事件對象建立了一個handle。它
是一個索引。
----------
? function _MuEvent() {
??? // get a handle and init MuEvent Object
??? var handle = all.length++;
??? //...
----------
為了讓add()等方法成為“唯一實例”,我將它放在了_MuEvent()之外來實現。但
這種情況下,對象執有的handle對add()方法就是不可見的了。因此我們還需要一
種機制,來使對象可以向add()等方法暴露handle。這里,我們選用了valueOf()。
對于函數(多投事件對象)ME來說,它的valueOf()的結果指向自身:ME。在大多數
的情況下,這是沒有意義的。因此我們這樣來實現valueOf():
----------
ME.valueOf = function() {
?return handle
};
----------
而在add()中,我們這樣來使用valueOf():
----------
var all2 = []; // all ME() object for recheck.
function add(foo) {
? var i=this.valueOf(), e=all[i];
? if (e && e==all2[i]) {
??? // add...
? }
}
----------
由于我們使用了第二個數組all2來復核,因此可以避免用戶使用這樣的代碼來套
取、破壞多投事件列表:
----------
// if e1's handle is 10, and hide into a Object/System
var e1 = new MuEvent();?
// 套取用的函數
f = function(){};
// 指定欲套取的句柄
f.valueOf = function() { return 10 }
// 重置(注意所有的多投事件對象的方法是相同的)
f.clear = (new MuEvent()).clear;
// 破解e1的事件列表(利用valueOf()返回10的特性)
f.clear();
----------
所以這樣來看,加入數組all2[]來復核是必須的。
? 3. “強壯”與“快”是兩難的
? ----------
? 但接下來,我們也發現這個“多投事件系統”是不“強壯”的。為什么呢?因為
valueOf()仍然可以被外部代碼改寫。——這將導致依賴它來獲取handle的add()
等方法失效。事實上,由于我們重定義了valueOf()的含義,也使得Qomo與一些第
三方的框架、系統中可能出現不兼容。
Qomo應當是一個強壯的系統。由于valueOf()的存在,影響了強壯性,也使“透明”
成為空話。
我們回到前面這個all2[]。事實上,由于復核的必要,我們已經存放了一份所有對
象的列表。因此,不通過handle來查找ME和事件列表對象,是可能的:
----------
function add(foo) {
? var e = all.search(this);
? if (e) {
???? // ...
? }
}
----------
在這個代碼中,我們需要在all.search()其實被設計成一個算法,用于在all2[]中
查找this對象(也就是ME()函數)。而search()返回的,則是“使用all2[]中this對象
的索引”,在all[]中查找到的“投送事件列表”。——這個索引其實就是handle。
這樣,就不需要重寫ME().valueOf()來公布handle了。但是,由于每次add()等操作
都將查找all2[],使得系統會相對慢一些。——簡單的說:強壯了,但慢了。
所以整個MuEvent的實現代碼是這樣:
----------
var MuEvent = function (fast) {
? var all = {
??? length : 0,
??? strong : !fast, // ^.^
??? // ...
? }
? return _MuEvent;
}(true);
----------
簡單地說,上面的一行代碼真實的反映了:強壯就不快,快就不強壯。
? 4. 真的不快嗎?
? ----------
? 簡單地分析一下我們在使用事件系統時候的一些特點,我們會發現:
??? - 事實上通常我們會成批地添加一個事件,或者一個對象的一組事件
??? - 事實上相關的對象、事件總是被“在臨近時間上”被處理的
例如我們通常會在對象初始化的時候寫這樣的代碼:
----------
obj.onclick.add(foo1);
obj.onclick.add(foo2);
obj.onmouseout.add(foo3);
----------
而在多投事件體系中,同一對象的OnXXXXX事件通常是連續被創建的,而且剛剛完成
創建的事件對象可能會被賦以一些初值。簡單的講,這些使用習慣表現為:
??? - 最近創建的事件總是可能會被很快操作到
??? - 最近操作的事件附近的(同一對象的)事件總是可能會被很快操作到
基于這兩個原理。Qomo設計了一個在all2[]中查找事件對象的方法:總是從上一次
添加或查找到的事件對象的附近,開始前向、后向檢索。具體的算法參見all.search().
加入檢索算法使得add等行為的速度大大的加快。當然,這是基于開發人員的代碼行
為分析的,而真正的“在數組中檢索對象”的方法的效率并沒有辦法提高。這是JS自
身無可回避的問題。
但是,如果引用hash或者使用對象屬性名來檢測,則必然要給ME()對象一個“可在外
部訪問的key/name值”。這又回到了前面提供handle的“fast方法”同樣的問題上。
因此這樣的問題,是不必要再討論的。
Qomo的JSEnhance.js中,默認采用"fast = false"的配置,以提供一套強壯的系統。但
如果你確信你的系統是封閉的、不會導致第三方的框架的影響的,那么你可以在JSEnhance
中開啟下面這個開關:
----------
var MuEvent = function (fast) {
? // ...
? return _MuEvent;
}(false);???? // <-- here, set to true
----------
最后,最重要的一點提示,是Qomo在這個多投事件系統上的效率犧牲,只會表現在add
等方法的調用上。并不會對ME()事件的執行構成任何的影響。因為在代碼上:
----------
function _MuEvent() {
? // get a handle and init MuEvent Object
? var handle = all.last = all.length++;
? var ME = function() {
??? if (all[handle].length > 0)? // <--- 直接使用handle
????? return run.call(this, handle, arguments)
? }
? //...
}
----------
由于ME()的執行可以直接使用內部的handle變量,根本就不會調用all.search()。因此
Qomo只是在“維護事件投送列表(add/reset等)”時有一些search()的性能開銷。“執行
投送事件”時,是性能最優化的。
轉載于:https://www.cnblogs.com/encounter/archive/2006/03/07/2188709.html
總結
以上是生活随笔為你收集整理的Qomolangma实现篇(四):基本特性增强与多投事件系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分析unix系统日期析取ftp登陆和断开
- 下一篇: StringBuilder的实现与技巧