首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Vue中的nextTick

前言

熟悉Vue的前端,想必对Vue里的nexTick也很熟悉了,用的时候都知道他是延迟回调,有时候用起来甚至和setTimeout看起来是同样的效果。但他和setTimeout到底有什么区别呢?它是如何实现的?

本文就nextTick的实现引入,来讨论一下js中的异步和同步,微任务和宏任务。

nexTick

用法

在下一次DOM更新循环结束之后执行延迟回调(例如,操作更新后的 DOM 元素)。在修改数据之后立即使用这个方法,获取更新后的DOM。

nextTick 方法有两种用法:

使用回调函数:

你可以将一个回调函数传递给 this.$nextTick,这个回调函数将在 DOM 更新之后执行。这可以用于访问已经更新的 DOM 元素或执行其他需要等待 DOM 更新完成的操作。

// 修改数据

vm.msg = 'Hello'

// DOM 还没有更新

this.$nextTick(function () {

// 在 DOM 更新之后执行操作

});

使用 Promise:

从 Vue.js 2.1.0 开始,nextTick 还返回一个 Promise 对象,可以用于更方便的异步操作管理。

this.$nextTick().then(function () {

// 在 DOM 更新之后执行操作

});

完整例子:

{{ message }}

更新消息

export default {

data() {

return {

message: '初始消息'

};

},

methods: {

updateMessage() {

this.message = '新消息';

// 使用 nextTick 确保 DOM 已经更新后再操作

this.$nextTick(() => {

// 此时可以安全地访问更新后的 DOM 元素

const paragraph = this.$el.querySelector('p');

paragraph.style.color = 'red';

});

}

}

};

在上面的示例中,当点击按钮时,updateMessage 方法首先更新 message 数据,然后使用 this.$nextTick 来确保在 DOM 更新后再修改段落的文字颜色。这是因为 Vue.js 异步更新 DOM,如果不使用 nextTick,则在修改 DOM 元素之前可能会尝试访问尚未更新的 DOM。

源码实现

// The nextTick behavior leverages the microtask queue, which can be accessed

// via either native Promise.then or MutationObserver.

// MutationObserver has wider support, however it is seriously bugged in

// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It

// completely stops working after triggering a few times... so, if native

// Promise is available, we will use it:

/* istanbul ignore next, $flow-disable-line */

if (typeof Promise !== 'undefined' && isNative(Promise)) {

const p = Promise.resolve()

timerFunc = () => {

p.then(flushCallbacks)

// In problematic UIWebViews, Promise.then doesn't completely break, but

// it can get stuck in a weird state where callbacks are pushed into the

// microtask queue but the queue isn't being flushed, until the browser

// needs to do some other work, e.g. handle a timer. Therefore we can

// "force" the microtask queue to be flushed by adding an empty timer.

if (isIOS) setTimeout(noop)

}

isUsingMicroTask = true

} else if (!isIE && typeof MutationObserver !== 'undefined' && (

isNative(MutationObserver) ||

// PhantomJS and iOS 7.x

MutationObserver.toString() === '[object MutationObserverConstructor]'

)) {

// Use MutationObserver where native Promise is not available,

// e.g. PhantomJS, iOS7, Android 4.4

// (#6466 MutationObserver is unreliable in IE11)

let counter = 1

const observer = new MutationObserver(flushCallbacks)

const textNode = document.createTextNode(String(counter))

observer.observe(textNode, {

characterData: true

})

timerFunc = () => {

counter = (counter + 1) % 2

textNode.data = String(counter)

}

isUsingMicroTask = true

} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {

// Fallback to setImmediate.

// Technically it leverages the (macro) task queue,

// but it is still a better choice than setTimeout.

timerFunc = () => {

setImmediate(flushCallbacks)

}

} else {

// Fallback to setTimeout.

timerFunc = () => {

setTimeout(flushCallbacks, 0)

}

}

可以看到上面有几个条件判断,如果支持 Promise 就用 Promise

如果不支持就用 MutationObserver MDN-MutationObserver

MutationObserver 它会在指定的DOM发生变化时被调用

如果不支持 MutationObserver 的话就用 setImmediate MDN-setImmediate

但是这个特性只有最新版IE和node支持,然后是最后一个条件

如果这些都不支持的话就用setTimeout。

看完这一段其实也很懵,为什么要这样设计呢?为什么要这样一个顺序来判断呢?说到这里就不得不讨论JavaScript 运行机制(Event Loop)&微任务宏任务了。

JavaScript 运行机制(Event Loop)

单线程

JavaScript 是一门单线程编程语言,这意味着它在任何给定时刻只能执行一个任务。单线程的特点如下:

