最开始查看nextTick这个方法的时候,眼瞎看成了nextClick。。。我还在疑问难道是下一次click之后处理事件。。。

然后用这个方法的时候,就只知道是用在DOM更新之后调用回调方法。

这时就产生了一堆疑问:

1)DOM更新后?难道修改数据之后,DOM没有及时更新,还有延迟?但是页面上看到的就是实时更新呀,难道还有什么猫腻?

2)它是怎么监听到DOM被更新了

3)它和异步的setTimeout、setInterval有没有关系?

深入了解后才发现里面有大学问。。。

在理解nextTick之前,先来一段代码

setTimeout(function(){
console.log(11)
},300)

这段代码很简单,一般人都会说,300ms之后控制台打印出11。

但是,一定是精确的300ms之后马上打印出11吗。答案是不一定。为什么?这就涉及到下面的知识点

1. js为什么是单线程

深究原因我不是很清楚,但是我是这样理解的:假如js是多线程,意思是如果我对同一个DOM进行操作,那么都会同时处理。那这时一个线程我对一个按钮修改颜色为red,同时另外一个线程对这个按钮修改颜色为blue。那浏览器到底是执行哪一个呢,这样就矛盾了。所以这就能很好理解为什么要设计成单线程了。

2. Event loop

既然是单线程,那么事件任务就一定会在主线程上排队执行。同一时间就只能按队列执行一个方法。要是某个方法要花费很长时间,那后面的方法就只能等待了,这是极其不能忍受的。所以js设计者把任务分成了同步任务和异步任务。同步任务即主线程(执行栈)上运行的任务,而异步任务则是挂载到一个任务队列里面。等待主线程的所有任务执行完成后(栈空),通知任务队列可以把可执行的任务放到主线程里面执行。异步任务放到主线程中执行完后,栈又空了,又通知任务队列把异步任务放到主线程中执行。这个过程一直持续,直到异步任务执行完成,这个持续重复的过程就叫Event loop。而一次循环就是一次tick

注意:

1) 这里异步任务例如setTimeout这种,实际上是先由浏览器其它模块(应该是IO设备)处理之后,它的回调函数才再加入到任务队列里面。注意是回调函数。

2) onclick,onmouseover等都属于异步任务。回调都会挂载到任务队列。

3. microtast(微任务)和macrotask(宏任务)

任务队列里面异步任务也分macrotast(标准说法是task)和microtast(标准说法中它是不属于task的,应该叫 job)。

典型的microtast包含:Promises(浏览器原生Promise)、MutationObserver、Object.observe(已废弃)、以及nodejs中的process.nextTick,UI rendering(UI渲染)

典型的macrotast包含:script整体代码(这个很重要)、setTimeout(最短4ms) 、 setInterval(最短10ms)、MessageChannel、以及只有 IE 支持的 setImmediate。

执行优先级上,先执行宏任务macrotask,再执行微任务mincrotask

process.nextTick > Promise.then > MutationObserver > setImmediate > setTimeout。

注意:

1) 对于microtast和macrotask,这两个在一次event loop中,microtask在这一次循环中是一直取一直取,直到清空microtask队列,而macrotask则是一次循环取一次。

2) 相当于事件循环的过程是:主线程(栈空)--->取一个macrotask执行---->查看有没有microtask,如果有就执行该任务直到清空microtask队列,然后执行下一个macrotask任务--->又取macrotask执行--->清空microtask里面的任务 。重复第二和第三的步骤直到macrotask任务队列也执行完毕

3) 如果执行事件循环的过程中又加入了异步任务,如果是macrotask,则放到macrotask末尾,等待下一轮循环再执行。如果是microtask,则放到本次event loop中的microtask任务末尾继续执行。直到microtask队列清空。

4) 为什么宏任务先执行,反而处理时间还比微任务慢呢?因为script整体也是macrotask,就先把script里面的代码放到主线程执行,如果再遇到macrotask,就把它放到macrotask任务队列末尾,由于一次event loop只能取一个macrotask放到主线程执行,所以遇到的宏任务就需要等待其它轮次的事件循环了;如果遇到microtask,则放到本次循环的microtask队列中去,等待主线程执行完再执行microtast队列并清空。然后继续执行下一个tick的宏任务。

到这里,上面那个300ms的定时器为什么不一定是精确的300ms之后打印就能理解了:

因为300ms的setTimeout并不是说300ms之后立马执行,而是300ms之后被放入任务列表里面。等待事件循环,等待回调函数放到主线程执行的时候才能执行代码。如果异步任务列表里面只有它这个macrotask任务,那么就是精确的300ms。但是如果 还有microtast等其它的任务,就不止300ms了。

所以,下面的代码也能很好理解了

for(var i = 0; i < 3; i++) {
console.log("for:"+i);
var time=setTimeout(function() {
console.log("setTime:"+i);
}, 300);
  console.log(time)
}

这个运行的结果是:

