深入理解nextTick()

这篇文章主要讲一下nextTick()的使用,event loop,和vue中nextTick()的原理,以及在使用nextTick()的时候踩到的坑。作为我学习的记录。
首先,nextTick()的用法有两种:

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

两个方法的作用都是在DOM更新循环结束之后执行延迟回调。当我们改变了数据的时候,DOM的渲染需要时间,然而我们希望去操作DOM元素,就需要等待渲染完成后再去操作。就需要用到nextTick,将等待DOM渲染完成后需要的操作放在回调函数里。
不同的是,Vue.nextTick([callback, context])是全局的,使用vm.$nextTick([callback])时的回调会自动绑定到调用它的实例上。而这里文档中并没有说明全局的Vue.nextTick([callback, context])context参数是用来做什么的,后面我将通过源码的分析告诉大家这个参数的用法。

好,现在大家应该都知道nextTick是用来做什么的了。这个方法是怎么实现的呢?首先,需要理解一下Event loop。

Event loop

很多时候我们看到别人的代码里有这么一句setTimeout(fn, 0)。额,作为前端小白的我,觉得这段代码很神奇。延时0毫秒,不就是不用延时么,为什么还要这么写一句呢?这里其实就是Event loop的知识点。

首先,JavaScript是一个单线程的语言。
也就是说,在特定的时间只能是特定的代码被执行,要等待上一步的代码执行完成后在执行下一段代码。那么问题来了,如果上一段代码的请求需要等待很长时间,那么后面的代码就得给我等着,用户也得给我等着。最终,用户就会关掉浏览器走人。那我们今天的表演就结束了,欢迎收看,下期再见。
呵呵,其实,JavaScript除了主线程以外,还有一个叫做任务队列的东东。他会把一些需要一定等待时间的操作,放进任务队列里。

JavaScript的执行依靠函数调用栈和任务队列。
首先我们弄懂栈和队列的区别:
栈是先进后出,后进先出。
队列则相反,是先进先出。

函数执行栈

我们的js代码从上到下的执行,当一个函数被执行的时候,都会有一个执行上下文,全局环境也有一个执行上下文,就是全局的上下文。JavaScript将以栈的形式来存储他们。每执行一个函数,就把它上下文存入栈。栈的最底层就是全局上下文,栈顶就是当前正在执行的函数。每当一个函数执行结束,他的执行上下文就从栈中被弹出,释放。最底层的全局上下文,在浏览器关闭的时候才被弹出。

任务队列

任务队列有两种:macro-task(task)和micro-task(job)

macro-task(task):

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

micro-task(job):

  • process.nextTick
  • Promise
  • MutationObserve
注意:以上的方法的回调函数会被分发到执行队列中,而他们自身会被直接执行,比如Promise只有then()会被加入到执行队列中,而Promise本身会被直接执行。

JavaScript执行的机制是:首先执行调用栈中的函数,当调用栈中的执行上下文全部被弹出,只剩下全局上下文的时候,就开始执行job的执行队列,job的执行完以后就开始执行task的队列中的。先进入的先执行,后进入的后执行。无论是task还是job都是通过函数调用栈来执行。task执行完成一个,js代码会继续检查是否有job需要执行。就形成了task-job-task-job的循环(其实这里可以将第一次的函数调用栈也看成一个task)。这就形成了event loop.

好了,现在可以来看nextTick的实现原理了

  var nextTick = (function () {
    // 这里存放的是回调函数的队列
    var callbacks = [];
    var pending = false;
    var timerFunc;

    //这个函数就是DOM更新后需要执行的
    function nextTickHandler () {
      pending = false;
       //这里将回调函数copy给copies
      var copies = callbacks.slice(0);
      callbacks.length = 0;
      //进行循环执行回调函数的队列
      for (var i = 0; i < copies.length; i++) {
        copies[i]();
      }
  }
})()

vue用了三个方法来执行nextTickHandler函数,分别是:

  • 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])的第二个参数是什么呢?来看下面的代码。

  return function queueNextTick (cb, ctx) {
    var _resolve;
    callbacks.push(function () {
    //看这里,其实是可以给cb指定一个对象环境,来改变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')
  }
})

结果报错了,这是为什么呢?
源码中使用的是if (cb) { cb.call(ctx) } 所以不能使用箭头函数,箭头函数的this是固定的,是不可用apply,call,bind来改变的。改成这样:

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

OK

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容