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

859 阅读1分钟

前言

  • 本章项目地址
  • watch 中每个属性都会new一个用户watcher(new Watcher)
  • 在数据初始化得时候 开始new Watcher, Dep.target 指向此时的用户watcher, 此时该属性中的加入用户watcherdep.addSub.push(watcher)
  • 当data中的数据发生变化时, 调用该数据的所有watcher
  • Watcher先将老值存起来 数据发生变化时 将新值与老值 返回给表达式(cb) 图片替换文本

示例

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

<script src="dist/vue.js"></script>
<script>
    var vm = new Vue({
        data: {
            name: 'one'
        },
        /** 用户watcher 几种方式 */
        watch: {
            // name(newVal, oldVal) {
            //     console.log(newVal, oldVal)
            // },
            // name: [
            //     function(newVal, oldVal) {
            //         console.log(newVal, oldVal)
            //     },
            //     function(a, b) {
            //         console.log(a, b)
            //     }
            // ],
            // 'obj.n'(newVal, oldVal) {
            //     console.log(newVal, oldVal)
            // }
        }
    })

    vm.$mount('#app')

    vm.$watch('name', function(newValue, oldValue) {
        console.log(newValue, oldValue)
    })
    setTimeout(() => {
        vm.name = 'two'
    }, 2000)

正题

$watch

export function stateMixin(Vue) {
    Vue.prototype.$watch = function(key, handler, options={}) {
        // 用户创建的watcher
        options.user = true

        new Watcher(this, key, handler, options)
    }
}

options.watch 初始化开始执行

export function initState(vm) {
    const opts = vm.$options

    if (opts.watch) {
        initWatch(vm, opts.watch)
    }

}
/** initWatche module */
/**
 * @description 调用$watch
 */
function createWatcher(vm, key, handler) {
    return vm.$watch(key, handler)
}

/**
 * @description 初始化 watch 将观察属性的function拆分出来
 * @description 每个观察属性 创建new watcher
 */
function initWatch(vm, watch) {
    for (const key in watch) {
        const handler = watch[key]
        if (Array.isArray(handler)) {
            for (let i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i])
            }
        } else {
            createWatcher(vm, key, handler)
        }
    }
}

Watcher 类 (关键)

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

/** 给new Watcher一个id */
let id = 0

/**
 * @description 数据劫持时 期望一个属性对应多个watcher 同时一个watcher对应多个属性
 */
class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm
        this.exprOrFn = exprOrFn
        this.cb = cb
        this.options = options

        
        this.id = id++
        
        this.deps = []
        this.depsId = new Set()
        
        /** 用户watcher */
        this.user = !!options.user
        // 看这里 将获取的函数名 封装成表达式 
        if (typeof exprOrFn == 'string') {
            this.getter = function() {
                // vm取值 'obj.n' -> ['obj', 'n'] -> vm['obj']['n']
                let path = exprOrFn.split('.')
                let obj = vm
                for (let i = 0; i < path.length; i++) {
                    obj = obj[path[i]]
                }
                return obj
            }
        } else {
            this.getter = exprOrFn
        }

        /** 用户watcher 默认取得第一次值 */
        this.value = this.get()
    }

    /** render生成vnode时 dep.push(watcher) 并更新视图 */
    get() {
        pushTarget(this)
        //  看这里 在VM上寻找劫持的数据 可将该属性上push用户Watcher
        const value = this.getter.call(this.vm)
        popTarget()

        return value
    }

    update() {
         queueWatcher(this)
    }

    run() {
        /** 考虑1 渲染组件watcher */
        /** 考虑2 用户watcher(newValue oldValue) */
        let newValue = this.get()
        let oldValue = this.value
        this.value = newValue

        /** 看这里 用户watcher 将值返回给cb, 并调用 */
        if (this.user) {
            this.cb.call(this.vm, newValue, oldValue)
        }
    }

    /** 存储dep 并排除生成vnode时多次调用一样属性 只存一个 dep 或 watcher */
    /** 其次当属性发生变化时将不再存储dep 和 watcher */
    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

Dep 类

/** 每个劫持的属性 加上唯一的标识 */
let id = 0

/**
 * @description 每个劫持的属性 new Dep
 */
class Dep {
    constructor() {
        this.id = id++
        this.subs = []
    }

    /** dep传给watcher */
    depend() {
        if (Dep.target) {
            Dep.target.addDep(this)
        }
    }

    addSub(watcher) {
        this.subs.push(watcher)
    }

    notify() {
        this.subs.forEach(watcher => watcher.update())
    }

}

Dep.target = null
let stack = []

export function pushTarget(watcher) {
    Dep.target = watcher
    stack.push(watcher)
}

export function popTarget() {
    stack.pop()
    Dep.target = stack[stack.length - 1]
}

export default Dep

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