异步更新原理
大约 2 分钟约 677 字
// Vue实例化
let vm = new Vue({
el: '#app',
data() {
return {
a: 123,
}
},
// render(h) {
// return h('div',{id:'a'},'hello')
// },
template: `<div id="a">hello {{a}}</div>`,
})
// 当我们每一次改变数据的时候 渲染watcher都会执行一次 这个是影响性能的
setTimeout(() => {
vm.a = 1
vm.a = 2
vm.a = 3
}, 1000)
一、watcher 更新的改写
// src/observer/watcher.js
import { queueWatcher } from './scheduler'
export default class Watcher {
update() {
// 每次watcher进行更新的时候 是否可以让他们先缓存起来 之后再一起调用
// 异步队列机制
queueWatcher(this)
}
run() {
// 真正的触发更新
this.get()
}
}
我们把 update 更新方法改一下 增加异步队列的机制
二、queueWatcher 实现队列机制
// src/observer/scheduler.js
import { nextTick } from '../util/next-tick'
let queue = []
let has = {}
function flushSchedulerQueue() {
for (let index = 0; index < queue.length; index++) {
// 调用watcher的run方法 执行真正的更新操作
queue[index].run()
}
// 执行完之后清空队列
queue = []
has = {}
}
// 实现异步队列机制
export function queueWatcher(watcher) {
const id = watcher.id
// watcher去重
if (has[id] === undefined) {
// 同步代码执行 把全部的watcher都放到队列里面去
queue.push(watcher)
has[id] = true
// 进行异步调用
nextTick(flushSchedulerQueue)
}
}
新建 scheduler.js 文件 表示和调度相关 先同步把 watcher 都放到队列里面去 执行完队列的事件之后再清空队列 主要使用 nextTick 来执行 watcher 队列
三、nextTick 原理
// src/util/next-tick.js
let callbacks = []
let pending = false
function flushCallbacks() {
pending = false //把标志还原为false
// 依次执行回调
for (let i = 0; i < callbacks.length; i++) {
callbacks[i]()
}
}
let timerFunc //定义异步方法 采用优雅降级
if (typeof Promise !== 'undefined') {
// 如果支持promise
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
}
} else if (typeof MutationObserver !== 'undefined') {
// MutationObserver 主要是监听dom变化 也是一个异步方法
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)
}
} else if (typeof setImmediate !== 'undefined') {
// 如果前面都不支持 判断setImmediate
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// 最后降级采用setTimeout
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick(cb) {
// 除了渲染watcher 还有用户自己手动调用的nextTick 一起被收集到数组
callbacks.push(cb)
if (!pending) {
// 如果多次调用nextTick 只会执行一次异步 等异步队列清空之后再把标志变为false
pending = true
timerFunc()
}
}
四、挂载原型
// src/state.js
export function stateMixin(Vue) {
Vue.prototype.$nextTick = function (cb) {
nextTick(cb)
}
}
// src/index.js
import { initMixin } from './init'
import { lifecycleMixin } from './lifecycle'
import { stateMixin } from './state'
import { renderMixin } from './vdom/index'
function Vue(options) {
this._init(options) // 入口方法,做初始化操作
}
// 写成一个个的插件进行对原型的扩展
initMixin(Vue)
// 混合生命周期 组件挂载、组件更新
lifecycleMixin(Vue)
// _render
renderMixin(Vue)
stateMixin(Vue)
export default Vue
源码
https://github.com/luotianxu1/learn/tree/main/vue2/04.update