vue 响应式基本实现 (基于 Object.defineProperty )

前言:该篇文章是vue响应式的基本实现,包括对对象,数组的处理,以及对部分数组方法的重写。

        vue的响应式原理实现 ==>> 基于Object.defineProperty 实现

特点:

        1. 使用对象的时候 必须先声明属性 ,这个属性才是响应式的

        2. 增加不存在的属性 不能更新视图 (需要使用 vm.$set  ==> 该文章中没有实现)

        3. 默认会递归增加 get 和 set 属性

        4. 数组里套对象 对象是支持响应式变化的,如果是常量则没有效果

        5. 如果新增的数据 vue中也会帮你监控(对象类型)

        6. 修改数组索引和长度 是不会导致视图更新的

代码实现:

let arrayProto = Array.prototype;         //保存下数组原型上的方法
let newProto = Object.create(arrayProto);       //以 数组原型上的方法为原型 创建新的对象 即 newProto.__proto__ = arrayProto
['push', 'unshift', 'splice', 'reverse', 'sort', 'shift', 'pop'].forEach(method => {
    // 'push', 'unshift', 'splice' ==>> 这三个方法会给数组新增元素,所以需要先监控新增的元素,在调用本身的方法
    // 'reverse','sort','shift','pop' ==>> 这几个方法不会新增元素,所以只需要调用数组本身对应的方法即可
    newProto[method] = function(...args) {
        let inserted = null;         //默认没有插入新的数据,即没有调用 'push', 'unshift', 'splice'
        switch (method){
            // args 是一个数组
            case 'push':
                inserted = args;
                break;
            case 'unshift':
                inserted = args;
                break;
            case 'splice':   //splice方法有三个参数,第三个参数才是被新增的元素
                inserted = args.slice(2);       //slice返回一个新的数组
                break;
        }
        console.log('数组类型导致视图更新')
        if (inserted) ArrayObserver(inserted);      //因为inserted是一个数组,所以要调用数组处理方法 ArrayObserver
        arrayProto[method].call(this, ...args);     //调用数组本身对应的方法
    }
})

function observer(obj){
    if(typeof obj !== 'object' || obj === null){      //obj是常量
        return obj;
    }
    if(Array.isArray(obj)){         //obj是数组
        Object.setPrototypeOf(obj,newProto); // 对数组部分方法进行重写;==>> obj.__proto__ = newProto
        ArrayObserver(obj);
    }else{          // obj 是对象
        for (const key in obj) {
            // 默认obj只有一层,即 {name:'张三'}
            defineReactive(obj, key, obj[key]);
        }
    }
}

function ArrayObserver(obj){        //处理数组类型的数据
    obj.forEach(item => {
        observer(item);  //监控每一个数组对象,如果是对象,会被defineReactive,而如果是常量,则被直接返回(不监控)
    });
}

// 给对象添加get/set属性 ===>> 实现响应式原理
function defineReactive(obj, key, value){
    observer(value);        //value有可能也是一个对象,所以也需要响应式  ==>> 缺点:在数据层级较深的情况下 递归创建 响应式数据,性能不好
    Object.defineProperty(obj, key, {       //给obj的key属性添加get/set
        get(){
            // 注意:这里不能返回 obj[key],会导致死循环(get中取值,又调用get....)
            //      ==>> RangeError: Maximum call stack size exceeded
            return value;
        },
        set(newValue){
            if(value !== newValue){     //如果新值和旧值是一样的,没必要更新
                observer(newValue);     //新值有可能也是一个对象,但如果是常量,会在 observer() 直接被返回
                value = newValue;       //这里也不能使用 obj[key] ==>> 调用get(),不能使用它的返回值去重新赋值
                console.log('视图更新')
            }
        }
    })
}

// ================== 测试代码 =================
// 多层对象
// let data = {name: {d: '123'}};
// observer(data);
// data.name.d = 'abc';
// console.log('===',data.name.d);
// ===============

// 使用对象的时候 必须先声明属性 ,这个属性才是响应式的
// let data = {name: {d: '123'}};
// observer(data);
// data.name = {age: 18}
// data.name.age = 20;
// console.log('====',data.name.age)

// ===============

// 数组里套对象 对象是支持响应式变化的,如果是常量则没有效果
let data = {name: [1, 2, 3, {age : 18}]};
observer(data);
// data.name[3].age = 30;
// data.name.splice(1, 0, 30);
data.name.push({a: 'abc'});
data.name[4].a = 'aaa'
console.log('==修改后的值',data.name[4].a)

补充:看一下 Object.create 在浏览器中的效果;

        可参考原型链的文章:ES5中对Object扩展的静态方法 -- Object.create可以实现原型继承(继承实现方法二,不常用)

                                            JS 的原型和原型链 以及 instanceof 是如何判断的(继承实现方法一,最基础,底层实现)

 

文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值