vue异步更新原理,vue 异步数据更新

  vue异步更新原理,vue 异步数据更新

  本文主要介绍vue源代码批量异步更新策略的相关信息。vue异步更新是我们在日常开发中经常遇到的一个功能。有需要的朋友可以参考一下。

  vue异步更新源代码中有与事件循环、宏任务、微任务相关的概念,我们先来看看这些概念。

  

一、事件循环、宏任务、微任务

  1.事件循环(Event Loop):浏览器定制的工作机制,用于协调事件处理、脚本执行、网络请求和渲染等任务。

  2.宏任务任务:表示离散和独立的工作单元。当浏览器完成一个宏任务时,它将在下一个宏任务开始之前重新呈现页面。主要包括创建文档对象、解析HTML、执行主线JS代码以及页面加载、输入、网络事件、定时器等各种事件。

  3.微任务:微任务是一个较小的任务,在当前宏任务执行后立即执行。如果有微任务,浏览器会在完成微任务后重新渲染。微任务的例子有Promise回调函数、DOM改变等。

  执行过程:宏任务完成=微任务执行=页面重新渲染=新一轮宏任务执行。

  任务执行顺序示例:

  //第一个宏任务进入主线程

  console . log(“1”);

  //扔进宏事件队列

  setTimeout(function() {

  console . log(“2”);

  process.nextTick(function() {

  console . log(“3”);

  })

  新承诺(功能(解决){

  console . log(“4”);

  resolve();

  }).then(function() {

  console.log(5 )

  })

  })

  //微事件1

  process.nextTick(function() {

  console.log(6 )。

  })

  //主线程直接执行

  新承诺(功能(解决){

  console . log( 7 );

  resolve();

  }).then(function() {

  //微事件2

  console.log(8 )

  })

  //扔进宏事件队列

  setTimeout(function() {

  console . log( 9 );

  process.nextTick(function() {

  console . log( 10 );

  })

  新承诺(功能(解决){

  console . log( 11 );

  resolve();

  }).then(function() {

  console.log(12 )

  })

  })

  //1,7,6,8,2,4,3,5,9,11,10,12

  解析:

  第一个宏任务

  第一个宏任务进入主线程,打印1。

  SetTimeout被抛出到宏任务队列中

  Process.nextTick被抛出到微任务队列中

  新承诺直接执行,打印7

  Promise then事件被扔进微任务队列。

  SetTimeout被抛出到宏任务队列中

  当第一个宏任务完成后,开始微任务。

  执行process.nextTick,print 6

  执行承诺然后事件,打印8

  微任务执行完毕,清空微任务队列,渲染页面,进入下一个宏任务setTimeout。

  执行打印2

  Process.nextTick被抛出到微任务队列中

  新承诺直接执行,打印4

  Promise then事件被扔进微任务队列。

  当第二个宏任务完成时,微任务开始。

  执行process.nextTick,print 3

  执行承诺然后事件,打印5

  微任务执行完毕后,清空微任务的队列,渲染页面,进入下一个宏任务setTimeout,重复上述类似过程,打印出9,11,10,12。

  

二、Vue异步批量更新过程

  1.解析:当检测到数据变化时,vue会打开一个队列,在队列中存储相关的watcher,在callbacks队列中存储回调函数,异步执行回调函数,遍历watcher队列进行渲染。

  异步:vue在更新DOM时异步执行。只要感知到数据变化,Vue就会打开一个队列,缓冲在同一个事件周期中发生的所有数据变化。

  Batch:如果同一个watcher被多次触发,那么它只会被推入队列一次。以避免不必要的计算和DOM操作。然后,在下一个事件循环“tick”中,vue刷新队列以执行实际工作。

  异步策略:Vue内部尝试使用原生Promise.then,对异步队列使用MutationObserver和setImmediate。如果执行环境不支持,它将使用setTimeout(fn,0)来代替。也就是我们会尝试先用微任务,再用宏任务。

  异步批量更新流程图:

  