「顺序执行:」 JavaScript 代码按照它们在程序中的顺序依次执行,每个操作都需要等待前一个操作完成后才能开始。这确保了代码的执行顺序是可控的。

「阻塞问题:」 由于 JavaScript 是单线程的,如果执行某个操作需要一段时间(例如,执行一个耗时的计算、等待网络请求返回等),它将会阻塞整个主线程,导致页面不响应。这种情况可能会对用户体验产生负面影响。

为了解决阻塞问题,JavaScript 使用了异步编程模型。通过使用回调函数、Promise、async/await 等机制,可以在不阻塞主线程的情况下处理耗时操作。这意味着即使 JavaScript 是单线程的,它仍然可以执行异步操作,以确保应用程序保持响应性。

一些典型的异步操作包括:

定时器:例如 setTimeout 和 setInterval,用于延时执行代码或周期性执行代码。

网络请求:例如使用 XMLHttpRequest 或 Fetch API 发起HTTP请求。

事件处理:例如点击、键盘输入等事件的处理。

文件操作:例如读写文件。

数据库访问:例如使用 IndexedDB 进行本地数据库操作。

虽然 JavaScript 是单线程的,但由于异步编程模型的存在,它能够有效地处理并发任务,确保应用程序的响应性,同时也需要开发者小心处理异步操作,以避免潜在的问题,例如回调地狱(Callback Hell)或异步并发问题。

同步和异步

JavaScript 中的同步(Synchronous)和异步(Asynchronous)是关于代码执行的方式。之间的区别:

「同步(Synchronous)」

同步代码按顺序执行:在同步代码中,每行代码按照它们在程序中的顺序依次执行,一个操作必须在另一个操作完成之后才能开始。这种行为称为阻塞(Blocking),因为在执行某个操作时,程序会等待其完成才继续执行下一个操作。

同步代码会阻塞主线程:在浏览器环境中,同步操作会阻塞 JavaScript 主线程,导致页面在执行耗时的任务时变得不响应,这可能会引发用户体验问题。

同步代码适用于一些简单的操作,但不适用于需要长时间执行的任务,因为它会阻塞应用程序的响应性。

「异步(Asynchronous)」

异步代码不按顺序执行:在异步代码中,操作不一定会按照它们出现在代码中的顺序执行。相反,异步操作通常会在后台执行,不会阻塞主线程,允许主线程继续执行其他任务。

异步代码适用于耗时操作:异步代码适用于需要执行较长时间操作(例如网络请求、文件读写、定时器等)的情况,因为它们不会阻塞主线程。

异步操作通常使用回调函数、Promise、async/await 等方式来处理结果或在操作完成时执行特定的代码。

以下是一个使用异步操作的示例,使用了异步回调函数:

console.log("开始");

setTimeout(function() {

console.log("定时器回调函数执行");

}, 2000);

console.log("结束");

在上面的示例中,setTimeout 是一个异步操作,它会在2秒后执行回调函数,但不会阻塞主线程。因此,"开始" 和 "结束" 日志会立即显示,然后在2秒后才会显示 "定时器回调函数执行"。

JavaScript 中的异步操作是非常常见的,用于处理许多与用户界面、网络通信、文件操作等相关的任务,以确保应用程序保持响应性。

宏任务和微任务

JS任务又分为宏任务和微任务。

「宏任务(macrotask)」

宏任务代表一组异步操作,它们通常比微任务的优先级低。宏任务包括:

用户交互事件(例如,点击、滚动等)

文件 I/O 操作(例如,读写文件)

定时器操作(例如,setTimeout、setInterval、setImmediate)

网络请求(例如,Ajax 请求)

原生事件(例如,页面加载、资源加载)

requestAnimationFrame(用于执行动画操作)

宏任务是在事件循环的主循环中执行的,每个宏任务完成后,JavaScript 引擎会检查是否有微任务需要执行。

「微任务(microtask)」

微任务通常具有比宏任务更高的执行优先级。微任务包括:

Promise 的回调函数

async/await 中 await 后面的代码

MutationObserver 的回调函数

微任务队列会在当前宏任务执行完毕之后立即执行,确保了微任务会在下一个宏任务之前执行完。这对于处理异步操作的结果,以及确保一些特定的回调尽早执行非常有用。

以下是一个示例,演示宏任务和微任务的执行顺序:

console.log('Start');

setTimeout(() => {

console.log('Timeout (macro task)');

}, 0);

Promise.resolve().then(() => {

console.log('Promise (micro task)');

});

console.log('End');

上述代码会产生以下输出:

Start

End

Promise (micro task)

Timeout (macro task)

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OsHSXfL35nBpzvPHHG-s7Pag0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券