Skip to content

七、异步更新与nextTick

0、index.html中调用

js
let vm = new Vue();

 setTimeout(() => {
	vm.arr.push(123);
	vm.arr.push(123);
	vm.arr.push(123);

	console.log(vm.$el.innerHTML);
	vm.$nextTick(() => {
		console.log(vm.$el.innerHTML);
	});
}, 2000);

1、Vue原型找那个扩展nextTick方法

js
// src\state.js

import { nextTick } from "./util"

export function stateMixin(Vue) {
    Vue.prototype.$nextTick = function(cb) {
        nextTick(cb);
    }
}

2、实现队列机制

js
// src\observer\watcher.js

class Watcher {
    constructor(vm, exprOrFn, cb, options) {
       ......
    }
	.....

    update() {
        // 这里不能每次都调用get方法,get方法会重新渲染页面,需要用队列实现多次调用只刷新一次
        queueWatcher(this);
    }
}
js
// 实现队列

// src\observer\watcher.js

import { nextTick } from "../util";

// 将需要批量更新的watcher存到一个队列中,稍后让watcher执行
let queue = [];
let has = {}; // 用于去重
let pending = false; // 防抖

function flushSchedulerQueue() {
    queue.forEach(watcher => {
        watcher.run();
        watcher.cb();
    });
    queue = []; // 清空watcher队列为了下次使用
    has = {}; // 清空标识的id
    pending = false;
}

function queueWatcher(watcher) {
    // 对watcher去重
    const id = watcher.id;
    if (has[id] == null) {
        // 将watcher存到队列中
        queue.push(watcher);
        has[id] = true;

        // 异步更新,等到所有同步代码执行完毕后再执行
        if (!pending) {
			// 内部调用
            nextTick(flushSchedulerQueue);
            pending = true;
        }

    }
    // console.log(watcher.id);
}

export default Watcher;

3、nextTick原理

Promise > MutationObserver > setImmediate > setTimeout

js
// src\util.js

const callbacks = [];
let pending = false;
let timerFunc;

function flushCallbacks() {
    // 让nextTick中传入的方法依次执行
    while (callbacks.length) {
        let cb = callbacks.pop();
        cb();
    }
    // 标识已经执行完毕
    pending = false;
}

if (Promise) {
    timerFunc = () => {
        // 异步处理更新
        Promise.resolve().then(flushCallbacks);
    }
} else if (MutationObserver) {
    // 可以监控dom变化,监控完毕后是异步更新
    let observe = new MutationObserver(flushCallbacks);

	/**
	* 思路:
	* 1、创建一个文本节点
	* 2、监控这个文本节点
	* 3、当文本节点里面的字符变化,就异步调用flushCallbacks进行更新操作
	*/

    // 先创建一个文本节点
    let textNode = document.createTextNode(1);
    // 观测文本节点中的内容
    observe.observe(textNode, { characterData: true });
    timerFunc = () => {
        // 文本内容更新为2,触发异步调用flushCallbacks
        textNode.textContent = 2;
    }
} else if (setImmediate) {
    // ie浏览器里面的api,性能比setTimeout要好些
    timerFunc = () => {
        setImmediate(flushCallbacks);
    }
} else {
    timerFunc = () => {
        setTimeout(flushCallbacks);
    }
}

// 内部会调用,用户也会调用,但是异步只需要一次
export function nextTick(cb) {
    callbacks.push(cb);
    // Vue3里面的nextTick原理就是Promise.resolve().then() 没有兼容性处理
    if (!pending) {
        timerFunc();
        pending = true;
    }
}

Released under the MIT License.