三、vue批量异步更新源码

  异步更新:整个过程相当于将臭袜子放到盆子里,最后一起洗。

  1.当一个数据更新时,会依次执行以下代码:

  (1)触发Data.set()

  (2)调用dep.notify():遍历所有相关的观察者,调用watcher.update()。

  核心/观察者/索引。js:

  通知(){

  const subs=this.subs.slice()

  //如果未运行异步,则不会在调度程序中对潜水艇进行排序

  if (process.env.NODE_ENV!==生产!config.async) {

  //排序,确保它们按正确的顺序执行

  subs.sort((a,b)=a.id - b.id)

  }

  //遍历相关观察者,并调用看守人更新

  对于(设i=0,l=subs.lengthI li ) {

  分句[我].更新()

  }

  }

  (3)执行watcher.update():判断是立即更新还是异步更新。若为异步更新,调用队列观察器(这个),将看守人入队,放到后面一起更新。

  核心/观察者/观察者。js:

  update () {

  /*伊斯坦布尔忽略else */

  如果(this.lazy) {

  this.dirty=true

  } else if (this.sync) {

  //立即执行渲染

  this.run()

  }否则{

  //观察器入队操作,后面一起执行渲染

  队列观察器(这个)

  }

  }

  (4)执行队列观察器(这个):观察器进行去重等操作后,添加到队列中,调用nextTick(flushSchedulerQueue)执行异步队列,传入回调函数flushSchedulerQueue。

  核心/观察者/调度程序。js:

  函数queueWatcher (watcher: Watcher) {

  //有标识,判断该看守人是否已在,避免在一个队列中添加相同的看守人

  const id=watcher.id

  如果(有[id]==null) {

  has[id]=true

  //冲洗标识,处理看守人渲染时,可能产生的新观察者。

  如果(!冲洗){

  //将当前看守人添加到异步队列

  队列.推送(观察器)

  }否则{

  //产生新的看守人就添加到排序的位置

  设i=queue.length - 1

  而(我索引队列[我].id watcher.id) {

  异

  }

  queue.splice(i 1,0,观察器)

  }

  //将刷新排队

  //正在等待标识,让所有的看守人都在一个滴答声内进行更新。

  如果(!等待){

  等待=真

  if (process.env.NODE_ENV!==生产!config.async) {

  flushSchedulerQueue()

  返回

  }

  //执行异步队列,并传入回调

  nextTick(flushSchedulerQueue)

  }

  }

  }

  (5)执行下一个节拍(cb):将传进去的flushSchedulerQueue函数处理后添加到复试队列中,调用定时器功能启动异步执行任务。

  core/util/next-tick.js:

  函数nextTick (cb?函数,ctx?对象){

  让_解决

  //此处的复试就是队列(回调数组),将传入的flushSchedulerQueue方法处理后添加到回调数组

  callbacks.push(()={

  if (cb) {

  尝试{

  cb。呼叫(ctx)

  } catch (e) {

  handleError(e,ctx, nextTick )

  }

  } else if (_resolve) {

  _解析(ctx)

  }

  })

  如果(!待定){

  待定=真

  //启动异步执行任务,此方法会根据浏览器兼容性,选用不同的异步策略

  定时器函数()

  }

  //$流量-禁用-线路

  如果(!可换股债券类型的承诺!==未定义){

  返回新承诺(resolve={

  _resolve=解决

  })

  }

  }

  (6)timerFunc():根据浏览器兼容性,选用不同的异步方式去执行刷新回调。由于宏任务耗费的时间是大于微任务的,所以先选用微任务的方式,都不行时再使用宏任务的方式,

  core/util/next-tick.js:

  让计时器运行

  //支持承诺则使用承诺异步的方式执行刷新回调

  如果(类型的承诺!==未定义是Native(Promise)) {

  const p=Promise.resolve()

  timerFunc=()={

  p .然后(刷新回调)

  如果(isIOS)设置超时(noop)

  }

  isUsingMicroTask=true

  } else if(!这是一种变异观察者!==未定义(

  isNative(变异观察器)

  变异观察者。tostring()===[对象变异观察器构造函数]

  )) {

  让计数器=1

  const observer=新的变异观察器(刷新回调)

  const textNode=document。创建textNode(String(counter))

  observer.observe(textNode,{

  characterData: true

  })

  timerFunc=()={

  计数器=(计数器1) % 2

  textNode.data=String(计数器)

  }

  isUsingMicroTask=true

  } else if (typeof setImmediate!==未定义是Native(setImmediate)) {

  timerFunc=()={

  setImmediate(flushCallbacks)

  }

  }否则{

  //实在不行再使用定时器的异步方式

  timerFunc=()={

  setTimeout(flushCallbacks,0)

  }

  }

  (7)刷新回调:异步执行复试队列中所有函数

  core/util/next-tick.js:

  //循环复试队列,执行里面所有函数flushSchedulerQueue,并清空队列

  函数flushCallbacks () {

  待定=假

  const copies=callbacks.slice(0)

  回调长度=0

  对于(设I=0;一份,长度;i ) {

  份数[我]()

  }

  }

  (8)flushSchedulerQueue():遍历看守人队列,执行watcher.run()

  watcher.run():真正的渲染

  函数flushSchedulerQueue() {

  currentFlushTimestamp=get now();

  flushing=true

  让观察者,id;

  //排序,先渲染父节点,再渲染子节点

  //这样可以避免不必要的子节点渲染,如:父节点中控制显示为错误的的子节点,就不用渲染了

  queue.sort((a,b)=a . id-b . id);

  //不缓存长度,因为可能会推送更多的观察器

  //当我们运行现有的观察器时

  //遍历所有看守人进行批量更新。

  对于(索引=0;索引队列长度;索引){

  watcher=queue[index];

  if (watcher.before) {

  观察者。之前();

  }

  id=watcher.id

  has[id]=null;

  //真正的更新函数

  观察者。run();

  //在开发版本中,检查并停止循环更新。

  if (process.env.NODE_ENV!==生产有[id]!=null) {

  circular[id]=(circular[id] 0)1;

  if(circular[id]MAX _ UPDATE _ COUNT){

  警告(

  您可能会有一个无限更新循环

  (观察者。用户

  ?`在带有表达式" ${watcher.expression} "的观察器中

  :`在组件渲染函数中.`),

  watcher.vm

  );

  打破;

  }

  }

  }

  //在重置状态之前保留发布队列的副本

  const activated queue=activated children。slice();

  const更新队列=queue。slice();

  resetSchedulerState();

  //调用组件更新并激活挂钩

  callactivatedbooks(激活队列);

  callUpdatedHooks(更新队列);

  //devtool挂钩

  /*伊斯坦布尔忽略if */

  if (devtools config.devtools) {

  开发工具。发出(“flush”);

  }

  }

  (9)更新组件():watcher。运行()经过一系列的转圈,执行更新组件,更新组件中执行render(),让组件重新渲染,再执行_update(vnode),再执行补丁()更新界面。

  (10)_更新():根据是否有虚拟节点分别执行不同的补丁。

  

四、Vue.nextTick(callback)

  1.Vue.nextTick(回调)作用:获取更新后的真正的数字正射影像图元素。

  由于某视频剪辑软件在更新数字正射影像图时是异步执行的,所以在修改数据之后,并不能立刻获取到修改后的数字正射影像图元素。为了获取到修改后的数字正射影像图元素,可以在数据变化之后立即使用Vue.nextTick(回调).

  2.为什么Vue .$nextTick能够获取更新后的多姆。

  因为Vue .$nextTick其实就是调用下一滴答方法,在异步队列中执行回调函数。

  vue。原型。$ next tick=Function(fn:Function){

  返回nextTick(fn,this);

  };

  3.使用Vue .$nextTick

  例子1:

  模板

  p id= test"{ foo } }/p

  /模板

  脚本

  导出默认值{

  data(){

  返回{

  福:"福"

  }

  },

  已安装(){

  让测试=文档。查询选择器(" # test ");

  this.foo= foo1

  //vue在更新数字正射影像图时是异步进行的,所以此处数字正射影像图并未更新

  控制台。日志(测试。innerhtml:测试。innerhtml’);

  这个. nextTick(()={

  //nextTick回调是在数字正射影像图更新后调用的,所以此处数字正射影像图已经更新

  控制台。日志(下一个滴答:测试。innerhtml:测试。innerhtml’);

  })

  }

  }

  /脚本

  执行结果:

  test.innerHTML:foo

  nextTick:test.innerHTML:foo1

  例子第二:

  模板(模板)

  p id= test“{ foo } }/p

  /模板-样板

  脚本编写

  导出默认值[

  日期()

  返回

  福:“福”

  }

  },

  已挂载()

  让测试=文档。查询选择器(" # test ");

  这个。福=福1

  //检视在更新多姆时是异步进行的,所以此处多姆并未更新

  控制台。日志( 1。测试。内部html:“测试。内部html ;

  这一点. nextTick()

  //nextTick(下一个打勾)回调是在多姆更新后调用的,所以此处多姆已经更新

  控制台。log( nexttick:test。内部html:“测试。内部html ;

  })

  这个。福=福2

  //此处多姆并未更新,且先于异步回调函数前执行

  控制台。日志( 2。测试。内部html:“测试。内部html ;

  }

  }

  /脚本

  执行结果:

  1 .测试。html内:foo

  2 .测试。html内:foo

  nexttick:测试。肝内动脉:foo 2

  例子第三:

  模板(模板)

  p id= test“{ foo } }/p

  /模板-样板

  脚本编写

  导出默认值[

  日期()

  返回

  福:“福”

  }

  },

  已挂载()

  让测试=文档。查询选择器(" # test ");

  这一点. nextTick()

  //nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,

  //压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染

  //所以此处多姆并未更新

  控制台。log( nexttick:test。内部html:“测试。内部html ;

  })

  这个。福=福1

  //检视在更新多姆时是异步进行的,所以此处多姆并未更新

  控制台。日志( 1。测试。内部html:“测试。内部html ;

  这个。福=福2

  //此处多姆并未更新,且先于异步回调函数前执行

  控制台。日志( 2。测试。内部html:“测试。内部html ;

  }

  }

  /脚本

  执行结果:

  1 .测试。html内:foo

  2 .测试。html内:foo

  nexttick:测试。html内:foo

  4、 nextTick与其他异步方法美元

  nextTick(下一个打勾)是模拟的异步任务,所以可以用答应我你好七个月来实现和这一点. nextTick美元相似的效果。

  例子1 .执行下列操作:

  模板(模板)

  p id= test“{ foo } }/p

  /模板-样板

  脚本编写

  导出默认值[

  日期()

  返回

  福:“福”

  }

  },

  已挂载()

  让测试=文档。查询选择器(" # test ");

  这一点. nextTick()

  //nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,

  //压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染

  //所以此处多姆并未更新

  控制台。log( nexttick:test。内部html:“测试。内部html ;

  })

  这个。福=福1

  //检视在更新多姆时是异步进行的,所以此处多姆并未更新

  控制台。日志( 1。测试。内部html:“测试。内部html ;

  这个。福=福2

  //此处多姆并未更新,且先于异步回调函数前执行

  控制台。日志( 2。测试。内部html:“测试。内部html ;

  Promise.resolve().然后()

  控制台。日志(承诺:测试。内部html:“测试。内部html ;

  });

  setTimeout()

  控制台。log( settimeout:test。内部html:“测试。内部html ;

  });

  }

  }

  /脚本

  执行结果:

  1 .测试。html内:foo

  2 .测试。html内:foo

  nexttick:测试。html内:foo

  承诺:测试。肝内动脉:foo 2

  settimeout:测试。肝内动脉:foo 2

  例子第二:

  模板(模板)

  p id= test“{ foo } }/p

  /模板-样板

  脚本编写

  导出默认值[

  日期()

  返回

  福:“福”

  }

  },

  已挂载()

  让测试=文档。查询选择器(" # test ");

  //承诺(承诺)你好七个月依旧是等到多姆更新后再执行

  Promise.resolve().然后()

  控制台。日志(承诺:测试。内部html:“测试。内部html ;

  });

  setTimeout()

  控制台。log( settimeout:test。内部html:“测试。内部html ;

  });

  这一点. nextTick()

  //nextTick(下一个打勾)回调是在触发更新之前就放入回调函数队列,

  //压根没有触发看守人更新以及以后的一系列操作,所以也就没有执行到最后的watcher.run()实行渲染

  //所以此处多姆并未更新

  控制台。log( nexttick:test。内部html:“测试。内部html ;

  })

  这个。福=福1

  //检视在更新多姆时是异步进行的,所以此处多姆并未更新

  console . log( 1 . test . innerhtml: test . innerhtml );

  this.foo= foo2

  //这里不更新DOM,在异步回调函数之前执行。

  console . log( 2 . test . innerhtml: test . innerhtml );

  }

  }

  /脚本

  执行结果:

  1.test.innerHTML:foo

  test.innerHTML:foo

  nextTick:test.innerHTML:foo

  Promise:test.innerHTML:foo2

  setTimeout:test.innerHTML:foo2

  

总结

  这就是本文关于vue的批量异步更新策略的源代码。更多关于vue批量异步更新策略的信息,请搜索我们之前的文章或者继续浏览下面的相关文章。希望大家以后能多多支持我们!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: