数据劫持、数据代理与回调地狱

109 阅读3分钟

数据劫持

什么是数据劫持

以原始数据为基础, 对数据进行一份复刻

特点:复刻出来的数据是不允许修改的, 值从原始数据里面获取

语法:

Object.defineProperty('哪一个对象' , '属性' , {'配置项'}')

配置项里的属性:

value : 这个属性的对应值

writable : 该属性能否被修改 , 默认为false 不能修改

enumerable : 该属性能不能被枚举(遍历), 默认为false 不能枚举.

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

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

        const obj = {}
        obj.name = '李四'
        Object.defineProperty(obj, 'age', {
        
            //value与writable不能与get 同时出现
            // value: 18,
            // writable : true,
            enumerable: true,
            get (){
                console.log('访问get函数 , 获取obj的age属性, 然后这个函数内可以做很多事')
                return 300
            },
            set (Val){
               console.log('修改obj的age属性 , 所以触发了setter 函数, 你想要设置的值为', Val) 

            }

        },)
        console.log(obj)
        obj.age = 99
        //修改age的值此时会运行 set 此时控制台会打印 '修改obj的age属性 , 所以触发了setter 函数, 你想要设置的值为', 99  这串字符串
        

如何进行数据劫持

基础版

    //获取元素
        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() {
                return obj.age
            },
            set(Val) {
                box.innerHTML = `res的年龄:${Val}`
                obj.age = Val
            }
        })
        res.age = 100
        box2.innerHTML = `obj的年龄:${obj.age}`
        console.log(res)
        console.log(obj)

优化版

        //原是对象
        const obj = {}
        obj.name = '李四'
        obj.age = 18

        function observer(origin, callback) {
            //创建一个对象 , 将 origin 内部的属性劫持到这个对象内
            const target = {}

            //劫持 origin 上的属性 到 target 上

            for (let key in origin) {
                Object.defineProperty(target, key, {
                    enumerable: true,
                    get() {

                        return origin[key]
                    },
                    set(Val) {
                        origin[key] = Val
                        callback(target)
                    }
                })
            }

            //最后将劫持到的 target 返回出去
            return target
        }

        const newObj = observer(obj, fn)
        console.log(newObj)
        function fn(res) {
            box.innerHTML = `姓名:${res.name} , 年龄:${res.age}`
        }

升级版

        //原对象
        const obj = {}
        obj.name = '李四'
        obj.age = 18
        
        //自己劫持自己
        for (let key in obj) {
            Object.defineProperties(obj, {
                //为了和原属性相同,所以会在原本的属性名前加一个下划线,用来区分
                ['_' + key]: {
                    value: obj[key],
                    writable: true
                },
                [key]: {
                    get() {
                        return obj['_' + key]
                    },
                    set(Val) {
                        obj['_' + key] = Val
                        box2.innerHTML = `res对象的姓名:${obj.name},年龄:${obj.age}`
                    }
                }
            })
        }
        //页面首次打开时渲染页面
        box2.innerHTML = `res对象的姓名:${obj.name},年龄:${obj.age}`        
        

数据代理

是官方给的名字, 有部分程序员还是习惯性的叫做 数据劫持

proxy 是ES6 以后官方推出的, 是一个内置函数

       const obj = {
            name: '李四',
            age: 18
        }

        //new proxy 第一个参数为 要代理的对象 , 第二个参数 一些配置项, 最后返回一个代理后的对象,我们需要使用一个变量去接收.

        const res = new Proxy(obj, {
            get(target, property) {
                /**
                 * 第一个形参 : 就是你要代理的对象, 在当前案例中指的是 obj
                 * 
                 * 第二个参数 : 就是该对象内部的某一个属性, 自动分配
                 *  
                 */

                return target[property]
            },
            Set(target, property, val) {
                target[property] = val
            }
        })

在代理完成后给原始对象新加一个属性,此时代理对象依然可以访问到( proxy 独有功能 )

回调地狱

什么是回调地狱

回调地狱不是我们写代码时出现的漏洞,只是我们在利用回调函数解决问题的时候,代码多了的一种视觉体验

例如:

       function fn (chenggong , shibai) {
            const timer = Math.ceil(Math.random()*3000)
            console.log('我去买水')
            setTimeout(() => {
                if(timer > 2000){
                    console.log('买水失败,用时',timer)
                    shibai()
                }else{console.log('买水成功,用时',timer)
                chenggong()
            }
            }, timer);
        }

        fn(
            () => {
                fn(
                    () => { console.log('班长买完水后, 又买了一箱饮料') },
                    () => { console.log('班长就买了一瓶水, 他不愿意给你们买饮料') }
                )
            },
            () => {
                fn(
                    () => { console.log('班长第二次买水 成功了') },
                    () => {
                        fn(
                            () => { console.log('班长第三次买水 成功了') },
                            () => { console.log('班长第三次买水 又失败了, 确实不争气') }
                        )
                    }
                )
            }
        )

缺点:不利于后期代码的阅读,与维护