1) 当执行for循环的时候,定义了3个定时器,由于setTimeout是异步任务,所以这三个定时器的回调函数,每个都会在300ms之后加入任务队列,并且是macrotask队列。

2) 此时执行代码,输出for:xx,并打印对应定时器的标识。

3) 300ms之后,每个setTimeout的回调函数加入到任务队列,这时候for循环早就执行完毕了。

4) 执行完循环之后,此时相当于主线程栈空了,通知任务队列,把异步任务放到主线程执行,这时候就开始执行setTimeout的回调函数。由于这时setTimeout匿名回调函数保持对外部变量 i 的引用,而此时的 i 由于主线程执行完之后变成了3,所以最终再打印出3个setTime:3。

再来分析一下下面的代码:

console.log(1);
setTimeout(function(){
console.log(2)
},0);
new Promise(function(resolve){
console.log(3)
for( var i=100 ; i>0 ; i-- ){
i==1 && resolve()
}
console.log(4)
}).then(function(){
console.log(5)
}).then(function(){
   console.log(6)
});
console.log(7);

1) 由于script也属于macrotask,所以整个script里面的内容都放到了主线程(任务栈)中,按顺序执行代码。然后遇到console.log(1),直接打印1。

2) 遇到setTimeout,表示在0秒后才加入任务队列,根据第3大点的 第3点注意事项,这个setTimeout会被放到下一个事件循环的macrotask里面,这次event loop不会执行。

3) 执行遇到new Promise,new Promise在实例化的过程中所执行的代码都是同步进行的,只有回调 .then()才是microtask。所以先直接打印3,执行完循环,然后再打印4。然后遇到第一个 .then(),属于microtask,加入到本次循环的microtask队列里面。接着向下执行又遇到一个 .then() ,又加入到本次循环的microtask队列里面。然后继续向下执行。

4) 遇到console.log(7),直接打印7。直到此时,一个事件循环的macrotask执行完成,然后去查看此次循环是否还有microtask,发现还有刚才的  .then() ,立即放到主线程执行,打印出5。然后发现还有第二个 .then(),立即放到主线程执行,打印出6 。此时microtask任务列表清空完了。到此第一次循环完成。

5) 第二次事件循环,从macrotask任务列表里面找到了第一次放进的setTimeout,放到主线程执行,打印出2。

6) 所以最终的结果就是  1 3 4 7 5 6 2


上面说了这么多,就是为了下面做铺垫

vue的nextTick使用方法:

接收两个参数:

第一个是回调函数,即DOM更新之后需要做的操作。

第二个是回调函数中,this指针的指向。

vue.nextTick(cb,obj)

vm.$nextTick(cb)。 注意实例中使用nextTick的时候,cb回调函数的this指向已经绑定为当前实例了。

这里附上vue 2.6 版本 nextTick源码的链接nextTick,2.5版本与2.6有些不一样。

export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => { //第一步
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) { //第二步
pending = true
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') { //第三步
return new Promise(resolve => {
_resolve = resolve
})
}
}

每次调用 Vue.nextTick(cb) :

1)cb 函数经处理压入 callbacks 数组,并且指定了cb的this指向。

2)pending表示是否正在执行回调即是否已经有异步任务在主线程执行,由于pending这个标识最初为false,所以把它设置为true,然后调用 timerFunc()。这个是用来触发异步回调函数的。

3)如果没有传入回调函数,并且支持promise的时候,则返回一个promise的调用

4)timerFunc()最初就看Promise(延迟调用) 、MutationObserver(监听变化)、setImmediate 、setTimeout这四个中谁的兼容当前浏览器,谁就优先用来做异步API来处理回调函数。

对于为什么是下一个tick,我有问题:

1)在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。这是官方对于nextTick的说法。

2)在设置了vm.xxx='xxx'的时候,如果立即去DOM的内容,获取到的并不是最新的值,说明DOM的更新一定是异步的,因为同步的话就能获取到修改后的内容了。但是nextTick的回调函数,在调用后要么属于microtask,要么就是macrotask,

3)如果是macrotask则好理解一点,因为执行代码遇到这个macrotask则会被添加到macrotask的末尾,等待event loop 取到它的时候才执行,而执行一次macrotask之后,如果microtask列表为空了,就会执行UI rendering,页面就渲染成最新的内容。这时候是能获取到更新后的内容的。

4)那如果是microtask,就是在当前event loop中需要执行完毕,是属于当前的tick,而这个时候是怎么获取到DOM更新的内容的???

对于上面的这个问题,好像要涉及到 watcher 中的 update 和 queueWatcher 。暂时就先放到一边。反正作用是搞懂了,原理还差一点。

如果有明白这个问题的,麻烦给我讲解一下。先谢谢了。

