欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue 数据劫持原理

程序员文章站 2022-07-02 20:28:17
...

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实现数据的观测