兩個問題
1.jQuery的鏈式操作是如何實現的?
2.為什么要用鏈式操作?
大家認為這兩個問題哪個好回答一點呢?
?
鏈式操作
原理相信百度一下一大把,實際上鏈式操作僅僅是通過對象上的方法最后
return this
把對象再返回回來,對象當然可以繼續調用方法啦,所以就可以鏈式操作了。那么,簡單實現一個:
// 定義一個JS類 function Demo() { } // 擴展它的prototype Demo.prototype = { setName: function (name) { this .name = name; return this ; }, getName: function () { return this .name; }, setAge: function (age) { this .age = age; return this ; } }; // //工廠函數 function D() { return new Demo(); } // 去實現可鏈式的調用 D().setName("CJ").setAge(18).setName();
?
但……為什么要用呢?
一般的解釋:
節省代碼量,代碼看起來更優雅。
例如如果沒有鏈式,那么你可能需要這樣寫代碼:
document.getElementById("ele"
).dosomething();
document.getElementById(
"ele").dootherthing();
這個代碼中調用了兩次document.getElementById來獲取DOM樹的元素,這樣消耗比較大,而且要寫兩行,而鏈式只要寫一行,節省了代碼……
但我們也可以用緩存元素啊。比如:
var ele = document.getElementById("ele" ); ele.dosomething(); ele.dootherthing();
而且兩行并沒有比一行多多少代碼,甚至相應的封裝反而使得代碼更多了。
最糟糕的是所有對象的方法返回的都是對象本身,也就是說沒有返回值,這不一定在任何環境下都適合。
舉個例子,我們想弄一個超大整數BigInteger(意思是如果用Javascript的Number保存可能會溢出的整數),順便擴展他的運算方法,會適合用鏈式操作么?
?例如運算3 1415926535 * 4 -? 271828182,如果設計成鏈式風格的方法可能會是這樣的:
var result = ( new BigInteger("31415926535")).multiply( new BigInteger("4")).subtract( new BigInteger("271828182" )).val(); console.log( "result == " + result);
這看起來似乎也很優雅,但是如果我們想要中間的結果怎么辦呢?或許會寫成這樣:
var bigInteger = new BigInteger("31415926535" ); var result1 = bigInteger.multiply( new BigInteger("4" )).val(); var result2 = bigInteger.subtract( new BigInteger("271828182" )).val(); console.log( "result1 == " + result1 + ", result2 == " + result2);
這似乎一點也不優雅了,和不用鏈式操作沒啥不同嘛!
那么如果要求是原來的BigInteger不能改變呢?好吧,鏈式操作似乎不能滿足這個需求了。
jQuery專注于DOM對象操作,而DOM的操作會在頁面上體現,不需要在Javascript中通過返回值來表示,但計算操作卻不一樣,我們很可能需要通過Javascript返回中間過程值另作他用。
在設計的時候,我們需要考慮鏈式帶來的好處和壞處,因為別人用了鏈式,所以就用鏈式,可能并不是一個很好的方案。
那么到底為什么要用鏈式操作呢?
為了更好的異步體驗
Javascript是無阻塞語言,所以他不是沒阻塞,而是不能阻塞,所以他需要通過事件來驅動,異步來完成一些本需要阻塞進程的操作。
但是異步編程是一種令人瘋狂的東西……運行時候是分離的倒不要緊,但是編寫代碼時候也是分離的就……
常見的異步編程模型有哪些呢?
- 回調函數?
所謂的回調函數,意指先在系統的某個地方對函數進行注冊,讓系統知道這個函數的存在,然后在以后,當某個事件發生時,再調用這個函數對事件進行響應。
function f(num, callback){ if (num<0 ) { alert( "調用低層函數處理!" ); alert( "分數不能為負,輸入錯誤!" ); } else if (num==0 ){ alert( "調用低層函數處理!" ); alert( "該學生可能未參加考試!" ); } else { alert( "調用高層函數處理!" ); setTimeout(function(){callback();}, 1000); } }
這里callback則是回調函數。可以發現只有當num為非負數時候callback才會調用。
但是問題,如果我們不看函數內部,我們并不知道callback會幾時調用,在什么情況下調用,代碼間產生了一定耦合,流程上也會產生一定的混亂。
雖然回調函數是一種簡單而易于部署的實現異步的方法,但從編程體驗來說它卻不夠好。
- 事件監聽
也就是采用事件驅動,執行順序取決于事件順序。?
function EventTarget(){ this .handlers = {}; } EventTarget.prototype = { constructor: EventTarget, addHandler: function (type, handler){ this .handlers[type] = []; }, fire: function (){ if (! event.target){ event.target = this ; } if ( this .handlers[event.type instanceof Array]){ var handlers = this .handlers[event.type]; for ( var i = 0, len = handlers.length, i < len; i++ ){ handlers[i](event); } } }, removeHandler: function (type, handler){ if ( this .handlers[type] instanceof Array){ var handlers = this .handlers[type]; for ( var i = 0, le = handlers.length; i < len; i++ ){ if (handlers[i] === handler){ break ; } } handlers.splice(i, 1 ); } } };
上面是《JavaScript高級程序設計》中的自定義事件實現。于是我們就可以通過addHandler來綁定事件處理函數,用fire來觸發事件,用removeHandler來刪除事件處理函數。
雖然通過事件解耦了,但流程順序更加混亂了。
- 鏈式異步?
個人覺得鏈式操作最值得稱贊的還是其解決了異步編程模型的執行流程不清晰的問題。jQuery中$(document).ready就非常好的闡釋了這一理念。DOMCotentLoaded是一個事件,在DOM并未加載前,jQuery的大部分操作都不會奏效,但jQuery的設計者并沒有把他當成事件一樣來處理,而是轉成一種“選其對象,對其操作”的思路。$選擇了document對象,ready是其方法進行操作。這樣子流程問題就非常清晰了,在鏈條越后位置的方法就越后執行。
( function (){ var isReady= false ; // 判斷onDOMReady方法是否已經被執行過 var readyList= []; // 把需要執行的方法先暫存在這個數組里 var timer; // 定時器句柄 ready= function (fn) { if (isReady ) fn.call( document); else readyList.push( function () { return fn.call( this );}); return this ; } var onDOMReady= function (){ for ( var i=0;i<readyList.length;i++ ){ readyList[i].apply(document); } readyList = null ; } var bindReady = function (evt){ if (isReady) return ; isReady = true ; onDOMReady.call(window); if (document.removeEventListener){ document.removeEventListener( "DOMContentLoaded", bindReady, false ); } else if (document.attachEvent){ document.detachEvent( "onreadystatechange" , bindReady); if (window == window.top){ clearInterval(timer); timer = null ; } } }; if (document.addEventListener){ document.addEventListener( "DOMContentLoaded", bindReady, false ); } else if (document.attachEvent){ document.attachEvent( "onreadystatechange", function (){ if ((/loaded|complete/ ).test(document.readyState)) bindReady(); }); if (window == window.top){ timer = setInterval( function (){ try { isReady ||document.documentElement.doScroll('left'); // 在IE下用能否執行doScroll判斷dom是否加載完畢 } catch (e){ return ; } bindReady(); }, 5 ); } } })();
上面的代碼不能用$(document).ready,而應該是window.ready。
- Deferred & Promise
CommonJS中的異步編程模型也延續了這一想法,
每一個異步任務返回一個Promise對象,該對象有一個then方法,允許指定回調函數。 ?
所以我們可以這樣寫:
f1().then(f2).then(f3);
這種方法我們無需太過關注實現,也不太需要理解異步,只要懂得通過函數選對象,通過then進行操作,就能進行異步編程。?
?
參考資料
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
