深入理解nextTick()

這篇文章主要講一下nextTick()的使用七冲,event loop亲配,和vue中nextTick()的原理蔓涧,以及在使用nextTick()的時候踩到的坑构灸。作為我學(xué)習(xí)的記錄上渴。
首先,nextTick()的用法有兩種:

  1. Vue.nextTick([callback, context])
  2. vm.$nextTick([callback])

兩個方法的作用都是在DOM更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)喜颁。當我們改變了數(shù)據(jù)的時候稠氮,DOM的渲染需要時間,然而我們希望去操作DOM元素半开,就需要等待渲染完成后再去操作隔披。就需要用到nextTick,將等待DOM渲染完成后需要的操作放在回調(diào)函數(shù)里寂拆。
不同的是奢米,Vue.nextTick([callback, context])是全局的抓韩,使用vm.$nextTick([callback])時的回調(diào)會自動綁定到調(diào)用它的實例上。而這里文檔中并沒有說明全局的Vue.nextTick([callback, context])context參數(shù)是用來做什么的鬓长,后面我將通過源碼的分析告訴大家這個參數(shù)的用法谒拴。

好,現(xiàn)在大家應(yīng)該都知道nextTick是用來做什么的了涉波。這個方法是怎么實現(xiàn)的呢英上?首先,需要理解一下Event loop啤覆。

Event loop

很多時候我們看到別人的代碼里有這么一句setTimeout(fn, 0)苍日。額,作為前端小白的我城侧,覺得這段代碼很神奇易遣。延時0毫秒彼妻,不就是不用延時么嫌佑,為什么還要這么寫一句呢?這里其實就是Event loop的知識點侨歉。

首先屋摇,JavaScript是一個單線程的語言。
也就是說幽邓,在特定的時間只能是特定的代碼被執(zhí)行炮温,要等待上一步的代碼執(zhí)行完成后在執(zhí)行下一段代碼。那么問題來了牵舵,如果上一段代碼的請求需要等待很長時間柒啤,那么后面的代碼就得給我等著,用戶也得給我等著畸颅。最終担巩,用戶就會關(guān)掉瀏覽器走人。那我們今天的表演就結(jié)束了没炒,歡迎收看涛癌,下期再見。
呵呵送火,其實拳话,JavaScript除了主線程以外,還有一個叫做任務(wù)隊列的東東种吸。他會把一些需要一定等待時間的操作弃衍,放進任務(wù)隊列里。

JavaScript的執(zhí)行依靠函數(shù)調(diào)用棧和任務(wù)隊列坚俗。
首先我們弄懂棧和隊列的區(qū)別:
棧是先進后出笨鸡,后進先出姜钳。
隊列則相反,是先進先出形耗。

函數(shù)執(zhí)行棧

我們的js代碼從上到下的執(zhí)行哥桥,當一個函數(shù)被執(zhí)行的時候,都會有一個執(zhí)行上下文激涤,全局環(huán)境也有一個執(zhí)行上下文拟糕,就是全局的上下文。JavaScript將以棧的形式來存儲他們倦踢。每執(zhí)行一個函數(shù)送滞,就把它上下文存入棧。棧的最底層就是全局上下文辱挥,棧頂就是當前正在執(zhí)行的函數(shù)犁嗅。每當一個函數(shù)執(zhí)行結(jié)束,他的執(zhí)行上下文就從棧中被彈出晤碘,釋放褂微。最底層的全局上下文,在瀏覽器關(guān)閉的時候才被彈出园爷。

任務(wù)隊列

任務(wù)隊列有兩種:macro-task(task)和micro-task(job)

macro-task(task):

  • setTimeout/setInterval
  • setImmediate
  • I/O操作
  • UI rendering

micro-task(job):

  • process.nextTick
  • Promise
  • MutationObserve
注意:以上的方法的回調(diào)函數(shù)會被分發(fā)到執(zhí)行隊列中宠蚂,而他們自身會被直接執(zhí)行,比如Promise只有then()會被加入到執(zhí)行隊列中童社,而Promise本身會被直接執(zhí)行求厕。

