前言:该篇文章是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 是如何判断的(继承实现方法一,最基础,底层实现)
文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出!
