学习vue源码(13)手写 $nextTick(1),2024前端面经

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

然后

this.$nextTick(function () {

console.log(this.message)

})

又把 打印message这个操作 放入callbacks中。

此时callbacks= [渲染Dom操作,打印message]

最后再把 依次执行callbacks里的操作这个操作 包装成 异步任务。

包装成 异步任务 很简单,作为setTimeout,或promise.then的回调就行

不懂watcher是什么的同学,推荐先看学习vue源码(12)大白话谈响应式原理

在这里 我必须先事先强调 nextTick在vue中有两种用途:

  • 一种就是 vue内部 使用nextTick,把 渲染Dom操作 这个操作 放入到callbacks 中,

  • 一种是把nextTick 给开发者使用,也就是 我们经常使用的$nextTick, 然后把我们传入$nextTick的回调函数放到callbacks中0。

刚好这两种用途 对应上面我们一开始举的例子中的两个操作。

2. 为什么Vue.js使用异步更新队列


Vue.js2.0开始使用虚拟DOM进行渲染,变化侦测的通知只发送到组件,组件内用到的所有状态的变化都会通知到同一个watcher,然后虚拟DOM会对整个组件进行“比对(diff)”并更改DOM。

也就是说,如果在同一轮事件循环中有两个数据发生了变化,那么组件的watcher会受到两份通知,从而进行两次渲染。事实上,并不需要渲染两次,虚拟DOM会对整个组件进行渲染,所有只需要等所有状态都修改完毕后,一次性将整个组件的DOM渲染到最新即可。

要解决整个问题,Vue.js的实现方式是将收到通知的watcher实例添加到队列中缓存起来,并且在添加到队列之前检查其中是否已经存在相同的watcher,只有不存在时,才将watcher实例添加到队列中。然后在下一次事件循环(event loop)中,Vue.js会让队列中的watcher触发渲染流程并清空队列。这样就可以保证即使在同一事件循环中有两个状态发生改变,watcher最后也只执行一次渲染流程。

举个例子,假如

export default {

data () {

return {

msg1: 1,

msg2:2,

msg3:3

}

},

mounted () {

this.msg1 = 0

this.msg2 = 0

this.msg3 = 0

},

}

如果我们 不使用异步队列,那上面的例子中,

  • this.msg1 = 0 会触发 该组件 更新视图,

  • this.msg2 = 0 会触发 该组件 更新视图

  • this.msg3 = 0 会触发 该组件 更新视图

显然,就 使得 同一个组件 更新了三次 视图。

注意:同一个组件里的任何一个数据发生改变都会触发整个组件Dom的更新。

当我们使用异步队列后。

  • this.msg1 = 0 把 更新该组件Dom的操作放入callbacks中,

  • this.msg2 = 0 发现 callbacks 已经有 该组件的更新操作,没事了。

  • this.msg3 = 0 发现 callbacks 已经有 该组件的更新操作,没事了。

因此,当callbacks 被包装成 异步任务 执行时, 该组件就只会更新一次DOM,这对性能的提升是多么的高啊啊!!!

了解nextTick原理前,我们 需要 有 是事件循环 和 执行栈 的预备知识 , 不了解的可以看

这一次,彻底弄懂 JavaScript 执行机制(别还不知道什么是宏任务,什么是微任务).

三 么是执行栈


当执行一个方法时,Javascript会生成一个与这个方法对应的执行环境,又叫执行上下文。这个执行环境中有这个方法的私有作用域、上层作用域的指向、方法的参数、私有作用域中定义的变量以及this对象。这个执行环境会被添加到一个栈中,这个栈就是执行栈。

如果在这个方法的代码中执行到了一行函数调用语句,那么Javascript会生成这个函数的执行环境并将其添加到执行栈中,然后进入这个执行环境继续执行其中的代码。执行完毕并返回结果后,Javascript会退出执行环境并把这个执行环境从栈中销毁,回到上一个方法的执行环境。这个过程反复进行,直到执行栈中的代码全部执行完毕。这个执行环境的栈就是执行栈。