JavaScript執(zhí)行的機制是:首先執(zhí)行調(diào)用棧中的函數(shù),當調(diào)用棧中的執(zhí)行上下文全部被彈出扰楼,只剩下全局上下文的時候呀癣,就開始執(zhí)行job的執(zhí)行隊列,job的執(zhí)行完以后就開始執(zhí)行task的隊列中的弦赖。先進入的先執(zhí)行项栏,后進入的后執(zhí)行。無論是task還是job都是通過函數(shù)調(diào)用棧來執(zhí)行腾节。task執(zhí)行完成一個忘嫉,js代碼會繼續(xù)檢查是否有job需要執(zhí)行。就形成了task-job-task-job的循環(huán)(其實這里可以將第一次的函數(shù)調(diào)用棧也看成一個task)案腺。這就形成了event loop.

好了庆冕,現(xiàn)在可以來看nextTick的實現(xiàn)原理了

  var nextTick = (function () {
    // 這里存放的是回調(diào)函數(shù)的隊列
    var callbacks = [];
    var pending = false;
    var timerFunc;

    //這個函數(shù)就是DOM更新后需要執(zhí)行的
    function nextTickHandler () {
      pending = false;
       //這里將回調(diào)函數(shù)copy給copies
      var copies = callbacks.slice(0);
      callbacks.length = 0;
      //進行循環(huán)執(zhí)行回調(diào)函數(shù)的隊列
      for (var i = 0; i < copies.length; i++) {
        copies[i]();
      }
  }
})()

vue用了三個方法來執(zhí)行nextTickHandler函數(shù),分別是:

  • Promise
//當瀏覽器支持Promise的時候就是用Promise
p.then(nextTickHandler).catch(logError);
  • MutationObserver
//當瀏覽器支持MutationObserver的時候就是用MutationObserver
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);
  };
  • setTimeout
//當以上都不支持的時候就用setTimeout
setTimeout(nextTickHandler, 0);

那么Vue.nextTick([callback, context])的第二個參數(shù)是什么呢?來看下面的代碼劈榨。

  return function queueNextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
    //看這里访递,其實是可以給cb指定一個對象環(huán)境,來改變cb中this的指向
      if (cb) { cb.call(ctx); }
      if (_resolve) { _resolve(ctx); }
    });
    if (!pending) {
      pending = true;
      timerFunc();
    }
    if (!cb && typeof Promise !== 'undefined') {
      return new Promise(function (resolve) {
        _resolve = resolve;
      })
    }
  }

看到代碼后同辣,我開心的這么寫道

Vue.nextTick(()=>{
    this.text()
}, { 
  text(){
    console.log('test')
  }
})

結(jié)果報錯了拷姿,這是為什么呢惭载?
源碼中使用的是if (cb) { cb.call(ctx) } 所以不能使用箭頭函數(shù),箭頭函數(shù)的this是固定的响巢,是不可用apply,call,bind來改變的描滔。改成這樣:

Vue.nextTick(function () {
    this.text()
}, { 
  text(){
    console.log('test')
  }
})

OK

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踪古,隨后出現(xiàn)的幾起案子含长,更是在濱河造成了極大的恐慌,老刑警劉巖伏穆,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拘泞,死亡現(xiàn)場離奇詭異,居然都是意外死亡枕扫,警方通過查閱死者的電腦和手機陪腌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烟瞧,“玉大人诗鸭,你說我怎么就攤上這事⊙嗫蹋” “怎么了只泼?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵剖笙,是天一觀的道長卵洗。 經(jīng)常有香客問我,道長弥咪,這世上最難降的妖魔是什么过蹂? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮聚至,結(jié)果婚禮上酷勺,老公的妹妹穿的比我還像新娘。我一直安慰自己扳躬,他們只是感情好脆诉,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贷币,像睡著了一般击胜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上役纹,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天偶摔,我揣著相機與錄音,去河邊找鬼促脉。 笑死辰斋,一個胖子當著我的面吹牛策州,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宫仗,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼够挂,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了藕夫?” 一聲冷哼從身側(cè)響起下硕,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎汁胆,沒想到半個月后梭姓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡嫩码,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年誉尖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片铸题。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡铡恕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丢间,到底是詐尸還是另有隱情探熔,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布烘挫,位于F島的核電站诀艰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏饮六。R本人自食惡果不足惜其垄,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卤橄。 院中可真熱鬧绿满,春花似錦、人聲如沸窟扑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽嚎货。三九已至橘霎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間厂抖,已是汗流浹背茎毁。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人七蜘。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓谭溉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親橡卤。 傳聞我的和親對象是個殘疾皇子扮念,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容