异步更新原理
上一章实现了依赖收集和自动更新, 但是存在问题: 如果对同一个属性多次更新, 如: vm.name = 1 vm.name = 2, vm.name = 3, 就是触发多次update方法, 会导致效率底下.
考虑将需要更新的watcher不是立即执行, 而是维护到一个队列里面去, 同时去重, 带一个更新周期结束后, 依次执行其中的代码, 这样子能有效避免性能的浪费, 就是nextTick的实现原理
watcher.js
import { Dep } from "./dep"
let id = 0
export class Watcher {
constructor(vm, fn, options) {
this.id = id ++
this.vm = vm
this.deps = []
this.depsId = new Set()
// 是否时渲染watcher
this.renderWatcher = options
// 重新渲染的方法
this.getter = fn
// 渲染watcher需要立即执行一次
this.get()
}
get() {
// 开始渲染时, 让静态属性Dep.target指向当前的watcher, 那么在取值的时候, 就能在对应的属性中记住当前的watcher
Dep.target = this
this.getter()
// 渲染完毕之后清空
Dep.target = null
}
// watcher里面添加dep, 去重
addDep(dep) {
if(!this.depsId.has(dep.id)) {
this.deps.push(dep)
this.depsId.add(dep.id)
// 去重之后, 让当前的dep,去记住当前的watcher
dep.addSub(this)
}
}
update() {
// 更新, 需要重新收集依赖
// this.get()
queueWatcher(this) // 把当前的watcher暂存起来
}
run() {
this.get()
}
}
let queue = [] // 用于存放需要更新吧的watcher
let has = {} // 用于去重
let pending = false // 防抖
function flushScheduleQueue() {
let flushQueue = queue.slice(0) // copy一份
queue = [] // 刷新过程中, 有新的watcher, 重新放到queue中
has = {}
pending = false
flushQueue.forEach(q => q.run()) // 添加一个run方法,真正的渲染
}
function queueWatcher(watcher) {
const id = watcher.id
if(!has[id]) { // 对watch进行去重
queue.push(watcher)
has[id] = true
// 不管update执行多少次, 但是最终值执行一次刷新操作
if(!pending) {
// 开一个定时器 里面的方法只执行一次, 并且是在所有的watcher都push进去之后才执行的
// setTimeout(() => {
// console.log('刷新')
// }, 0)
// setTimeout(flushScheduleQueue, 0)
nextTick(flushScheduleQueue, 0) // 内部使用的是nextTick, 第二个参数估计可以不要
pending = true
}
}
}
let callbacks = []
let waiting = false
// 跟上面的套路一样
function flushCallBacks() {
let cbs = callbacks.slice(0)
waiting = false
callbacks = []
cbs.forEach(cb => cb())
}
// vue内部 没有直接使用某个api, 而是采用优雅降级的方式
// 内部先使用的是promise(ie不兼容), MutationObserver (h5的api) ie专享的 setImmediate 最后setTimeout
let timerFunc
if(Promise) {
timerFunc = () => {
Promise.resolve().then(flushCallBacks)
}
} else if(MutationObserver) {
let observer = new MutationObserver(flushCallBacks) // 这里传入的回调时异步执行的
let textNode = document.createTextNode(1) // 应该是固定用法
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
textNode.textContent = 2
}
} else if(setImmediate) {
timerFunc = () => {
setImmediate(flushCallBacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallBacks)
}
}
export function nextTick(cb) { // setTimeout是过一段事件后, 执行cb, nextTick是维护了一个队列, 后面统一执行
callbacks.push(cb)
if(!waiting) {
// setTimeout(() => {
// flushCallBacks()
// }, 0)
timerFunc()
waiting = true
}
}
// nextTick既有同步,也有异步, push的时候是同步的, 执行的时候是异步的,
// 用户的nextTick和内部的nextTick哪个先执行取决于顺序
// nextTick采用的是一种优雅降级的方式实现的
scr/index.js 同时把nextTick挂载到Vue的原型上
// Vue 类是通过构造函数来实现的
// 如果通过 class来实现, 里面的类和方法就会有很多, 不利于维护
// 1. 新建一个Vue构造函数, 默认导出, 这样就有了全局 Vue
// 2. Vue中执行一个初始化方法, 参数是用户的选项
// 3. 在Vue的原型上添加这个方法, (注意: 添加的这个方法在引入vue的时候就执行了, 而不是在new Vue()的时候执行的)
import { initMixin } from "./init"
import { initLifeCycle } from "./lifecycle"
import { nextTick } from "./observe/watcher"
function Vue(options) {
this._init(options)
}
initMixin(Vue)
initLifeCycle(Vue)
Vue.prototype.$nextTick = nextTick
export default Vue
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。