下次DOM更新周期的意思其实是下次微任务执行时更新DOM。而vm.$nextTick其实是将回调添加到微任务中。只有在特殊情况下才会降级成宏任务,默认会添加到微任务中。

因此,如果使用vm.$nextTick来获取更新后的DOM,则需要注意顺序的问题。因为不论是更新DOM的回调还是使用vm.$nextTick注册的回调,都说向微任务队列中添加任务,所以哪个任务先添加到队列中,就先执行哪个任务。

事实上,更新DOM的回调也是使用vm.$nextTick来注册到微任务中的。这个我们在上面也强调过

如果想要在vm.$nextTick中获取更新后的DOM,则一定要在更改数据的后面使用vm.$nextTick注册回调。

new Vue({

methods:{

example:function(){

this.message = ‘changed’;

this.$nextTick(function(){

})

}

}

})

如果先使用vm.$nextTick注册回调,然后修改数据,则在微任务对垒中先执行使用vm.$nextTick注册的回调,然后执行更新DOM的回调。所以在回调中得不到最新的DOM,因为此时DOM还没有更新。

在事件循环中,必须当微任务队列中的事件都执行完之后,才会从宏任务队列中取出一个事件执行下一轮,所以添加到微任务队列中的任务的执行时机优先于向宏任务队列中添加的任务。

