(文章是讨论vue源码,但是代码中和真正的源码会有些不同,参杂个人理解,主要是讲原理,大家可以在理解后自行去看源码)
    1.我们回顾一下
    把你放到时光机,回到几年前,看看我们写的代码(骗人,啊啊啊,那时候,还没写前端呢),然后再结合当下的一些框架,感受下知识迭代的力量,上图:

    old5.jpg

    这是一个简单的原生态的表单,原谅我差点不会写了,form表单还是很强大的,知道我们的需求,但是时代变了,人类要的更多,由单向数据绑定迁移到了双向数据绑定:

    一个简单的vue实例 :

    image.png

    knockout 实例:

    old2.jpg

    你见或不见,他就在那里😂(yuque应该添加个更加的表情功能),虽然形式不同,都是数据绑定,我们先来看看vue是怎么操作的。

    2.初见vue
    1)vue构造函数

    old6.jpg

    vue初始化的函数_init()里面有这样一个函数:observe(this.$data) ,它就是来做数据监听的,这里体现一种设计模式:发布-订阅模式。
    发布者:dep.nodify() 实际上就是将dep收集来的依赖们,自行update()
    订阅者:依赖此数据的那些节点 (Watcher们),订阅相应数据的Dep ,dep.add()
    总之就是Dep 负责收集所有相关的的订阅者 Watcher,Watcher 负责订阅 Dep 。

    2)vue 2.0版本的数据监听

    主角:Observer 类 实现对viewModel的监视,当发生变更时发出变更消息
    更加基础的内容请移步:https://zhuanlan.zhihu.com/p/37753129

    1. export function observe(data) {
    2. if (!data || typeof data !== 'object') { // 因为用到递归遍历,所以要有终止条件
    3. return;
    4. }
    5. return new Observer(data);
    6. }
    class Observer{
      constructor(value){
          this.value = value
        //def函数是给value添加一个__ob__属性,值为当前observe实例
        def(value,'__ob__',this)
        if(isArray(value)){
            this.observeArray(value)//数组的监听有些特别,此处单独处理
        }else {
            this.walk(value);
        }
      }
    
      walk(obj){
          Object.keys(obj).forEach(key=>{
            this.defineReactive(obj,key,obj[key])
        })
      }
    
      defineReactive(obj, key, val) {
        //dep 可以理解为一个容器,或者一个栈都可以,存在某个数据的依赖集
        let dep = new Dep();
        let childObj = observe(val); // 递归遍历孩子
        Object.defineProperty(obj, key, {
           enumerable: true, // 枚举
           configurable: true, // 不可再配置
           get() {
                   if(Dep.target){
                //这里保证只有视图中需要被用到的data才会触发getter
                //在解析指令的时候,此处会被调用,Dep.target是当前的那个watcher
                 dep.addSub(Dep.target);
                 if(Array.isArray(val)){
                     if (childObj) {   // 一维数组
                        childObj.dep.addSub(Dep.target);
                      }
                       for (let e, i = 0, l = val.length; i < l; i++) { // 多维数组
                          e = val[i];
                          e && e.__ob__ && e.__ob__.dep.addSub(Dep.target);
                       }
                  }
               } 
               return val;
            },
            set(newValue) {
                if (newValue == val) {
                    return;
                }
                //这里用了闭包
                val = newValue;
                childObj = observe(newValue); // 新添加的属性也要进行转换
    
                dep.notify();
            }
         });
      }
    
      observeArray(arr) {
            arr.__proto__ = arrayProxyMethod;  //添加方法,直接遍历子属性
            arr.forEach(ele => {
                observe(ele);
            })
       }
    }
    
    export default class Dep{
        constructor(){
            this.subs = [];
        }
    
        addSub(sub){
            this.subs.push(sub);
        }
    
        notify(){
            this.subs.forEach(ele=>{
                ele.update();
            })
        }
    }
    

    new.png

    3)对于数组的监听 observeArray

    
    let test= {},arr = [1,2,3]
    Object.defineProperty(test,'book',{
        get:function(){
            console.log('get func')
            return arr
        },
        set:function(val){
            console.log('set func')
            arr = val
        }
    })
    

    由于js的限制,Object.defineProperty对于数组来说,除非对于test.arr整体赋值,否则,当应用数组的方法将其改变的时候,是无法触发set函数的:

    1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
    2. 当你修改数组的长度时,例如:vm.items.length = newLength
    3. vm.items.push(99)
    4. Vue 不能检测对象属性的添加或删除

    解决第一个问题的方法:
    Vue.set(vm.items, indexOfItem, newValue)或者vm.items.splice(indexOfItem, 1, newValue)
    解决第二个问题的方法:
    vm.items.splice(newLength)
    解决第四个问题的方法:
    Vue.set(object,propertyName,value)
    解决第三个问题的方法:改造下数组上的方法:

    const proxyMethods = [
        'pop',
        'push',
        'sort',
        'shift',
        'splice',
        'unshift',
        'reverse'
    ];
    
    const arrayProto = Array.prototype;
    export const arrayProxyMethod = Object.create(arrayProto);
    
    proxyMethods.forEach(method => {
        const oldMethod = arrayProto[method];
        arrayProxyMethod[method] = function() {
            let args = toArray(arguments);
            let result = oldMethod.apply(this, args);
            let insert;
            //这里就用到了之前存放的observer实例
            const ob = this.__ob__;
            switch (method) {
                case 'push':
                case 'unshift':
                    insert = args;
                    break;
                case 'splice':
                    insert = args.slice(2);
            }
            if (insert) {
                ob.observeArray(insert);
            }
            ob.dep.notify();
            return result;
        }
    });
    

    4)3.0版vue监听策略

    new Proxy()是es6里面的东西,用它可以弥补Object.defineProperty的不足,省却了如上许多代码,如我们可以不用在递归遍历属性了,也不用重写数组方法了,旧的事物取代新的事物,确实是它更适应现在的条件。

    下面是一个小例子,至于proxy原理还要在学习中

    Proxy基本语法

    const obj = new Proxy(target, handler);

    参数说明如下:
    target: 被代理对象。
    handler: 是一个对象,声明了代理target的一些操作。
    obj: 是被代理完成之后返回的对象。

    const target = {name:'doubao'}
    const handler = {
        get:function(target,key){
        return target[key]
      },
      set:function(){
          target[key] = value
      }
    }
    const testobj = new Proxy(target,hanler)
    testobj.name  //doubao
    testobj.name = 2
    testobj.name //2
    

    应用场景:
    数据过滤:如富文本内容过滤