本文主要介绍了vue.nexttick 的实现方法,这是一篇继event loop和microtask 后的vue.nexttick api实现的源码解析。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望能帮助到大家。
预热,写一个sleep函数
function sleep (ms) {
return new promise(resolve => settimeout(resolve, ms)
}
async function onetick (ms) {
console.log('start')
await sleep(ms)
console.log('end')
}
onetick(3000)
解释下sleep函数
async 函数进行await promisefn()时函数执行是暂停的,我们也知道现在这个promisefn是在microtask内执行。当microtask没执行完毕时,后面的macrotask是不会执行的,我们也就通过microtask在event loop的特性实现了一个sleep函数,阻止了console.log的执行
流程
1执行console.log('start')
2执行await 执行暂停,等待await函数后的promisefn在microtask执行完毕
3在sleep函数内,延迟ms返回
4返回resolve后执行console.log('end')
nexttick api
vue中nexttick的使用方法
vue.nexttick(() => {
// todo...
})
了解用法后看一下源码
const nexttick = (function () {
const callbacks = []
let pending = false
let timerfunc // 定时函数
function nexttickhandler () {
pending = false
const copies = callbacks.slice(0) // 复制
callbacks.length = 0 // 清空
for (let i = 0; i < copies.length; i++) {
copies[i]() // 逐个执行
}
}
if (typeof promise !== 'undefined' && isnative(promise)) {
var p = promise.resolve()
var logerror = err => { console.error(err) }
timerfunc = () => {
p.then(nexttickhandler).catch(logerror) // 重点
}
} else if ('!isie mutationobserver') {
var counter = 1
var observer = new mutationobserver(nexttickhandler) // 重点
var textnode = document.createtextnode(string(conter))
observer.observe(textnode, {
characterdata: true
})
timerfunc = () => {
counter = (counter + 1) % 2
textnode.data = string(counter)
}
} else {
timerfunc = () => {
settimeout(nexttickhandler, 0) // 重点
}
}
return function queuenexttick (cb, ctx) { // api的使用方式
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
err
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerfunc()
}
if (!cb && typeof promise !== 'undefined') {
return new promise((resolve, reject) => {
_resolve =resolve
})
}
}
})() // 自执行函数
大致看一下源码可以了解到nexttick api是一个自执行函数
既然是自执行函数,直接看它的return类型,return function queuenexttick (cb, ctx) {...}
return function queuenexttick (cb, ctx) { // api的使用方式
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
err
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerfunc()
}
if (!cb && typeof promise !== 'undefined') {
return new promise((resolve, reject) => {
_resolve =resolve
})
}
}
只关注主流程queuenexttick函数把我们传入的() => { // todo... } 推入了callbacks内
if (typeof promise !== 'undefined' && isnative(promise)) {
var p = promise.resolve()
var logerror = err => { console.error(err) }
timerfunc = () => {
p.then(nexttickhandler).catch(logerror) // 重点
}
} else if ('!isie mutationobserver') {
var counter = 1
var observer = new mutationobserver(nexttickhandler) // 重点
var textnode = document.createtextnode(string(conter))
observer.observe(textnode, {
characterdata: true
})
timerfunc = () => {
counter = (counter + 1) % 2
textnode.data = string(counter)
}
} else {
timerfunc = () => {
settimeout(nexttickhandler, 0) // 重点
}
}
这一段我们可以看到标注的三个点表明在不同浏览器环境下使用promise, mutationobserver或settimeout(fn, 0) 来执行nexttickhandler
function nexttickhandler () {
pending = false
const copies = callbacks.slice(0) // 复制
callbacks.length = 0 // 清空
for (let i = 0; i < copies.length; i++) {
copies[i]() // 逐个执行
}
}
nexttickhandler就是把我们之前放入callbacks的 () => { // todo... } 在当前tasks内执行。
写一个简单的nexttick
源码可能比较绕,我们自己写一段简单的nexttick
const simplenexttick = (function () {
let callbacks = []
let timerfunc
return function queuenexttick (cb) {
callbacks.push(() => { // 给callbacks 推入cb()
cb()
})
timerfunc = () => {
return promise.resolve().then(() => {
const fn = callbacks.shift()
fn()
})
}
timerfunc() // 执行timerfunc,返回到是一个promise
}
})()
simplenexttick(() => {
settimeout(console.log, 3000, 'nexttick')
})
我们可以从这里看出nexttick的原理就是返回出一个promise,而我们todo的代码在这个promise中执行,现在我们还可以继续简化
const simplenexttick = (function () {
return function queuenexttick (cb) {
timerfunc = () => {
return promise.resolve().then(() => {
cb()
})
}
timerfunc()
}
})()
simplenexttick(() => {
settimeout(console.log, 3000, 'nexttick')
})
直接写成这样。
const simplenexttick = function queuenexttick (cb) {
timerfunc = () => {
return promise.resolve().then(() => {
cb()
})
}
timerfunc()
}
simplenexttick(() => {
settimeout(console.log, 3000, 'nexttick')
})
这次我们把自执行函数也简化掉
const simplenexttick = function queuenexttick (cb) {
return promise.resolve().then(cb)
}
simplenexttick(() => {
settimeout(console.log, 3000, 'nexttick')
})
现在我们直接简化到最后,现在发现nexttick最核心的内容就是promise,一个microtask。
现在我们回到vue的nexttick api官方示例
<p id="example">{{message}}</p>
var vm = new vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textcontent === 'new message' // false
vue.nexttick(function () {
vm.$el.textcontent === 'new message' // true
})
原来在vue内数据的更新后dom更新是要在下一个事件循环后执行的。
nexttick的使用原则主要就是解决单一事件更新数据后立即操作dom的场景。
既然我们知道了nexttick核心是利用microtasks,那么我们把简化过的nexttick和开头的sleep函数对照一下。
const simplenexttick = function queuenexttick (cb) {
return promise.resolve().then(cb)
}
simplenexttick(() => {
settimeout(console.log, 3000, 'nexttick') // 也可以换成ajax请求
})
function sleep (ms) {
return new promise(resolve => settimeout(resolve, ms) // 也可以换成ajax请求
}
async function onetick (ms) {
console.log('start')
await sleep(ms)
console.log('end')
}
onetick(3000)
我们看出nexttick和我么写的onetick的执行结果是那么的相似。区别只在于nexttick是把callback包裹一个promise返回并执行,而onetick是用await执行一个promise函数,而这个promise有自己包裹的webapi函数。
那在用ajax请求的时候我们是不是直接这样使用axios可以返回promise的库
async function getdata () {
const data = await axios.get(url)
// 操作data的数据来改变dom
return data
}
这样也可以达到同nexttick同样的作用
最后我们也可以从源码中看出,当浏览器环境不支持promise时可以使用mutationobserver或settimeout(cb, 0) 来达到同样的效果。但最终的核心是microtask
相关推荐:
node.js中的process.nexttick使用实例
node.js中的定时器nexttick()和setimmediate()区别分析_node.js
node.js中的process.nexttick使用实例_node.js
以上就是vue.nexttick 的实现方法浅析的详细内容。
