更優雅的事件觸發相容

來源:互聯網
上載者:User

問題種種

做底層介面相容,無非就是利用if,判斷用戶端支援哪個介面的問題。最著名的例子就是事件:
複製代碼 代碼如下:
var addEvent = function(e, what, how) {
if (e.addEventListener) e.addEventListener(what, how, false)
else if (e.attachEvent) e.attachEvent('on' + what, how)
}

這裡考慮了給元素繫結事件時可能遇到的兩種狀況——標準的W3C DOM介面以及DHTML提供的介面。當然這個例子還很粗糙,但足夠說明問題了。

原先的方法是在相容層調用有現場判斷並進入相應的if分支。很顯然,這種“現場判斷”的方法效率並不高。後來,人們採用這樣的辦法:
複製代碼 代碼如下:
if (MSIE) {
addEvent = function(e, what, how) {
e.attachEvent('on' + what, how);
}
} else {
addEvent = function(e, what, how) {
e.addEventListener(what, how);
}
}

在一次判斷後給addEvent綁定不同的代碼,從而免去了運行時的分支判斷。

很可惜,這個問題也不小。首先把“採用attachEvent”和“用戶端是MSIE”綁定在一起是個很過時的想法。假如微軟哪天良心發現了怎麼辦?這事情現在就發生了——IE9明確支援了DOM介面,甚至DOM3都支援。結果,就這個“良心發現”的舉動會毀掉許多前端庫,他們必須被迫修改代碼(如同IE8來時那樣)。況且這種做法沒有考慮“未知的用戶端”——據我所知,Google發布Chrome後也導致不少類庫重寫代碼。
特性檢測

那究竟該怎麼做?特性檢測就可以最大限度地避免“新用戶端”帶來的麻煩——通過一組在類庫初始化時定義的代碼來檢測用戶端擁有的特性,並利用這一組檢測值綁定類庫代碼:
複製代碼 代碼如下:
var supportsAddEventListener = !!(checkerElement.addEventListener);
if (supportsAddEventListener) {
addEvent = function(e, what, how) {
e.addEventListener(what, how);
}
} else if (supportsAttachEvent) {
addEvent = function(e, what, how) {
e.attachEvent('on' + what, how);
}
}

特性檢測實際上是將“使用某個用戶端”和“支援某個特性”進行解耦——讓if分支直接針對“特性有無”(介面是否一致)判斷,從而消除用戶端製造商“良心發現”造成的“好心辦壞事”。事實上這麼做也是符合歷史潮流之選——當標準介面逐漸普及,用戶端之間漸漸“表徵一致”時,為什麼不做個一致的相容層介面呢?
跌落

讓我們重新看看這些代碼。通常,一條利用特性檢測進行相容的代碼往往是這樣:
複製代碼 代碼如下:
if (new_interface_detected) {
comp = function() {uses_new_interface};
} else if (old_interface_detected) {
comp = function() {uses_old_interface};
} else {
throw new Error('Unadaptable!')
}

換言之,過程是:

如果用戶端支援新介面,就將相容層綁定到新介面上
否則,如果用戶端支援老介面/不一致介面,就將相容層綁定到老介面上
否則,如果可以的話,給出錯誤回饋

亦即,相容層程式是從高空“掉”下來,如果用戶端支援“進階”特性(新介面、標準介面)就將它“接住”——相容層就有了歸宿;否則繼續向下掉——哦,老介面接住了,就用老介面;如果一直沒人接住,於是——啪——摔倒了地上,並且用最後一口氣喊一聲:“你用的用戶端太小眾,我拿你沒辦法了!”

這和什麼比較像?

事實上,如果你瞭解JavaScript對象系統的機理,你就可以類比:這不就是原型嘛!原型系統就是利用了這種跌落——尋找某個成員,如果它在這個對象裡定義了,就返回之;否則沿著原型鏈向上搜(沒錯,這次是向上的),如此重複,直到真的連原型鏈都到頭的時候,返回個undefined。

說做就做!這裡同樣用addEvent為例。首先,我們定義一個空驅動,它裡面什麼都不包含:

var nullDriver = {}

然後,就是建立個對象,並且把原型鏈指向它。在ECMA V5時代,我們可以用Object.create,可惜,現在還有N多老用戶端(否則做什麼相容啊),所以自己craft個函數:
複製代碼 代碼如下:
var derive = Object.create ? Object.create: function() {
var T = function() {};
return function(obj) {
T.prototype = obj;
return new T
}
}()

這個用法你可能會覺得很詭異,但它工作起來一點問題沒有,速度也不慢——能達到Object.create的一半。我們就用這個derive開動:
複製代碼 代碼如下:
var dhtmlDriver = derive(nullDriver);
var dhtmlDriverBugfix = derive(dhtmlDriver);

這裡的bugfix是針對一些“bug”和特殊情況定義的特別Driver。這裡你可以忽略它。好了,DHTML裡面addEvent是什麼來著?
複製代碼 代碼如下:
if (supportsAttachEvent) {
dhtmlDriver.addEvent = function(e, what, how) {
e.attachEvent('on' + what, how)
}
}

然後呢?位於原型鏈最前端的應該是W3C的標準驅動啊,寫上!
複製代碼 代碼如下:
var w3cDriver = derive(dhtmlDriverBugfix);
var w3cDriverBugfix = derive(w3cDriver);

if (supportsAddEventListener) {
w3cDriver.addEvent = function(e, what, how) {
e.addEventListener(what, how)
}
}

最後,我們就放個東西上去做最後調用的介面。(因為w3cDriverBugfix太難看……)

var driver = derive(w3cDriverBugfix);

然後就調用好了。看,這就讓那些長得嚇人的分支判斷變得簡單有效,但不失fallback本色:在支援addEventListener上調用addEvent等價於調用w3cDriver.addEvent,而在不支援addEventListener的用戶端上就會跌落到底下,比如調用dhtmlDriver.addEvent。另外,進行bugfix也很容易——可以在專門的“bugfix”層進行hook,而原有層絲毫不受影響。
等等,繼承這麼多層

會很慢嗎?誠然,那麼深的原型鏈肯定會慢,不過我有辦法。還記得給對象的屬性寫入時會發生什麼事情嗎?
複製代碼 代碼如下:
var ego = function(x) {return x}
for (var each in driver) {
if (! (each in nullDriver)) {
driver[each] = ego(driver[each])
}
}

沒錯,原來高企在原型鏈上面的方法會“嘩”的一下掉到最下面!這回不用沿著原型鏈向上搜了,直接從最底端擷取屬性即可。這裡用ego函數的原因是防止一些瀏覽器“最佳化掉”這裡的代碼。
總結

雖然這裡談相容,可是,它的精華卻在語言特性上——利用原型繼承,我們可以很優雅地完成這個令人頭疼的操作。是的,架構的美感不應該只在外表,其內部——即使是最最令人煩的內部——也同樣要優雅。

這裡的技術可以在dess中找到。
來自:typeof.net

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.