2024年最新学习vue源码(13)手写 $nextTick(3),阿里面试提问

Vue

  • 什么是MVVM?

  • mvvm和mvc区别?它和其它框架(jquery)的区别是什么?哪些场景适合?

  • 组件之间的传值?

  • Vue 双向绑定原理

  • 描述下 vue 从初始化页面–修改数据–刷新页面 UI 的过程?

  • 虚拟 DOM 实现原理

  • Vue 中 key 值的作用?

  • Vue 的生命周期

  • Vue 组件间通信有哪些方式?

  • vue 中怎么重置 data?

  • 组件中写 name 选项有什么作用?

  • Vue 的 nextTick 的原理是什么?

  • Vuex 有哪几种属性?

    开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

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]’)){

const channel = new MessageChannel();

const port = channel.port2;

channel.port1.onmessage = flushCallbacks;

macroTimerFunc = () =>{

port.postMessage(1);

}

}else{

macroTimerFunc = () =>{

setTimeout(flushCallbacks,0);

}

}

microTimerFunc的实现原理是使用Promise.then,但并不是所有浏览器都支持Promise,当不支持时,会被降级成macroTimerFunc。

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

const p = Promise.resolve();

microTimerFunc = () =>{

p.then(flushCallbacks);

}

}else{

microTimerFunc = macroTimerFunc;

}

官方文档中有一句话:如果没有提供回调且在支持Promise的环境中,则返回一个Promise。

this.$nextTick()

.then(function({

//DOM更新了

}))

要实现这个功能,需要在nextTick中进行判断,如果没有提供回调且当前环境支持Promise,那么返回Promsie,并且在callbacks中添加一个函数,当这个函数执行时,执行Promise 的resolve即可。

export function nextTick(cb,ctx){

let _resolve;

callbacks.push(()=>{

if(cb){

cb.call(ctx);

}else if(_resolve){

_resolve(ctx);

}

})

if(!pending){

pending = true;

if(userMacroTask){

macroTimerFunc();

}else{

microTimerFunc();

}

}

if(!cb && typeof Promise !== ‘undefined’){

return new Promise(resolve =>{

_resolve = resolve;

})

}

}

5. 完整的代码


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;

let userMacroTask = false;

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

macroTimerFunc = () =>{

setImmediate(flushCallbacks);

}

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

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

const channel = new MessageChannel();

const port = channel.port2;

channel.port1.onmessage = flushCallbacks;

macroTimerFunc = () =>{

port.postMessage(1);

}

}else{

macroTimerFunc = () =>{

setTimeout(flushCallbacks,0);

}

}

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

const p = Promise.resolve();

microTimerFunc = () =>{

p.then(flushCallbacks);

}

}else{

microTimerFunc = macroTimerFunc;

}

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){

let _resolve;

callbacks.push(()=>{

if(cb){

cb.call(ctx);

}else if(_resolve){

ES6

  • 列举常用的ES6特性:

  • 箭头函数需要注意哪些地方?

  • let、const、var

  • 拓展:var方式定义的变量有什么样的bug?

  • Set数据结构

  • 拓展:数组去重的方法

  • 箭头函数this的指向。

  • 手写ES6 class继承。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

微信小程序

  • 简单描述一下微信小程序的相关文件类型?

  • 你是怎么封装微信小程序的数据请求?

  • 有哪些参数传值的方法?

  • 你使用过哪些方法,来提高微信小程序的应用速度?

  • 小程序和原生App哪个好?

  • 简述微信小程序原理?

  • 分析微信小程序的优劣势

  • 怎么解决小程序的异步请求问题?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值