github的地址 歡迎star!
俗話說非同步是萬惡之源,但是沒有非同步是萬萬不行的
問題引出:
- 在react裡面的一個父元件中通過發起請求獲取後臺的資料,然後傳遞給子元件,發現在子元件用到該資料時並沒有觸發相應的改變。開啟谷歌控制檯發現xhr請求還在pending。因此就總結了非同步的處理。
js非同步的幾種方式
1.回撥
回撥函式的優點是簡單、容易理解和部署,缺點是不利於程式碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,而且每個任務只能指定一個回撥函式。
2.事件監聽
參考阮老師的解釋
3. 釋出/訂閱
const event = {
clientList: [],
listen(key, fn) {
if (!this.clientList[key]) {
this.clientList[key] = [];
}
this.clientList[key].push(fn); // 訂閱的訊息新增進快取列表
},
trigger() {
let key = Array.prototype.shift.call(arguments);
let fns = this.clientList[key];
if (!fns || fns.length === 0) {
return false;
}
for (let i = 0, fn; fn = fns[i++];) {
fn.apply(this, arguments);
//arguments 是trigger帶上的引數
}
},
// 移除事件
remove(key, fn) {
let fns = this.clientList[key];
if(!fns) return false;
if(!fn){
fns && (fns.length = 0);
}else{
for(let i = fns.length -1; i>=0; i--){
let _fn = fns[i];
if(_fn == fn){
fns.splice(i,1); //刪除訂閱者的回撥函式
}
}
}
}
};
複製程式碼
上面在使用的時候還沒有解決命名衝突的問題。具體使用的程式碼應該充分考慮
// 解決命名衝突問題
var globalEvent = (function () {
var global = this, Event,
_default = 'default';
Event = function () {
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function (ary, fn) {
var ret;
for (var i = 0, l = ary.length; i < l; i++) {
var n = ary[i];
ret = fn.call(n, i, n);
}
return ret;
};
_listen = function (key, fn, cache) {
if (!cache[key]) {
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function (key, cache, fn) {
if (cache[key]) {
if (fn) {
for (var i = cache[key].length; i >= 0; i--) {
if (cache[key][i] === fn) {
cache[key].splice(i, 1);
}
}
} else {
cache[key] = [];
}
}
};
_trigger = function () {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[key];
if (!stack || !stack.length) {
return;
}
return each(stack, function () {
return this.apply(_self, args);
});
};
_create = function (namespace) {
var namespace = namespace || _default;
var cache = {},
offlineStack = [], //離線事件
ret = {
listen: function (key, fn, last) {
_listen(key, fn, cache);
if (offlineStack === null) {
return;
}
if (last === 'last') {
offlineStack.length && offlineStack.pop()();
} else {
each(offlineStack, function () {
this();
});
}
offlineStack = null;
},
one: function (key, fn, last) {
_remove(key, cache);
this.listen(key, fn, last);
},
remove: function (key, fn) {
_remove(key, cache, fn);
},
trigger: function () {
var fn,
args,
_self = this;
_unshift.call(arguments, cache);
args = arguments;
fn = function () {
return _trigger.apply(_self, args);
};
if (offlineStack) {
return offlineStack.push(fn);
}
return fn();
}
};
return namespace ? (namespaceCache[namespace] ?
namespaceCache[namespace] : namespaceCache[namespace] = ret
) : ret;
};
return {
create: _create,
one: function (key, fn, last) {
var event = this.create();
event.one(key, fn, last);
},
remove: function (key, fn) {
var event = this.create();
event.remove(key, fn);
},
listen: function (key, fn, last) {
var event = this.create();
event.listen(key, fn, last);
},
trigger: function () {
var event = this.create();
event.trigger.apply(this, arguments);
}
};
}();
return Event;
})();
// 運用了虛擬代理,
複製程式碼
此時,可能就會問到,釋出訂閱和觀察者模式的區別:引入知乎裡面的回答:
釋出訂閱模式屬於廣義上的觀察者模式
釋出訂閱模式是最常用的一種觀察者模式的實現,並且從解耦和重用角度來看,更優於典型的觀察者模式
釋出訂閱模式多了個事件通道
- 觀察者模式中,觀察者需要直接訂閱目標事件;在目標發出內容改變的事件後,直接接收事件並作出響應
- 釋出訂閱模式中,釋出者和訂閱者之間多了一個釋出通道;一方面從釋出者接收事件,另一方面向訂閱者釋出事件;訂閱者需要從事件通道訂閱事件
具體的總結:
- 在觀察者中,觀察者監聽著目標物件,目標物件也管理儲存著它的觀察者們,釋出訂閱中,釋出者和訂閱者之間是獨立(不需要互相認識),它們只在訊息佇列或代理情況才能通訊
- 在釋出訂閱模式中,元件是鬆散連線的,觀察者與之相反
- 觀察者模式執行大多是通過同步的方法,例如,當一些事件觸發時,目標物件(Subject)就會呼叫觀察者的方法。釋出訂閱模式執行大多是非同步的(使用訊息佇列)
- 觀察者模式需要在單個應用程式地址空間上實現,相對應的,釋出-訂閱更像交叉應用模式。
觀察者模式像rxjs,可以看一下 rxjs 原理解析
4.Promise/Async
每一個非同步任務返回一個Promise物件,該物件有一個then方法,允許指定回撥函式。 在promise裡面主要作用就是將毀掉巢狀書寫為鏈式呼叫的方式,其中可以給then方法傳遞函式,將多個依賴非同步拆分,(方便維護,可擴充套件性)。對於需要同時得到結果的多個非同步,就可以使用Promise.all的方法
如果有錯誤或者不嚴謹的地方,請務必給予指正,十分感謝!