前言
在日常開發中,也許我們會遇到這樣的一個問題。我們利用【發布訂閱模式】(如果不了解的可以直接訪問此鏈接
www.cnblogs.com/xiaoxiaokun… )去執行【發布】事件時,遇到函數內部涉及到異步執行時,就比較難以處理。為了滿足這種需求,我專門寫了一個這樣的插件用於函數整合隊列並順序執行。
函數隊列循環執行
/** *1.0.0.1版本 */ var list=[];//存儲函數 list.push(function(){ console.log(1); }); list.push(function(){ console.log(2); }); list.push(function(){ console.log(3); }); for (var i=0;fn=list[i++];) { fn();//執行 }
結果:1,2,3
這是最簡單的方式,可以實現函數整合成一個隊列,按照先進先出順序執行。現在在仔細看發現,如果函數中有異步執行那函數執行就不能保證按照順序執行,例如:
var list=[];//存儲函數 list.push(function(){ console.log(1); }); list.push(function(){ setTimeout(function() { console.log(2); }, 2000); }); list.push(function(){ console.log(3); }); for (var i=0;fn=list[i++];) { fn();//執行 }
輸出的結果肯定是 1,3,2
/** *1.0.0.2版本 */ var list=[]; list.push(function(){ console.log(1); }); list.push(function(){ console.log(2); }); list.push(function(){ console.log(3); }); function start(list){ if(list.length){ list.shift()(); arguments.callee(list); } } start(list);
這種方式可以等到執行完畢,清除list內部執行過后的函數。不影響下次push 執行。但是異步函數還是未解決。
/** *1.0.0.3版本 */ var list=[];//存儲數組的集合 list.push(function(){ console.log(1); }); list.push(function(callback){ //callback是代表這個函數是異步的函數 setTimeout(function() { console.log(2); callback();//執行完異步函數執行回調函數 }, 2000); }); list.push(function(){ console.log(3); }); function start(){ //判斷數組的長度 if(list.length){ var fn=list.shift();//取出數組第一個函數 //判斷函數是否帶有參數 if(fn.length){ fn(start);//執行該函數,並且把 start本身傳遞進去。 }else{ fn(); start(); } } } start();
此版本可以解決帶有異步執行的函數按照剛開始push進去的順序依次執行。
需要注意的是,如果函數是內部帶有異步執行的函數,需要傳遞一個參數來告訴start。但是如果我們push進去的函數本身有好多個參數這需要怎么辦呢!!接下來看另一版本。
/** *1.0.0.4版本 */ var list=[];//存儲數組的集合 list.push(function(){ console.log(1); }); list.push(function(callback){ setTimeout(function() { console.log(2); callback(); }, 2000); }); list.push(function(){ console.log(3); }); function start(){ //判斷數組的長度 if(list.length){ var fn=list.shift();//取出數組第一個函數 //判斷函數是否帶有參數 if(fn.length && getfunarg(fn)[0]=='callback'){ fn(start);//執行該函數,並且把 start本身傳遞進去。 }else{ fn(); start(); } } } start(); /** * 查找函數參數名 * @fn {Function } 要查找的函數 * @return []返回參數數組 * */ function getfunarg(fn) { var f = /^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(fn.toString()); return f && f[1] ? f[1].split(/,\s*/) : []; }
到現在為止,我們這幾個函數基本已經滿足我們的需求,但是push的時候,假設函數多個參數,我們還需進一步優化代碼!為了把這個插件做的更好。我決定還是把callback放在最后,這樣就能保證函數傳遞參數不受影響。
最終版本
/** * 作者:小小坤 * 聯系:java-script@qq.com * 日期:2017-11-11 * 版本:1.0.0.4 * -----------使用說明---------- * 1、把所有函數【包含異步執行的函數】按照順序依次 使用lk.push存入 * 2、帶有參數的函數,一定要注意{最一個參數如果是callback}會被認為是 異步執行函數 * 3、異步執行的函數,需要把最一個參數設置為callback。並在函數執行完畢執行callback();函數保證按照順序執行 * * */ ;! function() { var list = [], //存儲函數的列表 isFun = Object.prototype.toString; //用於驗證是否是函數 /** * 添加到列表中 * @fn {Function} 函數體 * */ function push(fn) { isFun.call(fn) === "[object Function]" && list.push(fn); }; /** * 開始執行列表中的所有函數, * 按照先進先出原則 * * */ function star() { if(list.length) { var fn = list.shift(),//截取第一個函數 arry=getfunarg(fn),//獲取這個函數的參數列表 _length=arry.length;//參數列表的長度 if(_length && arry[_length-1] === 'callback') { if(_length===1){ fn(star); }else{ arry.pop();//刪除最后一個參數 arry.push(star);//把回調函數存入數組 fn.apply(this,arry); } } else { fn.apply(this,arry); star(); } } } /** * 查找函數參數名 * @fn {Function } 要查找的函數 * @return []返回參數數組 * */ function getfunarg(fn) { var f = /^[\s\(]*function[^(]*\(\s*([^)]*?)\s*\)/.exec(fn.toString()); return f && f[1] ? f[1].split(/,\s*/) : []; } //掛在到Windows上。 window.lk = { push: push, start: star } }(); //使用測試 /**--------一條華麗的分割線--------**/ var a=100, b=200, d=300, f=400; //定義函數 a2 ,此函數帶有一個參數,被認為是異步函數 function a2(a,b,callback) { console.time('2'); setTimeout(function() { console.timeEnd('2'); callback(); console.log(a,'a'); }, 1000); } //把函數函數 a2 放入數組 lk.push(a2); //定義函數 a3 function a3(d, f) { console.log(f,'f'); console.log(3); } //把函數函數 a3 放入數組 lk.push(a3); //定義函數 a4 此函數帶有一個參數,被認為是異步函數 function a4(callback) { console.time('4'); setTimeout(function() { console.timeEnd('4'); callback(); }, 2000); } //把函數函數 a4 放入數組 lk.push(a4); //最后開始執行 lk.start();
最終此插件完成,需要壓縮的同學可以自行壓縮。代碼比較簡單,提供了兩個方法。
push存儲函數列表
start開始執行
總結
通過上邊的代碼編寫我們學到了處理函數列表時候,需要考慮到異步函數。處理異步函數,需要回調函數參與。這樣就能幫助代碼按照順序執行。