Vue响应式数据原理2.0

Object.defineProperty

数据模型仅仅是普通的 JavaScript 对象,可是对这些对象进行操做时,却能影响对应视图,简而言之,就是你动我也动。 它的核心实现就是「响应式系统」,核心内容为Object.defineProperty 使用方法以下:编程

/* obj: 目标对象 prop: 目标对象的属性名 descriptor: 描述符 return value 传入对象 */
Object.defineProperty(obj, prop, descriptor)
复制代码

descriptor的一些属性数组

  • enumerable,属性是否可枚举,默认 false。
  • configurable,属性是否能够被修改或者删除,默认 false。
  • get,获取属性的方法。
  • set,设置属性的方法。

实现 observer(可观察到你动了)

首先定义一个假的函数来模拟更新app

function updateView(val) {
    /* 伪装是视图 */
    console.log("我动了");
}
复制代码

而后咱们定义一个 defineReactive ,这个方法经过 Object.defineProperty 来实现对对象的「响应式」化,通过 defineReactive 处理之后,咱们的 target 的 key 属性在「」的时候会触发 get 方法,而在该属性被「」的时候则会触发 set 方法。函数

function defineReactive (target, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            return val;
        },
        set(newVal) {
            if (newVal === val) return;
            updateView(newVal);
        }
    });
}
复制代码

这样貌似ok了,可是没人让他动起来,咱们再封装一层observer,这个函数传入一个 value(须要「响应式」化的对象),经过遍历全部属性的方式对该对象的每个属性都经过 defineReactive 处理。测试

function observer (target) {
    if (!target || (typeof target !== 'object')) {
        return;
    }
    
    Object.keys(target).forEach((key) => {
        defineReactive(target, key, target[key]);
    });
}
复制代码

最后为了好看点,封装一个Vueui

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
// 测试
let o = new Vue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,world.";  /* 我动了 */
复制代码

问题来了

1,这只测试了字符串,要是测试嵌套对象就很容易发现问题,不动啦,原理也很简单,指向同一内存,能够类比深浅拷贝

那么咱们要进行递归调用this

function defineReactive(target, key, value){
    observer(value); // 递归 我就将这个对象 继续拦截
    Object.defineProperty(target,key,{
        get(){
            return value 
        },
        set(newValue){
            if(newValue !== value){ // 不一样值才更新
                // o_data.age = {n:200};o _data.age.n = 300;这种状况就须要从新观察
                observer(newValue)
                updateView();
                value = newValue
            }
        }
    });
}
复制代码

2,新加数组项不会触发更新

改写observer, 为了让不改变原数组,巧妙运用切片编程spa

let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 继承
['push','shift','unshift'].forEach(method=>{
    proto[method] = function(){ //函数劫持 把函数进行重写 内部 继续调用老的方法
        updateView(); // 切片编程
        oldArrayPrototype[method].call(this, ...arguments)
        // oldArrayPrototype[method].apply(this, arguments)
    }
});
function observer(target){
    if(typeof target !== 'object' || target == null){
        return target;
    }
    if(Array.isArray(target)){ // 拦截数组 给数组的方法进行了重写 
        Object.setPrototypeOf(target,proto); // 写个循环 赋予给target
        // target.__proto__ = proto;
        for(let i = 0; i< target.length ;i++){
            observer(target[i]);
        }
    }else{
        Object.keys(target).forEach((key) => {
            defineReactive(target, key, target[key]);
        });
    }
   
}
复制代码

3,新增的属性不更新,使用$set啦

整合代码以下

let oldArrayPrototype = Array.prototype;
let proto = Object.create(oldArrayPrototype); // 继承
['push','shift','unshift'].forEach(method=>{
    proto[method] = function(){ //函数劫持 把函数进行重写 内部 继续调用老的方法
        updateView(); // 切片编程
        oldArrayPrototype[method].call(this,...arguments)
    }
});
function observer(target){
    if(typeof target !== 'object' || target == null){
        return target;
    }
    if(Array.isArray(target)){ // 拦截数组 给数组的方法进行了重写 
        Object.setPrototypeOf(target,proto); // 写个循环 赋予给target
        // target.__proto__ = proto;
        for(let i = 0; i< target.length ;i++){
            observer(target[i]);
        }
    }else{
        Object.keys(target).forEach((key) => {
            defineReactive(target, key, target[key]);
        });
    }
   
}
function defineReactive(target,key,value){
    observer(value); // 递归 我就将这个对象 继续拦截
    Object.defineProperty(target,key,{
        get(){ // get 中会进行依赖收集
            return value 
        },
        set(newValue){
            if(newValue !== value){
                // data.age = {n:200}; data.age.n = 300;这种状况就须要从新观察
                observer(newValue) 
                updateView()
                value = newValue
            }
        }
    });
}
function updateView(){
    console.log('我动啦')
}

class Vue {
    constructor(options) {
        this._data = options.data;
        observer(this._data);
    }
}
// 测试
let o = new Vue({
    data: {
        test: "I am test."
    }
});
o._data.test = "hello,world.";  /* 我动了 */

复制代码
相关文章
相关标签/搜索