去年有幾個項目需要使用JavaScript互斥鎖,所以寫了幾個類似的,這是其中一個: 複製代碼//Published by Indream Luo//Contact: indreamluo@qq.com//Version: Chinese 1.0.0 !function ($) { window.indream = window.indream || {}; $.indream = indream; indream.async = { // //鎖 //lock: 鎖的編號 //action: 解鎖後執行的方法 // lock: function (lock, action) { $.indream.async.waitings[lock] = $.indream.async.waitings[lock] || []; $.indream.async.waitings[lock].push(action); //如果該鎖未被使用,則當前action阻塞該鎖 if (!$.indream.async.lockStatus[lock] && action) { $.indream.async.lockStatus[lock] = true; if (arguments.length > 2) { var args = 'arguments[2]'; for (var i = 3; i < arguments.length; i++) { args += ', arguments[' + i + ']'; } eval('$.indream.async.action.call(action, ' + args + ')'); } else { $.indream.async.action.call(action); } } }, // //解鎖 //lock: 鎖的編號 // releaseLock: function (lock) { $.indream.async.waitings[lock].shift(); //如果等待隊列有對象,則執行等待隊列,否則解鎖 if ($.indream.async.waitings[lock].length) { $.indream.async.waitings[lock][0](); } else { $.indream.async.lockStatus[lock] = false; } }, // //鎖的狀態 // lockStatus: [], // //等待事件完成 //lock:鎖編碼,相同的編碼將被整合成一個序列,觸發時同時觸發 // wait: function (lock, action) { $.indream.async.waitings[code] = $.indream.async.waitings[code] || []; $.indream.async.waitings[code].push(action); }, // //等待序列 // waitings: [], // //資料緩衝 // action: { // //監聽和回調的相關方法 // callback: { // //監聽 // listen: function (actionName, callback) { var list = $.indream.async.action.callback.list; list[actionName] = list[actionName] || []; list[actionName].push(callback); }, // //回調 // call: function (actionName, args) { var list = $.indream.async.action.callback.list; if (list[actionName] && list[actionName].length) { for (var i in list[actionName]) { $.indream.async.action.call(list[actionName][i], args); } } }, // //現有的回調列表 // list: [] }, // //根據方法是否存在和參數是否存在選擇適當的執行方式 // call: function (action) { if (action) { if (arguments.length > 1) { var args = 'arguments[1]'; for (var i = 2; i < arguments.length; i++) { args += ', arguments[' + i + ']'; } eval('action(' + args + ')'); } else { action(); } } } } }}(window.jQuery);複製代碼一個互斥鎖的幾個元素是: 鎖與解鎖等待隊列執行方法以上鎖的用法: 複製代碼//定義鎖的名稱var lock = 'scrollTop()';//使用鎖$.indream.async.lock(lock, function () { var scrollTop = $(window).scrollTop(); var timer; var fullTime = 100; for (timer = 0; timer <= fullTime; timer += 10) { setTimeout('$(window).scrollTop(' + (scrollTop * (fullTime - timer) / fullTime) + ');', timer); } //釋放鎖 setTimeout('$.indream.async.releaseLock("' + lock + '");', fullTime);});複製代碼關於這次所的實現,簡單說明下。 -自旋鎖還是訊號量JavaScript本身沒有鎖的功能,所以做的鎖都是在高層實現的。 依據JavaScript單線程的原理,JS的線程資源十分有限,非常不適合使用自旋鎖,所以選擇了使用訊號量。 自旋鎖實現起來的樣子大致是這樣的,當然do while更多用了: while(true) { //do something...}這樣必然需要佔滿線程資源,可惜JS只有一條線程可以用來執行,所以這樣做十分不適用。當然,有需要可以選擇setInterval和clearInterval的組合去實現,效果也會不錯。 這裡選用了訊號量的方式,原理也簡單,就如代碼那麼短。工作的執行順序大致是: 把程式碼片段(回調的action)推入等待隊列判斷當前鎖是否被持有,如果被持有則等待釋放,否則擷取該鎖,執行回調當鎖被釋放,則在等待隊列中shift出下一個回調,將鎖傳遞給它並執行 -自動釋放還是手動釋放看起來最舒服的方式當然是鎖住之後噹噹前程式執行完就自動釋放,不過這樣並不容易,因為有更多的情況需要自訂釋放情境。 本身使用鎖的就是在非同步中的方法,所以各種通常也會出現其他非同步內容,比如AJAX、jQuery動畫。這個時候,自動釋放就不符合需求了,因為實際上真正的“執行完畢”是在它內部的非同步回調完成後,也就是基本上只有開發人員自己能把握,所以這裡選擇了手釋放。 不過還是有缺陷的,就是重複釋放。 可以看到所有的鎖的對象都是公有的,或者應該說JS所有對象都是公有的,除非使局部變數在存取層級上進行隔離。不過這裡“鎖”本身就是個公用資源,所以沒辦法處理。 這裡可以做的最佳化應該是像setInterval和clearInterval的那樣,以公用的鎖名稱進行加鎖,以私人的鎖ID進行解鎖,就可以防止重複釋放了。不過上面這段老代碼中沒有,估計很快就會用到的了。