某人

此前素未谋面、此后遥遥无期

0%

vue之NextTick实现

语法

Vue.nextTick( [callback, context] )

  • 参数:
    • {Function} [callback]
    • {Object} [context]
  • 用法:
    • 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
1
2
3
4
5
6
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})

2.1.0起新增:如果没有提供回调且在支持 Promise 的环境中,则返回一个 Promise。请注意 Vue 不自带 Promisepolyfill,所以如果你的目标浏览器不原生支持 Promise (IE:你们都看我干嘛),你得自己提供 polyfill

异步更新队列

  1. Vue 在更新 DOM 时是异步执行的
  2. 只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
  3. 在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中更新
  4. 如果同一个 watcher 被多次触发,只会被推入到队列中一次
  5. Vue 在内部对异步队列尝试使用原生的 Promise.thenMutationObserversetImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替

v2.6.12next-tick源码

辅助方法
1
2
3
4
5
6
7
8
9
10
//是否IE
export const isIE = UA && /msie|trident/.test(UA)
//空函数
export function noop (a?: any, b?: any, c?: any) {}
/* istanbul ignore next */
export function isNative (Ctor: any): boolean {
return typeof Ctor === 'function' && /native code/.test(Ctor.toString())
}
//是否ios
export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
next-tick.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/* @flow */
/* globals MutationObserver */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIE, isIOS, isNative } from './env'

// 是否正在使用微任务
export let isUsingMicroTask = 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++) {
copies[i]()
}
}

// 这里,我们使用微任务异步延迟包装器。
// 在2.5中,我们使用了(宏)任务(与微任务结合使用)。
// 但是,当在重新绘制之前更改状态时,它存在一些细微的问题
// (例如#6813,由外向内的过渡)。
// 此外,在事件处理程序中使用(宏)任务会导致一些奇怪的行为
// 无法规避的代码(例如#7109,#7153,#7546,#7834,#8109)。
// 因此,我们现在再次在各处使用微任务。
// 这种折衷的主要缺点是存在一些场景
// 微任务的优先级过高,并且在两者之间触发
// 顺序事件(例如,具有解决方法的#4521,#6690)
// 甚至在同一事件冒泡之间(#6566)。
let timerFunc

// nextTick行为利用了微任务队列,可以访问它
// 通过本地Promise.then或MutationObserver。
// MutationObserver拥有更广泛的支持,但是在此方面存在严重错误
// 在触摸事件处理程序中触发时,iOS> = 9.3.3中的UIWebView。 它
// 触发几次后完全停止工作...因此,如果是原生的
// Promise可用,我们将使用它:
/* istanbul 忽略下一个, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
// 在有问题的UIWebViews中,Promise.then不会完全中断,但是
// 它可能会陷入怪异的状态,在这种状态下,回调被推入微任务队列,
// 但是直到浏览器才刷新队列,需要做其他一些工作
// 例如 处理一个计时器。 因此,我们可以
// 通过添加空计时器来“强制”刷新微任务队列。
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// timerFunc函数执行时会导致文本节点textNode的数据发生改变,因为MutationObserver对象在监听文本节点,
// 所以进而也就会触发flushCallbacks回调函数
// 如果本地Promise不可用,请使用MutationObserver,
// 例如 PhantomJS,iOS7,Android 4.4
//(#6466 MutationObserver在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)) {
// 回退到setImmediate。
// 从技术上讲,它利用了(宏)任务队列,
// 但它仍然是比setTimeout更好的选择。
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 回退到setTimeout。
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}

export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc()
}
// 如果没有提供回调且在支持 `Promise` 的环境中,则返回一个 `Promise`
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}

MutationObserver

  • MutationObserver接口提供了监视对DOM树所做更改的能力。
  • 它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。
  1. childList:子节点的变动(指新增,删除或者更改)。
  2. attributes:属性的变动。
  3. characterData:节点内容或节点文本的变动。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function flushCallbacks (mutationsList, observer) {
console.log("flushCallbacks",{
mutationsList,
observer
});
}
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
//节点内容或节点文本的变动。
characterData: true
})

textNode.data = String(counter);

// 停止监控
observer.disconnect();

相关链接

  1. Vue-nextTick
  2. 异步更新队列
  3. v2.6.12/src/core/util/next-tick