1. Watch

1.1. Test

<body>
    <div id="app" style="color:red; background:green">{{name}}{{obj}}{{age}}</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {name:'zhangsan'.obj: {a:'1'
                },
                age:18
            },
            watch: {name(newVal,oldVal){
                    console.log(newVal,oldVal)
                },
                'obj.a'(newVal,oldVal){
                    console.log(newVal,oldVal)
                },
                age: [function(newVal,oldVal){
                        console.log(newVal,oldVal)
                    },
                    function(newVal,oldVal){
                        console.log(newVal,oldVal)
                    }
                ]
            }
        })
        vm.$watch('name'.function(newVal,oldVal){
            console.log(newVal,oldVal)
        })
        setTimeout(() = > {
            vm.name='lisi';
            vm.age=2;
            vm.obj.a='4';
        }, 2000);
    </script>
</body>
Copy the code

1.2.index.js

stateMixin(Vue);
Copy the code

1.3.state.js

  • Initialize the watch
  • Function and array watch are merged
/** * initialize $watch *@param {*} Vue 
 */
export function stateMixin(Vue) {
    Vue.prototype.$watch = function (key, handler, options = {}) {
        //TODO:This is used to mark the user watch
        options.user = true;
        new Watcher(this, key, handler, options); }}/** * Initialization status *@param {*} * / vm instance
export function initState(vm) {
    // Initialize watch
    if (opt.watch) {
        initWatch(vm, opt.watch)
    }
}


/** * Initializes watch *@param {*} vm 
 * @param {*} Watch Watch object */
function initWatch(vm, watch) {
    for (const key in watch) {
        let handler = watch[key];
        if (Array.isArray(handler)) { // Array case
            for (let i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); }}else {
            createWatcher(vm, key, handler)
        }
    }
}

/** * create watcher *@param {*} vm 
 * @param {*} key 
 * @param {*} * handler function@returns * /
function createWatcher(vm, key, handler) {
    return vm.$watch(key, handler)
}
Copy the code

1.4.observer/watcher.js

class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        this.vm = vm;
        if (typeof exprOrFn === 'string') {
            this.exprOrFn = function () {
                //TODO: exprOrFn may be 'obj.a'
                const splits = exprOrFn.split('. ')
                let obj = vm;
                for (let i = 0; i < splits.length; i++) {
                    obj = obj[splits[i]]
                }
                return obj; //vm[key] returns to the getter method}}else {
            this.exprOrFn = exprOrFn;
        }
        // check whether user watcher is using it
        this.cb = cb;
        this.options = options;
        this.user = !! options.user;this.getter = this.exprOrFn;

        this.value = this.get(); // Initialization is performed once by default
    }

    get() {
        //Dep.target=watcher
        pushTarget(this);
        const value = this.getter(); //TODO:This.getter ->render() executes, vm values, will be in get
        popTarget();
        return value;
    }


    run() {
        console.log('run')
        const newValue = this.get();
        const oldValue = this.value;
        this.value = newValue;
        if (this.user) {
            this.cb.call(this.vm, newValue, oldValue); }}}Copy the code

2. com puted implementation

  • 1. Compute attributes are not executed by default
  • 2. Multiple values If dependent variables do not change, the value will not be re-executed
  • 3. The dependency value changes and needs to be executed again
  • 4. Dirty Indicates whether the command needs to be executed again

2.1. Test

  • 1. When fullName is directly written in a page, fullName will not collect rendering watcher, because fullName has no DEP and no collection function. This logic is implemented in defineComputed of state
  • 2. FirstName is dependent on the calculated property, so it will collect the property watcher and render the property Watcher
<body>
    <div id="app" style="color:red; background:green">{{fullName}}</div>
    <script src="./dist/vue.js"></script>
    <script>
        const vm=new Vue({
            el:'#app'.data: {firstName:'zhang'.lastName:'san'
            },
            computed: {fullName(){
                    console.log('get')
                    return this.firstName+this.lastName;
                }
                // fullName:{
                // get(){
                // console.log('get')
                // return this.firstName+this.lastName;
                / /},
                // set(newVal){
                // console.log('set',newVal)
                / /}
                // }}})setTimeout(() = > {
            vm.firstName='li'
        }, 2000);
    </script>
</body>
Copy the code

2.2.state.js

/** * initialize $watch *@param {*} Vue 
 */
export function stateMixin(Vue) {
    Vue.prototype.$watch = function (key, handler, options = {}) {
        //TODO:This is used to mark the user watch
        options.user = true;
        new Watcher(this, key, handler, options); }}/** * Initialization status *@param {*} * / vm instance
export function initState(vm) {
    const opt = vm.$options;

    // Initialize computed
    if(opt.computed) { initComputed(vm, opt.computed); }}/** * execute * when dirty is true@param {*} key 
 * @returns * /
function createComputedGetter(key) {
    return function () { // Take the value of the calculated property
        const watcher = this._computedWatchers[key];
        //stack=[render watcher, calculate watcher]
        if (watcher.dirty) { // Determine whether to reevaluate according to dirty
            watcher.evaluate();
        }
        // If dep. target still has a value after execution, you need to collect firstName and lastName so that they have both calculated watcher and rendered watcher
        if (Dep.target) {
            watcher.depend();
        }
        returnwatcher.value; }}function defineComputed(vm, key, useDef) {
    let sharedProperty = {};
    if (typeof useDef === 'function') { // is a method, which is itself a get method
        sharedProperty.get = useDef;
    } else { / / object
        sharedProperty.get = createComputedGetter(key);
        sharedProperty.set = useDef.set;
    }
    Object.defineProperty(vm, key, sharedProperty); // Computed is a defineProperty
}

/** * Initialize computed *@param {*} vm 
 * @param {*} Computed object */
function initComputed(vm, computed) {
    // Mount the mapping of watcher and attribute to vm._computedWatchers for future use
    const watchers = vm._computedWatchers = {};
    for (let key in computed) {
        const userDef = computed[key];
        //userDef can be a method or an object
        const getter = typeof userDef === 'function' ? userDef : userDef.get;
        /** * TODO: * 1. Each calculated property is actually a calculated watcher * 2. Map watcher to properties */
        watchers[key] = new Watcher(vm, getter, () = > {}, {
            lazy: true // Calculate attributes watcher does not execute by default
        })
        // Mount the calculated properties to the VMdefineComputed(vm, key, userDef); }}Copy the code

2.3.observer/watcher.js

class Watcher {
    constructor(vm, exprOrFn, cb, options) {
        
        // Calculate attributes watcher, lazy:true,dirty:true
        this.lazy = !! options.lazy;this.dirty = !! options.lazy;// Compute attributes, not executed by default
        this.value = this.lazy ? undefined : this.get(); // Initialization is performed once by default
    }

    update() {
        if (this.dirty) { // When calculating property changes
            this.dirty = true;
        } else {
            // Update the watcher cache
            queueWatcher(this); }}evaluate() {
        this.dirty = false; //false, the value is already fetched
        this.value = this.get(); // User getter execution
    }

    depend() {
        let i = this.deps.length;
        while (i--) { //firtName lastName collection render watcher
            this.deps[i].depend(); }}}Copy the code

2.4.observer/dep.js

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];
}
Copy the code