Skip to content

八、watch的原理

0、使用方式

js
// 第一种
let vm = new Vue();
vm.$watch(function (newVal, oldVal) {
    console.log(newVal, oldVal);
});

vm.$watch('a.b.c', function (newVal, oldVal) {
  console.log(newVal, oldVal);
})

// 第二种
let vm = new Vue({
	el: '#app',
	data: {
		a: {a: {a: 1}},
		b: 2
	},
	methods: {
	    cc() {
	        console.log('method cc');
	    }
	},
	watch: {
		// 1. key value
		'a.a.a': {
			handler(newValue, oldValue) {
				// 对象没有老值,都是新
				console.log(newValue, oldValue);
			},
			immediate: true // 可选
		},
		// 2. 写成key和数组的方式
		'b': [
		    (newValue, oldValue) => {
		        console.log(newValue);
		    },
		    (newValue, oldValue) => {
		        console.log(newValue);
		    }
		],
		// 3、监控当前实例上的方法
		'c': 'cc',
		// 4、handler的写法
		'd': {
		    handler() {
		        console.log('ddd');
		    }
		}
	}
});

1、扩展$watch方法

js
// src\state.js

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

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

function initProps(vm) {}

function initMethods(vm) {}

// 初始化数据
function initData(vm) {}

function initComputed(vm) {}

// 初始化watch
function initWatch(vm) {
    let watch = vm.$options.watch;
    for (let key in watch) {
        // handler 可能是数组、字符串、对象、函数
        const handler = watch[key];
        if (Array.isArray(handler)) {
            // 数组
            handler.forEach(handle => {
                createWatcher(vm, key, handle);
            })
        } else {
            // 字符串、对象、函数
            createWatcher(vm, key, handler);
        }
    }
}

// 创建watcher:options 用来标识是用户watcher
function createWatcher(vm, exprOrFn, handler, options) {
    if (typeof handler == 'object') {
        options = handler;
        handler = handler.handler; // 是一个函数
    }
    if (typeof handler == 'string') {
        handler = vm[handler]; // 将实例的方法作为handler
    }
    // key  handler 用户传入的选项
    return vm.$watch(exprOrFn, handler, options);
}

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

	// 在Vue的原型上面挂载$watch方法
    Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
        // 数据应该依赖这个watcher,数据变化后应该让watcher从新执行,user表示为自定义的watcher
        let watcher = new Watcher(this, exprOrFn, cb, {...options, user: true });
        if (options.immediate) {
            // 如果是immediate,立刻执行更新
            cb();
        }
    }
}

2、watcher类的getter方法改写

js
// src\observer\watcher.js

class Watcher {
    constructor(vm, exprOrFn, cb, options) {
		// .....
		
        this.user = options.user; // 用户watcher
        this.isWatcher = typeof options === 'boolean'; // 标识是渲染watcher

        if (typeof exprOrFn == 'function') {
            this.getter = exprOrFn;
        } else {
            // exprOrFn 可能传递过来的是一个字符串a
            this.getter = function() {
                // 当去当前实例上取值时,才会触发依赖收集
                let path = exprOrFn.split('.'); // ['a', 'a', 'a']
                let obj = vm;
                for (let i = 0; i < path.length; i++) {
                    obj = obj[path[i]]; // vm.a.a.a
                }
                return obj;
            }
        }

        // 默认会先调用一次get方法,进行取值,将结果保留下来
        this.value = this.get();
    }

    addDep(dep) {}

    get() {
        // 当前watcher实例
        pushTarget(this);
        // 调用exprOrFn,渲染页面 > 取值(执行了get方法)
        let result = this.getter();
        // 渲染完成后,将watcher删掉
        popTarget();
        return result;
    }

    run() {
        // 渲染逻辑
        let newValue = this.get();
        let oldValue = this.value;
        this.value = newValue; // 更新老值
        if (this.user) {
			// 调用cb方法
            this.cb.call(this.vm, newValue, oldValue);
        }
    }
    update() {}
}

export default Watcher;

Released under the MIT License.