new Vue({

methods:{

example:function(){

setTimeout(_=>{

},0)

this.message = ‘changed’;

}

})

setTimeout属于宏任务,使用它注册的回调会加入到宏任务中。宏任务的执行要比微任务晚,所以即便是先注册,也是先更新DOM后执行setTimeout中设置的回调

4. vm.$nextTick原理


vm.$nextTick和全局Vue.nextTick是相同的,所以nextTick的具体实现并不是在Vue原型上的$nextTick方法中,而是抽象成了nextTick方法供两个方法共用。

import { nextTick } from ‘…/util/index’

Vue.prototype.$nextTick = function(fn){

return nextTick(fn,this);

}

Vue原型上的$nextTick方法只是调用了nextTick方法,具体实现其实在nextTick中。

由于vm.$nextTick会将回调添加到任务队列中延迟执行,所以在回调执行前,如果反复调用vm.$nextTick,Vue.js并不会反复将回调添加到任务队列中,只会向任务队列中添加一个任务。(这个我们在上面说过了)

Vue.js内部有一个列表用来存储vm.$nextTick参数中提供的回调。在一轮事件循环中,vm.$nextTick只会向任务队列添加一个任务,多次使用vm.$nextTick只会将回调添加到回调列表中缓存起来。当任务触发时,依次执行列表中的所有回调并清空列表。

(5)nextTick方法的实现方式

const callbacks = [];

let pending = false;

const callbacks = [];

let pending = false;

function flushCallbacks(){

pending = false;

const copies = callbacks.slice(0);

callbacks.length = 0;

for(let i = 0;i<copies.length;i++){

copiesi;

}

}

let microTimerFunc;

const p = Promise.resolve();

microTimerFunc = () =>{

p.then(flushCallbacks)

}

export function nextTick(cb,ctx){

callbacks.push(()=>{

if(cb){

cb.call(ctx);

}

})

if(!pending){

pending = true;

microTimerFunc();

}

}

nextTick(function(){

console.log(this.name);//Berwin

},{name:‘Berwin’})

解释上面代码

1、通过数组callbacks来存储用户注册的回调。

2、声明了变量pending来标记是否已经向队列中添加一个任务了。每当向任务队列中插入任务时,将pending设置为true,每当任务被执行时将pending设置为false,这样就可以通过pending的值来判断是否需要向任务队列中添加任务。

3、函数flushCallbacks,即被注册的那个任务。当这个函数被触发时,会将callbacks中的所有函数依次执行,然后清空callbacks,并将pending设置为false。即一轮事件循环中,flushCallbacks只会执行一次。

4、microTimerFunc函数,它的作用是使用Promise.then将flushCallbacks添加到微任务队列中。这个其实就是我们所说的包装成异步。

5、执行nextTick函数注册回调时,首先将回调函数添加到callbacks中,然后使用pending判断是否需要向任务队列中新增任务。

在Vue.js2.4版本之前,nextTick方法在任何地方都使用微任务,但是微任务的优先级太高,在某些场景下可能会出现问题。所以Vue.js提供了在特殊场合下可以强制使用宏任务的方法。

const callbacks = [];

let pending = false;

function flushCallbacks(){

pending = false;

const copies = callbacks.slice(0);

callbacks.length = 0;

for(let i = 0;i<copies.length;i++){

copiesi;

}

}

let microTimerFunc;

let macroTimerFunc = function(){…}

let userMacroTask = false;

const p = Promise.resolve();

microTimerFunc = () =>{

p.then(flushCallbacks)

}

export function withMacroTask(fn){

return fn._withTask || (fn_withTask = function({

userMacroTask = true;

const res = fn.apply(null,arguments);

userMacroTask = false;

retrun res;

}))

}

export function nextTick(cb,ctx){

callbacks.push(()=>{

if(cb){

cb.call(ctx);

}

})

if(!pending){

pending = true;

if(userMacroTask){

macroTimerFunc();

}else{

microTimerFunc();

}

}

}

1、新增了withMacroTask函数,它的作用是给回调函数做一层包装,保证在整个回调函数执行过程中,如果修改了状态(数据),那么更新DOM的操作会被推到宏任务队列中,也就是说,更新DOM的执行时间会晚于回调函数的执行时间。

2、withMacroTask先将变量userMacroTask设置为true,然后执行回调,如果这时候回调中修改了数据(触发了更新DOM的操作),而userMacroTask是true,那么更新DOM的操作会被推送到宏任务队列中。当回调执行完毕后,将userMacroTask恢复为false。

3、被withMacroTask包裹的函数所使用的所有vm.$nextTick方法都会将回调添加到宏任务队列中,其中包括状态被修改后触发的更新DOM的回调和用户自己使用vm.$nextTick注册的回调等。

macroTimerFunc如何将回调添加到宏任务队列中


Vue.js优先使用setImmediate,但是它存在兼容性问题,只能在IE中使用,所以使用MessageChannel作为备选方案。如果浏览器也不支持MessageChannel,那么最后会使用setTimeout来将回调添加到宏任务队列中。

if(typeof setImmediate !==‘undefined’ && isNative(setImmediate)){

macroTimerFunc = () =>{

setImmediate(flushCallbacks);

}

}else if(typeof MessageChannel !== ‘undefined’ &&(isNative(MessageChannel)||

MessageChannel.toString()===‘[Object MessageChannelConstructor]’)){

最后

中年危机是真实存在的,即便有技术傍身,还是难免对自己的生存能力产生质疑和焦虑,这些年职业发展,一直在寻求消除焦虑的依靠。

  • 技术要深入到什么程度?

  • 做久了技术总要转型管理?

  • 我能做什么,我想做什么?

  • 一技之长,就是深耕你的专业技能,你的专业技术。(重点)

  • 独立做事,当你的一技之长达到一定深度的时候,需要开始思考如何独立做事。(创业)

  • 拥有事业,选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
选择一份使命,带领团队实现它。(创业)

一技之长分五个层次

  • 栈内技术 - 是指你的前端专业领域技术

  • 栈外技术 - 是指栈内技术的上下游,领域外的相关专业知识

  • 工程经验 - 是建设专业技术体系的“解决方案”

  • 带人做事 - 是对团队协作能力的要求

  • 业界发声 - 工作经验总结对外分享,与他人交流

永远不要放弃一技之长,它值得你长期信仰持有

主要内容包括html,css,html5,css3,JavaScript,正则表达式,函数,BOM,DOM,jQuery,AJAX,vue 等等。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-BJhQJtDS-1713555352906)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 25
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值