vue---由nextTick原理引出的js执行机制的更多相关文章

  1. JS执行机制详解,定时器时间间隔的真正含义

     壹 ❀ 引 通过结果倒推过程是我们常用的思考模式,我在上一篇学习promise笔记中,有少量关于promise执行顺序的例子,通过倒推,我成功让自己对于js执行机制的理解一塌糊涂,js事件机制,事件 ...

  2. JS学习笔记:(三)JS执行机制

    首先我们先明确一点:JavaScript是一门单线程语言.单线程也就是说同一时间只能执行一个任务,所有的任务都必须排队顺序执行.那么如果一个任务耗时很长,阻塞了其它任务的执行,就会给用户造成不友好的体 ...

  3. 浅谈js执行机制

    关于js执行机制,老早之前就一直想写篇文章做个总结,因为和js执行顺序的面试题碰到的特别多,每次碰到总是会去网上查,没有系统地总结,搞得每次碰到都是似懂非懂的感觉,这篇文章就系统的总结一下js执行机制 ...

  4. 从一道看似简单的面试题重新理解JS执行机制与定时器

     壹 ❀ 引 最近在看前端进阶的系列专栏,碰巧看到了几篇关于JS事件执行机制的面试文章,因为我在之前一篇 JS执行机制详解,定时器时间间隔的真正含义 博文中也有记录JS执行机制,所以正好用于作为测试自 ...

  5. 浏览器中js执行机制学习笔记

    浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...

  6. js执行机制

    js是单线程的,为什么可以执行异步操作呢? 这归结与浏览器(js的宿主环境)通过某种方式使得js具备了异步的属性. 区分进程和线程: 进程:正在运行中的应用程序.每个进程都自己独立的内存空间.例如:打 ...

  7. JS 执行机制笔记

        js同步和异步同步 前一个任务结束以后再执行下面一个任务,程序的执行顺序与任务的排列顺序是一致的 同步任务都在主线程上执行,形成一个执行线 异步 前一个任务没结束之前程序还可以执行别的任务 j ...

  8. Vue this.$nextTick原理

    虽然 Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们确实要这么做.比如一个新闻滚动的列表项.如果在这里需要操作dom, 应该是等待 Vue 完成更新 DO ...

  9. 简单而面试中又常见的知识点:JS执行机制

        在开始讲解之前,我们先来看一段代码: console.log('1'); setTimeout(function() { console.log('2'); process.nextTick( ...

随机推荐

  1. python开发规范和(configparser、random模块)

    目录结构: bin:存放程序入口,程序启动文件. conf:存放配置文件,配置文件主要是一些全局变量,路径信息等. core:程序核心文件,不涉及到业务逻辑. app:存放和系统业务相关的逻辑. db ...

  2. 使用laravel-admin后台sdk报错Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID、Provisional headers are shown

    报错Failed to load resource: net::ERR_CERT_AUTHORITY_INVALID请先确定自己的资源url是否可以确实访问到(地址正确与否.访问权限是否开启等) 若n ...

  3. 安装mysql的踩坑之旅

    近期的一个项目要求用mysql数据库,正好系统重装了,复习下mysql的安装,哪成想是踩了无数坑啊! 要安装首先自然是火速进官网下个安装包(下载地址https://dev.mysql.com/down ...

  4. 【Python 08】汇率兑换2.0-1(字符串索引)

     1.案例描述 设计一个汇率换算程序,其功能是将人民币转换为美元,或者美元转换为人民币. 增加功能:根据输入判断是人民币还是美元,进行相应的转换计算. 2.案例分析 3.字符串 两个双引号或单引号括起 ...

  5. LOJ #2542. 「PKUWC 2018」随机游走(最值反演 + 树上期望dp + FMT)

    写在这道题前面 : 网上的一些题解都不讲那个系数是怎么推得真的不良心 TAT (不是每个人都有那么厉害啊 , 我好菜啊) 而且 LOJ 过的代码千篇一律 ... 那个系数根本看不出来是什么啊 TAT ...

  6. MySQL数据库引擎类别和更换方式

    MySQL数据库引擎类别 能用的数据库引擎取决于mysql在安装的时候是如何被编译的.要添加一个新的引擎,就必须重新编译MYSQL.在缺省情况下,MYSQL支持三个引擎:ISAM.MYISAM和HEA ...

  7. Django路由(url)

    1.基本配置 from django.conf.urls import url from . import views urlpatterns = [ url(r'^articles/2003/$', ...

  8. Facebook第三方网页登录(JavaScript SDK)

    文档网址:https://developers.facebook.com/docs/facebook-login/web#logindialog 一.应用配置  https://www.faceboo ...

  9. 技术趋势:React vs Vue vs Angular

    React.Vue 和 Angular 这两年发展状况如何?2019 年哪个技术最值得学习? 前几天 Medium 上有一位作者发表了一篇关于 React.Vue 和 Angular 技术趋势的文章( ...

  10. Equinox OSGi应用嵌入Jersey框架搭建REST服务

    原文地址:https://www.cnblogs.com/kira2will/p/5040264.html 一.环境 eclipse版本:eclipse-luna 4.4 jre版本:1.8 二.Eq ...