javascrip中的数据劫持与数据代理

一、用Object.defineProperty实现数据劫持

首先,举一个例子,VUE2中的响应式原理:

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用
Object.defineProperty 把这些 property 全部转为
getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持
IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property
被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装
vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的
setter 触发时,会通知 watcher,从而使它关联的组件重新渲染
javascrip中的数据劫持与数据代理_第1张图片

也就是说vue对data选项的所有property加了监听,当数据变化时,vue2中每一个实例都有一个watcher进行依赖收集,也就是这个实例所关联的property。property变化时候被setter监听到,然后通知watcher再进行后续处理。
我们先用一个Object.defineProperty实现一个简单的数据劫持:

    let a = {
        name: "小红",
        age: 12,
        oinfo: {
            school: "狗屁大学",
            hobby: ["football", "pingpang"]
        },
    }

    const observer = function (data) {

        if (!data || typeof data !== 'object') {
            return
        }
        for (const key in data) {
            if (Object.hasOwnProperty.call(data, key)) {
                let currentData = data[key]
                observer(currentData)
                Object.defineProperty(data, key, {
                    enumerable: true,
                    configurable: false,
                    get() {
                        console.log(`调用数据时候:${key}数据被劫持劫持`);
                        return currentData
                    },
                    set(value) {
                        console.log(`设置数据时候:${key}数据被劫持劫持`);
                        currentData = value
                    }
                })
            }
        }
    }
    observer(a)
    a.oinfo.hobby = ["1", "2"]
    console.log(a.oinfo.hobby);

通过observer这个方法,我们把对象a里面的所有property进行了劫持,在对property进行get和set的时候我们就可以做自己想做的事情了,比如说进行标记的添加之类的,当然这里只是简单的实现,在vue中为了能够更深度的监听数组的变化vue重写了数组的方法。

	const extendedArr = Object.create(Array.prototype)
    //重写这几个方法,用Object.assign把新的方法混入到Array.prototype上
    const methods = ["push", "pop", "shift", "splice", "sort", "reverse"]
    methods.forEach(method => {
        const oldm = Array.prototype[method]
        const newm = function (...args) {
            oldm.apply(this, args)
            console.log(`劫持方法,爱干嘛干嘛`)
        }
        extendedArr[method] = newm
    })

二、用Proxy实现数据劫持

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

一个代理类的简单例子:

	let student = {
        name: "小红",
        age: 15
    }
    student = new Proxy(student, {
        set(obj, prop, value) {
            if (prop == "age" && typeof value !== "number") {
                throw new Error("年龄必须是数字")
            }
            Reflect.set(...arguments)
        },
        get(obj, prop) {
            console.log("get操作");
            return Reflect.get(...arguments)
        }
    })

上面代码把student这个对象进行了代理,判断一下age属性,当age不是number时候就报错。
VUE3中实现响应式核心变成了代理模式:

当把一个普通的 JavaScript 对象作为 data 选项传给应用或组件实例的时候,Vue 会使用带有 getter 和 setter
的处理程序遍历其所有 property 并将其转换为 Proxy 。这是 ES6 仅有的特性,但是我们在 Vue 3 版本也使用了 Object.defineProperty 来支持 IE 浏览器。两者具有相同的 Surface API,但是 Proxy 版本更精简,同时提升了性能。

	let a = {
        name: "小红",
        age: 12,
        oinfo: {
            school: "大学",
            hobby: ["football", "pingpang"]
        },
    }
	function observerP(data) {
        if (!data && Object.prototype.toString.call(data) !== '[object, Object]') {
            return
        }

        Object.keys(data).forEach(key => {
            let currentItem = data[key]
            if (typeof currentItem == "object") {
                observerP(currentItem)
                data[key] = new Proxy(currentItem, {
                    set(obj, prop, value) {
                        console.log("调用了set");
                        return Reflect.set(...arguments);
                    }
                })
            } else {
                Object.defineProperty(data, key, {
                    set(value) {
                        console.log("调用了set");
                        currentItem = value
                    }
                })
            }
        })
    }
    observerP(a)

当我们a.oinfo.hobby.push(“fff”)向数组中追加数据时,依然被set函数捕获,所以说Proxy支持代理数组的变化。

是不是很简单啊?喜欢的小伙伴留言点赞关注吧!

你可能感兴趣的:(javascrip基础知识,javascript,vue.js,前端)