衆所周知,隨着
Vue
技術的愈來愈熱,大量的前端開發者開始探究這門神奇的框架,筆者也是從JQuery
時代一腳邁進了Vue
的世界。談到Vue
,在這呢,就不得不提一下筆者在研究一個Vue
項目的時候碰到的問題,父組件修改標誌位變量,而子組件的相應組件並無顯示,後來經過多方研究,發現了Vue.nextTick
這個原型方法能夠達到我想要的這個效果,因此筆者今天也來談談這個神奇的方法。前端
參數node
{Function} [callback]
{Object} [context]
用法es6
在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。web
// 修改數據
vm.msg = 'Hello'
// DOM 尚未更新
Vue.nextTick(function () {
// DOM 更新了
})
// 做爲一個 Promise 使用 (2.1.0 起新增,詳見接下來的提示)
Vue.nextTick()
.then(function () {
// DOM 更新了
})
複製代碼
這裏其實涉及到
js
的事件循環機制,有興趣的話能夠右轉 js事件循環數組
具體使用場景各位小夥伴應該也不用筆者多囉嗦了,今天筆者的重點仍是研究一下這個東西源碼是怎麼實現的,畢竟做爲當代前端一員至少不能只會用 API
了,我們仍是去底層僞裝研究一下是吧。promise
其實話提及來,咱們就得來了解一下這個
js
是單線程的這個特性上來了,它其實全部事件的處理都依賴於這一個事件循環機制,,主線程的執行過程就是一個 tick,而全部的異步結果都是經過 「任務隊列」 來調度被調度,消息隊列中存放的是一個個的任務task
。 規範中規定task
分爲兩大類,分別是macro task
(宏任務) 和micro task
(微任務),而且每一個macro task
結束後,都要清空全部的micro task
。瀏覽器
回到正題,Vue.nextTick
怎麼實現當前頁面更新完以後最先執行它所綁定的回調呢,這就用到了咱們上面所說的這個任務隊列,每次當前宏任務執行完畢以前,都會清空全部微任務,那麼爲了在界面更新完以後最短期內執行回調,最佳選擇不就是這個微任務了麼,利用這個機制,咱們總能在下次事件循環以前把咱們要處理的事件處理掉。bash
常見的宏任務有
setTimeout
、MessageChannel``、postMessage
、setImmediate
閉包
微任務有
MutationObserver
和Promise.then
以及node
的process.nextTick
框架
固然,爲了程序的優化和性能提高,咱們的最佳選擇固然是 Promise
啦,但是呢,Promise
屬於es6
中提出的,部分瀏覽器可能出現不兼容的狀況 (PS: IE:你看我幹嗎?)
,因此官方就給了一個優雅降級策略,若是當前瀏覽器支持 Promise
則使用Promise
,其次就是MutationObserver
,若是以上兩個都不支持,就只能搬出咱們的setTimeout
了。話很少說,下面開始搬代碼。
//存儲須要觸發的回調函數
var callbacks=[];
/**是否正在等待的標誌(false:容許觸發在下次事件循環觸發callbacks中的回調,
* true: 已經觸發過,須要等到下次事件循環)
*/
var pending=false;
//設置在下次事件循環觸發callbacks的觸發函數
var timerFunc;
複製代碼
上面的這個timerFunc
將用於達到觸發條件後觸發全部回調函數
//處理callbacks的函數
function nextTickHandler() {
// 能夠觸發timeFunc
pending=false;
//複製callback
var copies=callbacks.slice(0);
//清除callback
callbacks.length=0;
for(var i=0;i<copies.length;i++){
//觸發callback的回調函數
copies[i]();
}
}
複製代碼
這部分代碼就是實現觸發全部綁定的回調函數的主要邏輯部分,下面咱們來看看官方的的優雅降級策略怎麼實現的
//若是支持promise,使用promise實現
if(typeof Promise !=='undefined' && isNative(promise)){
var p=Promise.resolve();
var logError=function (err) {
console.error(err);
};
timerFunc=function () {
p.then(nextTickHandler).catch(logError);
//iOS的webview下,須要強制刷新隊列,執行上面的回調函數
if(isIOS) {setTimeout(noop);}
};
// 若是Promise不支持,但支持MutationObserver
// H5新特性,異步,當dom變更是觸發,注意是全部的dom都改變結束後觸發
} else if (typeof MutationObserver !=='undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString()==='[object MutationObserverConstructor]')){
var counter = 1;
var observer=new MutationObserver(nextTickHandler);
var textNode=document.createTextNode(String(counter));
observer.observe(textNode,{
characterData:true
});
timerFunc=function () {
counter=(counter+1)%2;
textNode.data=String(counter);
};
} else {
//上面兩種都不支持,用setTimeout
timerFunc=function () {
setTimeout(nextTickHandler,0);
};
}
複製代碼
看完這段代碼,你們可能對官方的這個降級策略有了一種恍然大悟的感受,不過可能你們也會有疑問,這個MutationObserver的實現方式怎麼這麼詭異,那讓咱們來看看它的用法吧。
該構造函數用於實例化一個新的 MutaionObserver ,同時指定觸發 DOM 變更時的回調函數:
var observer = new MutationObserver(callback);
複製代碼
callback,即回調函數接收兩個參數,第一個參數是一個包含了全部 MutationRecord 對象的數組,第二個參數則是這個MutationObserver 實例自己。具體詳細介紹能夠參考 深刻了解MutationObserver。
咳咳咳,回到正題
//nextTick接收的函數,參數1:回調函數 參數2:回調函數的執行上下文
return function queueNextTick(cb,ctx) {
//用於接收觸發Promise.then中回調的函數
//向回調函數中pushcallback
var _resolve;
callbacks.push(function () {
//若是有回調函數,執行回調函數
if(cb) {cb.call(ctx);}
//觸發Promise的then回調
if(_resolve) {_resolve(ctx);}
});
//是否執行刷新callback隊列
if(!pending){
pending=true;
timerFunc();
}
//若是沒有傳遞迴調函數,而且當前瀏覽器支持promise,使用promise實現
if(!cb && typeof Promise !=='undefined'){
return new Promise(function (resolve) {
_resolve=resolve;
})
}
}
複製代碼
以上其實就是你調用這個方法實際調用的函數啦,利用閉包原理保存了前面提到的各個函數的引用,首先他會把你傳入的回調函數包裝一下保存到callback
數組中。
若是當前隊列還未執行過回調,那麼開始執行回調,並把pending
標誌位置爲true
,表示當前任務隊列已經執行過回調。
而後最後加一層判斷,若是當前瀏覽器具備Promise
環境且未傳遞迴調函數則採用Promise
執行。
最後附上完整代碼
export const nextTick=(function () {
//存儲須要觸發的回調函數
var callbacks=[];
//是否正在等待的標誌(false:容許觸發在下次事件循環觸發callbacks中的回調,
// true: 已經觸發過,須要等到下次事件循環)
var pending=false;
//設置在下次事件循環觸發callbacks的觸發函數
var timerFunc;
//處理callbacks的函數
function nextTickHandler() {
// 能夠觸發timeFunc
pending=false;
//複製callback
var copies=callbacks.slice(0);
//清除callback
callbacks.length=0;
for(var i=0;i<copies.length;i++){
//觸發callback的回調函數
copies[i]();
}
}
//若是支持promise,使用promise實現
if(typeof Promise !=='undefined' && isNative(promise)){
var p=Promise.resolve();
var logError=function (err) {
console.error(err);
};
timerFunc=function () {
p.then(nextTickHandler).catch(logError);
//iOS的webview下,須要強制刷新隊列,執行上面的回調函數
if(isIOS) {setTimeout(noop);}
};
// 若是Promise不支持,但支持MutationObserver
// H5新特性,異步,當dom變更是觸發,注意是全部的dom都改變結束後觸發
} else if (typeof MutationObserver !=='undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString()==='[object MutationObserverConstructor]')){
var counter = 1;
var observer=new MutationObserver(nextTickHandler);
var textNode=document.createTextNode(String(counter));
observer.observe(textNode,{
characterData:true
});
timerFunc=function () {
counter=(counter+1)%2;
textNode.data=String(counter);
};
} else {
//上面兩種都不支持,用setTimeout
timerFunc=function () {
setTimeout(nextTickHandler,0);
};
}
//nextTick接收的函數,參數1:回調函數 參數2:回調函數的執行上下文
return function queueNextTick(cb,ctx) {
//用於接收觸發Promise.then中回調的函數
//向回調函數中pushcallback
var _resolve;
callbacks.push(function () {
//若是有回調函數,執行回調函數
if(cb) {cb.call(ctx);}
//觸發Promise的then回調
if(_resolve) {_resolve(ctx);}
});
//是否執行刷新callback隊列
if(!pending){
pending=true;
timerFunc();
}
//若是沒有傳遞迴調函數,而且當前瀏覽器支持promise,使用promise實現
if(!cb && typeof Promise !=='undefined'){
return new Promise(function (resolve) {
_resolve=resolve;
})
}
}
})()
複製代碼
好啦本文暫時介紹到這裏,若是發現筆者寫的不對的地方,歡迎給筆者留言。