vue2核心原理(简易)-异步更新(Vue.nextTick)笔记

800 阅读1分钟

前言

  • 本章项目地址
  • 为什么异步更新,拿data中的属性举例,如果某个数据多次的去赋值,每次都去重新编译、比较vnode、渲染Dom 耗费性能
  • 我们把更新视图的数据行为收集起来,去重,防抖 通过异步行为实现(如setTimeout, Ajax...)
  • 本次拿data举例

示例

<div id="app">{{ name }}</div>

<script>
var vm = new Vue({
    data: {
        name: 'one'
    }
})

vm.$mount('#app')

setTimeout(() => {
    vm.name = 'two'
    vm.name = 'three'
    vm.name = 'four'
    vm.name = 'five'

    vm.$nextTick(()=>{
    	// 只执行一次
        console.log(vm.$el)
    })
}, 1000)

正题

Vue.nextTick方法(重点)

import { nextTick } from './utils'

Vue.prototype.$nextTick = nextTick
/** nextTick */
/**
 * @description callbacks   队列要执行的异步函数
 * @description waiting     防抖
 */
let callbacks = []
let waiting = false

/**
 * @description 执行回调函数
 */
function flushCallbacks() {
    callbacks.forEach(cb => cb())
    waiting = false
    callbacks = []
}

/**
 * @description 异步函数的降级处理(兼容适配)
 */
function timer(flushCallbacks) {
    let timerFn = () => {}
    if (Promise) {
        timerFn = () => { Promise.resolve().then(flushCallbacks) }
    } else if (MutationObserver) {
        let textNode = document.createTextNode(1)
        let observe = new MutationObserver(flushCallbacks)
        observe.observe(textNode, { characterData: true })
        timerFn = () => { textNode.textContent = 3 }
    } else if (setImmediate) {
        timerFn = () => { setImmediate(flushCallbacks) }
    } else {
        timerFn = () => { setTimeout(flushCallbacks) }
    }

    timerFn()
}

export function nextTick(cb) {
    callbacks.push(cb)

    if (!waiting) {
        timer(flushCallbacks)
        waiting = true
    }
}

scheduler Watcher里update中调度的方法

import { nextTick } from '../utils'

/**
 * @description queue   存放watcher
 * @description has     存放那些watcher
 * @description pending 防抖
 */
let queue = []
let has = {}
let pending = false

/** 核心方法 */
/**
 * @description 执行watcher
 */
function flushSchedulerQueue() {
    for (let i = 0; i < queue.length; i++) {
        queue[i].run()
    }

    queue = []
    has = {}
    pending = false
}

/**
 * @description 去除重复的watcher
 * @description 有多个watcher时 进行批处理(防抖)
 * @description 当前执行栈中代码执行完毕后,会先清空微任务,在清空宏任务 希望更早的渲染页面 nextTick
 */
export function queueWatcher(watcher) {
    const id = watcher.id
    if (has[id] == null) {
        queue.push(watcher)
        has[id] = true

        if (!pending) {
            nextTick(flushSchedulerQueue)
            pending = true
        }

    }

}

new Wacther文件

import { popTarget, pushTarget } from './dep'
import { queueWatcher } from './scheduler'

let id = 0
class Watcher {
    constructor(vm,exprOrFn,cb,options){
        this.vm = vm
        this.exprOrFn = exprOrFn
        this.cb = cb
        this.options = options
        this.id = id++
		
        // 视图更新 就是上面的updateComponent方法
        this.getter = exprOrFn 
        this.deps = [] 
        this.depsId = new Set()

        this.get()
    }

    get(){
        pushTarget(this)
        this.getter()
        popTarget()
    }

    update(){
      // 看这里 异步更新操作 就是将更新缓存起来(做一些去重, 防抖)然后一起调用,最后还是调用下方run方法
       queueWatcher(this)
    }
    run(){
        this.get()
    }
    
    addDep(dep){
        let id = dep.id;
        if(!this.depsId.has(id)){
            this.depsId.add(id);
            this.deps.push(dep);
            dep.addSub(this)
        }
    }
}

export default Watcher

下一章 vue2核心原理(简易)-watch笔记