(一)变动侦测

  • 初始化
  • Object变动侦测
  • Array变动侦测

observe流程图

1、初始化

  • 定义Vue构造函数
  • 向Vue原型混入操作方法,不便前期扩大
  • 在初始化函数中进行 state初始化 -> data初始化
// index.jsimport {initMixin} from "init.js"const Vue = function(options){    // 选项初始化    this._init(options);}// 向Vue原型混入操作方法initMixin(Vue);...export default Vue;
// init.jsimport {initState} from "state.js"export function initMixin(Vue){    Vue.prototype._init = function(options){        const vm = this;        vm.$options = options;        // 初始化状态        initState(vm);    }}
// state.js 初始化状态import {observe} from 'observe/index.js'export function initState(vm){    const opt = vm.$options;    if(opt.data){        // 初始化data        initData(vm);    }}function initData(vm){    let data = vm.$options.data;    // 判断data是否为函数    data = typeof data === 'function' ? data.call(vm) : data;    // 对对立后的data对象从新挂载在vm实例上    vm._data = data     // 数据侦测与劫持    observe(data);}

2、Object变动侦测

Object.defineProperty()毛病

  • 不能侦测新增与删除属性

2.1 数据劫持

// observe/index.js// 对数据进行侦测/重写 返回可侦测对象export function observe(data){    // 非对象类型不进行劫持    if(typeof data != 'object' || data == null) return;    return new Observe(data);}// 数据侦测类class Observe {    constructor(data){        this.walk(data);    }    // 对象数据劫持 - 相当于重写 性能瓶颈    walk(){        Object.keys.forEach(key => {            defineReactive(data,key,data[key])        })    }}// 数据劫持公共办法export const defineReactive(target,key,value){    Object.defineProperty(target,key,{        enumerable:true,    // 默认也为true        configurable:true,    // 同上        get(){            return value;     // 闭包        },        set(newVal){             if(newVal === value) return;            value = newVal;        }    })}
此时咱们能够通过observe办法对传入的对象进行数据侦测,劫持数据的取值更改
然而数据是在_data上的,为了开发模式语法尽量简洁,这里须要数据代理

2.2 数据代理

// state.js 对initData进行补充function initData(vm){    //...other code    for(let key in data){        proxy(vm,'_data',key);    }}function proxy(vm,target,key){    Object.defineProperty(vm,key,{        get(){            return vm[target][key];        },        set(newVal){            vm[target][key] = newVal;        }    })}
const vm = new Vue({    data(){        return {            name:"foo",            age:11        }    }})

当咱们执行以上代码时能够在vm上读取到name属性,并且name与age都领有gettersetter

2.3 深度侦测与新值侦测

当data中的值是嵌套的对象,以及对data属性设置对象值时,咱们心愿依然对其进行侦测,
并且对于曾经侦测的数据不再进行重写

// obseve/index.jsexport function observe(data){    ...    // data曾经存在了Observe的一个实例 阐明曾经被侦测过    if(data.__ob__ instanceof Observe) return data.__ob__    ...}class Observe {    constructor(data){        Object.defineProperty(data,"__ob__",{            value:this, // 间接应用实例赋值 前面数组侦测须要该属性            enumerable:true // 避免深度侦测时 递归爆栈        })        ...    }}export const defineReactive = function(target,key,value){    // 深度侦测    observe(value);    Object.defineProperty(target,key,{        ...        set(newVal){            ...            // 新值侦测            observe(value);            ...        }    })}

3、Array变动侦测

上述observe流程图中的hasMethod判断是指数组调用的办法是否在被重写列表中

数组个别数据元素较多,如果一一下标进行侦测,会节约性能,因为相较于对下标的批改咱们更常应用的是数组办法批改

留神并不是Object.defineProperty不能侦测,而是Vue在设计时摈弃了侦测下标这种形式
  • 数组中援用数据类型的元素仍然应用observe进行侦测
  • 对能批改原数组的办法进行切面补充(原型链继承的形式)
// observe/index.jsimport {newArrayProto} from "observe/array.js"...class Observe {    constructor(data){        ...        //数组类型判断        if(Array.isArray(data)){            // 设置data的原型对象            Object.setPrototypeOf(data,newArrayProto)            this.observeArray(data)        }else {                    }    }    // 数组劫持    observeArray(data){        // 元素为数组/对象进行递归劫持        data.forEach(item => observe(item))    }}...
// observe/array.jslet originArrayProto = Array.prototype// 创立一个以originArrayProto为原型的对象export let newArrayProto = Object.create(originArrayProto);// 批改数组的7种办法const methods = ["push","pop","unshift","shift","sort","splice","reverse"]methods.forEach(method => {    newArrayProto[method] = function(..args){        // 调用原始办法        const result = originArrayProto[method].apply(this,args);        const ob = this.__ob__; // __ob__为上述增加的Observe实例        // 数据劫持新数据        let inserted;        switch(method){            case 'push':            case 'unshift':                inserted = args;                breake;            case 'splice':                inserted = args.slice(2);// 第三个参数为新数据                break;        }        // 新数据侦测        if(inserted){            ob.observeArray(inserted);        }        return result;    }})
通过原型链继承将中间层对象设置为原数据的原型对象,是一种面向切面编程的形式,只重写局部办法
__ob__是原数据的一个属性,值为Observe的实例,能够通过它劫持新增的元素数组(十分优雅 也有点恶心)