JS数据劫持相关

28 阅读1分钟

数据劫持

  • 将来我们在使用框架的时候(vue), 框架目前都支持一个 "数据驱动视图"完成数据驱动视图 需要借助 数据劫持帮助我们完成
  • 以原始数据为基础, 对数据进行一份复刻, 复刻出来数据是不允许修改的, 值从原始数据里面获取

语法:

    Object.defineProperty('哪一个对象', '属性', '配置项')
  • 配置项: value: 这个属性对应的值

  • writable: 该属性是否可以被重写, 默认是 false 不允许被修改

  • enumerable: 该属性是否可以被枚举, 默认是 false 不能被枚举到

  • get: 是一个函数, 叫做 getter 获取器, 可以决定当前属性的值, 不能与 value writable 同时出现

  • set: 是一个函数, 叫做 setter 设置器, 当你需要修改这个属性的时候, 会触发该函数

        const obj = {}
        obj.name = '张三'

        Object.defineProperty(obj, 'age', {
            // value: 18,
            // writable: true,
            enumerable: true,
            get () {
                console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
                return 300
            },
            set (val) {
                console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
            }
        })

        obj.age = 99

        console.log(obj.age)


数据劫持与渲染页面

    <h1 class="box"></h1>
    <h1 class="box2"></h1>
        const box = document.querySelector('.box')
        const box2 = document.querySelector('.box2')

        // 原始对象
        const obj = {}
        obj.name = '张三'
        obj.age = 18

        // 将 obj 的属性, 劫持到这个对象内
        const res = {}

        Object.defineProperty(res, 'age', {
            enumerable: true,
            get() {
                // console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
                return obj.age
            },
            set(val) {
                // console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
                box.innerHTML = `res 年龄: ${val}`
                obj.age = val
            }
        })

        res.age = 999

        box.innerHTML = `res 年龄: ${res.age}`
        box2.innerHTML = `obj 年龄: ${obj.age}`

        obj.age = 666
        res.age = 777
        // box2.innerHTML = `obj 年龄: ${obj.age}`

        // obj.age = 777
        // box2.innerHTML = `obj 年龄: ${obj.age}`

封装数据劫持

        // 将来工作中: 这个函数由框架提供, 我们直接使用即可
        // 为什么要封装函数: 如果劫持的属性多了, 原本的写法不太方便, 代码量比较多, 所以封装数据劫持
        function observer(origin, callback) {
            // 1. 创建一个对象, 将 origin 内部的属性劫持到这个对象内
            const target = {}

            // 2. 劫持 origin 上的属性到 target 中
            for (let key in origin) {
                Object.defineProperty(target, key, {
                    enumerable: true,
                    get() {
                        // console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
                        return origin[key]
                    },
                    set(val) {
                        // console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
                        origin[key] = val
                        callback(target)
                    }
                })
            }

            // 3. 将劫持后的 target 返回出去
            return target
        }


        const box = document.querySelector('.box')
        const box2 = document.querySelector('.box2')

        // 原始对象
        const obj = {}
        obj.name = '张三'
        obj.age = 18

        // 创建一个数据劫持后的对象
        const newObj = observer(obj, fn)
        function fn(res) {
            box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
        }

        newObj.age = 666
        newObj.name = '李四'

数据劫持升级

        const box = document.querySelector('.box')

        // 原始对象
        const obj = {}
        obj.age = 18
        obj.name = '张三'

        // 升级版: 自己劫持自己 (全都在 原始对象上进行操作)
        for (let key in obj) {
            Object.defineProperties(obj, {
                /**
                 *  通常我们在处理 "自己劫持自己" 的时候, 不会再对象的原属性上操作, 而是复制出来一份一摸一样的数据操作
                 * 
                 *  为了和原属姓名相同, 所以会在 原本的属性名前 加一个下划线, 用来区分
                */
                ['_' + key]: {
                    value: obj[key],
                    writable: true
                },
                [key]: {
                    get() {
                        return obj['_' + key]
                    },
                    set(val) {
                        obj['_' + key] = val
                        box.innerHTML = `obj 对象的 age 属性: ${obj.age}, name 属性: ${obj.name}`
                    }
                }
            })
        }

        // 首次打开页面的时候, 给页面做一个赋值
        box.innerHTML = `obj 对象的 age 属性: ${obj.age}, name 属性: ${obj.name}`

        // 首次渲染完毕页面后 更改对象的属性值
        obj.age = 666
        obj.name = '李四'