vue 数据劫持原理

1,什么是 Object.defineProperty

  • Object.defineProperty(obj,property,descriptor) 有三个参数 分别是
  • obj 对象 property 属性值 属性值 当前属性所拥有的特性

descriptor 有如下属性

  • get 获取值时触发
  • set 修改值时触发 
  • enumerable 是否可枚举  如果值为false就不可以别for in Object.keys等循环
  • value 当前值
  • configurable 是否可修改  
  • writeable是否可重写

数据劫持是双向绑定中的数据改变时触发视图修改需要的,由上述可知,当数据变化可以用Object.defineProperty的set方法触发,修改视图。

let info = {
        name: "sjf",
        age: 18,
      };
      Object.keys(info).forEach((ele) => {
        let value = info[ele];
        Object.defineProperty(info, ele, {
          enumerable: true,
          configurable: true,
          get() {
            return value;
            console.log("get", value);
          },
          set(newVal) {
            console.log("数据发生了变化,监听到了");
            if (newVal == value) {
              return;
            }
            value = newVal;
          },
        });
      });
      setTimeout((ele) => {
        info.age = 222;//数据发生了变化,监听到了
        console.log(info.age);
      }, 20);

2,对象的劫持

import { arrayMethods } from "./array"

class Observer{
    constructor(value) {//需要对value属性重新定义
        // value可能是对象 肯恩故
        // value.__ob__ = this
        //value.__ob__ 对象里还是对象,一直会导致死循环
        // 有这个属性 表示被观测过
        Object.defineProperty(value, "__ob__", {
            value: this,
            enumerable: false,// 不能被枚举,避免死循环
            configurable:false//不能删除此属性
        })
        
        if (Array.isArray(value)) {
            // 数组不用defineProperty来进行代理,性能不好

            // push shift reverse sort 我要重写这些方法 增加更新逻辑    
            // value.__proto__ = arrayMethods//当是数组时改写方法为自己重写后的方法
            Object.setPrototypeOf(value, arrayMethods)//循环将属性赋予上去
            this.observeArray(value)//原有数组中的对象
        } else {
            this.walk(value)
        }
        
    }
    observeArray(value) {
        for (let i = 0; i < value.length; i++) {
            observe(value[i])
        }
    }
    walk(data) {
        // 将对象中的所有key,重新用 defineProperty定义响应式的
        Object.keys(data).forEach(key => {
            defineReactive(data,key,data[key])
        })
    }
}  


export function defineReactive(data, key, value) {
    // vue2中数据嵌套不要过深,过深浪费性能
    // value 可能也是一个对象
    observe(value)//对结果递归拦截
    Object.defineProperty(data, key, {
        get() {
            console.log("取值")
            return value
        },
        set(newValue) {
            console.log("设置值")
            if (newValue === value) {
                return 
            }
            observe(value)
            // 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
            value = newValue
        }
    })
}



export function observe(data) {
    console.log("-----------", data)
    // 需要对数据defineProperty 进行重新观测
    // 只对对象类型进行观测,非对象类型无法观测
    if (typeof data !== "object" || data == null) {
        return 
    }
    if (data.__ob__) {//数据被观测过
        return 
    }
    return new Observer(data)
}



 

代码解析


export function observe(data) {
    console.log("-----------", data)
    // 需要对数据defineProperty 进行重新观测
    // 只对对象类型进行观测,非对象类型无法观测
    if (typeof data !== "object" || data == null) {
        return 
    }
    if (data.__ob__) {//数据被观测过
        return 
    }
    return new Observer(data)
}
  • observe 数据观测拦截
  • 如果数据不是对象类型 或者 数据是 null类型 就终止数据观测
  • 如果数据观测过 具有 __ob__属性就也终止数据观测
export function defineReactive(data, key, value) {
    // vue2中数据嵌套不要过深,过深浪费性能
    // value 可能也是一个对象
    observe(value)//对结果递归拦截
    Object.defineProperty(data, key, {
        get() {
            console.log("取值")
            return value
        },
        set(newValue) {
            console.log("设置值")
            if (newValue === value) {
                return 
            }
            observe(value)
            // 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
            value = newValue
        }
    })
}
  • defineReactive定义响应式数据
  • 数据传递过来的时候,value的值可能是对象所以用observe(value)对结果递归拦截
  • 如果用户设置的是一个对象,就继续将用户设置的对象变成响应式的
class Observer{
    constructor(value) {//需要对value属性重新定义
        // value可能是对象 肯恩故
        // value.__ob__ = this
        //value.__ob__ 对象里还是对象,一直会导致死循环
        // 有这个属性 表示被观测过
        Object.defineProperty(value, "__ob__", {
            value: this,
            enumerable: false,// 不能被枚举,避免死循环
            configurable:false//不能删除此属性
        })
        
        if (Array.isArray(value)) {
            // 数组不用defineProperty来进行代理,性能不好

            // push shift reverse sort 我要重写这些方法 增加更新逻辑    
            // value.__proto__ = arrayMethods//当是数组时改写方法为自己重写后的方法
            debugger
            Object.setPrototypeOf(value, arrayMethods)//循环将属性赋予上去
            this.observeArray(value)//原有数组中的对象
        } else {
            this.walk(value)
        }
        
    }
    observeArray(value) {
        for (let i = 0; i < value.length; i++) {
            observe(value[i])
        }
    }
    walk(data) {
        // 将对象中的所有key,重新用 defineProperty定义响应式的
        Object.keys(data).forEach(key => {
            defineReactive(data,key,data[key])
        })
    }
}  

  • 首先判断传入的数据是对象还是
  • 数组类型,用Array.isArray 判断,
  • 如果是数组类型
  • 就将重写过后的数组方法arrayMethods设置到数据value的原型上
  • 然后调用observeArray方法循环处理数据调用 observe 方法处理每一项数据
  • 如果是对象类型
  • 就使用object.keys 循环处理对象并调用defineReactive对对象的数据进行响应式处理

3,数据属性值为数组时,数据原型上的方法的重写,以监听数组的变化

let oldArrayProtoMethods = Array.prototype;
// 数组原来的方法
// oldArrayProtoMethods.push = function () {

// }
// 不能直接改写数组原有方法,不可靠,因为只有被vue控制的数组才需要改写

export let arrayMethods = Object.create(Array.prototype);
// Object.create() 继承  ?
// 改变原数组
// concat slice ... 都不能改变原数组
let methods = ["push", "pop", "shift", "unshift", "splice", "reverse", "sort"];

methods.forEach(method => {
    // AOP切片编程
    arrayMethods[method] = function (...args) {//重写数组方法
        // todo ...
        // 有可能用户新增的数据是对象格式,也需要进行拦截
        let inserted
        let ob = this.__ob__
        switch (method) {
            case 'push':
            case "unshift":
                inserted = args
                break;
            case "splice": //splice(0,1,2)
                inserted = args.slice(2)
            default:
                break;
        }
        if (inserted) {
            ob.observeArray(inserted)
        }
        console.log("数组变化")
        let result = oldArrayProtoMethods[method].call(this, ...args)

        return result

    }
})
  • Object.create()  使用现有对象提供新对象的proto

  • 只由unshift,splice会产生新增的值,这写新增的数据需要使用observeArray实现数据的观测

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值