ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

6. 异步更新的原理

2022-06-26 23:32:52  阅读:135  来源: 互联网

标签:nextTick 异步 Vue dep 更新 watcher let 原理 id


异步更新原理

上一章实现了依赖收集和自动更新, 但是存在问题: 如果对同一个属性多次更新, 如: 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

标签:nextTick,异步,Vue,dep,更新,watcher,let,原理,id
来源: https://www.cnblogs.com/littlelittleship/p/16414709.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有