关于vue.js:Vue2源码一变化侦测

39次阅读

共计 3365 个字符,预计需要花费 9 分钟才能阅读完成。

(一)变动侦测

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

observe 流程图

1、初始化

  • 定义 Vue 构造函数
  • 向 Vue 原型混入操作方法,不便前期扩大
  • 在初始化函数中进行 state 初始化 -> data 初始化
// index.js
import {initMixin} from "init.js"
const Vue = function(options){
    // 选项初始化
    this._init(options);
}
// 向 Vue 原型混入操作方法
initMixin(Vue);
...

export default Vue;
// init.js
import {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.js
export 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.js
import {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.js
let 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 的实例,能够通过它劫持新增的元素数组(十分优雅 也有点恶心)

正文完
 0