Vue变化侦听

变化侦听

为什么是变化侦听重要?

​ 因为它是MV*框架的基石,变化侦听可以说是各种自动化前端框架的基础,将view和model 单向或者说双向绑定在一起。

变化侦听原理:代理模式 发布订阅机制

这些变化侦听的思路就是给被侦听的对象加上一个包装,由包装实现功能,就是代理模式,

  • 实现对象的变化侦听
  • 实现数组的变化侦听
  • 为什么Vue要用Proxy重写变化侦听
  • 如何使用Proxy实现变化侦听

实现对象的变化侦听

对于每一个被对象我们用Observe类其包装起来。

对于每一个监听者我们用watch类将其注册到被监听对象上。

题外话: 在旧版的vue上,每一个vnode都被注册到监听对象上,这种粒度太细了,导致会浪费很多的内存空间,于是到了后面vue只讲每个组件注册到监听对象上,组件再更新一组新的vnode出来和旧的vnode进行比较(diff 算法) , 最后渲染。

Observer

class Dep {
    constructor() {
        this.subs = [];
    }
    addSub(sub) {
        this.subs.push(sub);
    }
    removeSub(sub) {
        remove(this.subs,sub);
    }
    depend() {
        if (window.target) {
            this.addSub(window.target);
        }
    }
    notify() {
        for(let i=0;i<this.subs.length;i++) {
            // console.log(this.subs[i])
            this.subs[i].update();
        }
    }
}

function remove(arr,sub) {
    if (arr.length) {
        const index = arr.indexOf(sub);
        if (index > -1) {
            return arr.splice(index,1);
        }
    }
}

export class observe {
    constructor(data) {
        this.data = data;
        
        if (!Array.isArray(this.data)) {
            this.handle(this.data);
        }
    }
    handle(data) {
        const keys = Object.keys(data)
        for (let i=0;i<keys.length;i++) {
            defineReactive(data,keys[i],data[keys[i]]);
        }
    }
}

// 包装每个属性的set和get
function defineReactive(data,key,val) {
    // 如果属性还是属性 那么递归处理
    if (typeof val === 'object') {
        new observe(val);
    }
    let dep = new Dep();
    Object.defineProperty(data,key,{
        enumerable: true,
        configurable: true,
        // get 和 set方法不能处理set和delete方法--所以vue又有vm.$delete 和 vm.$add Api
        get() {
            // 添加到广播列表中
            dep.depend();
            return val;
        },
        set(newVal) {
            if (val===newVal) {
                return;
            }
            val = newVal;
            // 通知数据改变
            dep.notify();
        }
    }) 
}

watcher

// 注册自己到被Observe里
export class watcher {
    constructor(vm,path,update) {
        this.vm = vm;
        this.getter = parsePath(path);
        this.cb = update;
        // 通过调用getter将自己注入到Observe中
        this.value = this.get();
    }
    get() {
        window.target = this;
        const value = this.getter.call(this.vm,this.vm);
        window.target = null;
        return value;
    }
    update() {
        const oldValue = this.value;
        this.value = this.getter(this.vm,this.vm);
        this.cb.call(this.vm,this.value,oldValue);
    }
}

function parsePath(path) {
    const segs = path.split('.');
    return function(obj) {
        for (let i=0;i<segs.length;i++) {
            if (!obj)return;
            obj = obj[segs[i]];
        }
        return obj;
    }
}

效果

可以看到数字不断增加

<div id="app"></div>
<script type="module">
    import {observe,watcher} from "./detectChange/dep.js"
    let vm = {
        app: {},
        init() {
            this.app = document.getElementById('app');
        },
    }

    function update(val) {
        console.log(this);
        this.app.innerText = val;
    }
    vm.data = {
        age: 1,
    }
    vm.init();
    update.call(vm,vm.data.age);
    setInterval(()=>{
        vm.data.age++
    },1000)
    new observe(vm.data);
    new watcher(vm,"data.age",update);
</script>

实现Array的变化侦听

上面的方法对于数组我们只能监听数组指向的那些元素,而对于数组本身调用push,等待方法我们无法进行监听,所以对于数组我们要单独处理

  1. 封装数组本来的api
  2. 替换api
  3. 依赖收集

具体实现可以看vue代码或者其他人的代码

上一篇:vm workstation pro 安装centos7


下一篇:Vue表单实例