【学习笔记70】数据劫持

本文探讨了数据驱动视图的渲染优化,包括直接操作DOM和利用数据劫持技术(如Object.defineProperty)来创建不可变对象。接着介绍了如何封装数据劫持,并结合输入事件实现实时渲染,最后展示了更高级的数据劫持策略,如对象深度劫持。
摘要由CSDN通过智能技术生成

一、 数据驱动视图

  • 多次渲染页面,多的时候,比较麻烦和繁琐
        const box = document.querySelector('.box')
        const obj = {
            name: 'QF666',
            age: 18
        }
        box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`;

        obj.age = 99;
        box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`;


        obj.name = 'QF999';
        box.innerHTML = `名字: ${obj.name};    年龄: ${obj.age}`;
        console.log(obj);

二、数据劫持

  • 将原始数据,劫持出一份一摸一样, 听起来有点像浅拷贝
  • 劫持出来的数据, 默认是不可以修改的
语法: Object.defineProperty(那个对象, '对象的key', {配置项})
    配置项:
    1. value        访问这个值 之后, 得到结果
    2. writable     决定当前这个属性能否被修改, 默认是 false
    3. enumerable   决定当前这个属性能否被枚举, 决定当前这个属性能否被遍历到
    4. getter       是一个函数, 是一个获取器, 当访问这个属性时, 会执行这个函数
                    getter不能和value、writable一起使用

    5. setter      是一个函数, 是一个设置器, 当设置这个属性是, 会执行这个函数

1、初级版 

        const obj = {}
        obj.name = 'QF666'
        console.log(obj)

        // Object.defineProperty(obj, str, {配置项})
        Object.defineProperty(obj, 'age', {
            // value: 'QF999',
            // writable: true,
            enumerable: true,
            get() {
                // console.log('你当前访问了这个age属性, 触发了get函数')
                return 'qwerty'
            },
            set(val) {
                console.log('你当前想要修改这个age属性, 修改的值是: ', val)
            }
        })
        obj.age = 999
        console.log(obj.age)

2、升级版

    <div id="box"></div>
    <script>
        // 实现数据劫持
        const box = document.querySelector('#box')
        const obj = {
            name: 'QF666',
            age: 18
        }
        console.log('原始对象obj:', obj)
        const res = {}

        Object.defineProperty(res, 'name', {
            get() {
                return obj.name
            },
            set(val) {
                // console.log('你想要修改这个属性的值, 新值为: ', val)
                obj.name = val
                box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
            }
        })

        Object.defineProperty(res, 'age', {
            get() {
                return obj.age
            },
            set(val) {
                // console.log('你想要修改这个属性的值, 新值为: ', val)
                obj.age = val
                box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
            }
        })
        box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
        res.age = 100;
    </script>

 三、封装数据劫持

    <input type="text" name="" id="inp">
    <div id="box"></div>

函数的封装 :

        const box = document.querySelector('#box')
        const obj = {
            name: 'QF666',
            age: 18
        }
        function observer(origin, callback) {
            const target = {}

            for (let k in origin) {
                Object.defineProperty(target, k, {
                    get() {
                        return origin[k]
                    },
                    set(val) {
                        // console.log('你现在想要修改的key是' , k, '修改的值为', val)
                        origin[k] = val
                        callback(target)
                    }
                })
            }
            callback(target)
            return target
        }

调用的 

        function fn(res) {
            box.innerHTML = `名字: ${res.name}; 年龄: ${res.age}`
        }

        const app = observer(obj, fn)


        document.querySelector('#inp').oninput = function (e) {
            app.age = e.target.value
        }

四、封装数据劫持+渲染

  • 模拟一个其他人写的代码, 框架的源码
  • 把需要渲染的代码, 都放到HTML 中, 然后由我这段代码帮助我们去渲染
    <div id="app">
        <h1> {{msg}} </h1>
        <h1> {{ name }} </h1>
        <h1> {{ age }} </h1>
        <h1> {{ abc }} </h1>
    </div>

封装的

    <script>
        function observer(options) {
            // 1. 验证root有没有传递
            if (options.root === undefined) {
                // console.log('您没有传递root属性, 请重新传递')
                // 手动返回一个错误
                throw new Error('您没有传递root属性, 请重新传递') 
            }

            // 2. 验证root能否正确获取到节点
            const rootHtml = document.querySelector(options.root)
            if (rootHtml == null) {
                // 手动返回一个错误
                throw new Error('您传递 root 的属性, 有问题, 请检查后重新传递') 
            }

            // 3. 验证是否传递data
            if (options.data == undefined) {
                throw new Error('您没有传递 data 属性, 请重新传递')
            }

            // 4. 验证data是否为一个对象
            if (options.data.constructor !== Object) {
                throw new Error('您传递的 data 不是一个对象, 请重新传递')
            }

            // 5. 数据劫持
            // 存储一份最初的html文本
            const rootHtmlStr = rootHtml.innerHTML
            //  这里存储最开始的具有{{}}的HTML文本,
            //  如果不写, 第一次渲染完毕, HTML中就没有花括号, 第二次渲染正则没有办法正确匹配
            const _data = {}
            for (let k in options.data) {
                Object.defineProperty(_data, k, {
                    get() {
                        return options.data[k]
                    },
                    set(val) {
                        options.data[k] = val
                        // 5.2 数据更新后, 重新渲染页面
                        randers(rootHtml, _data, rootHtmlStr)
                    }
                })
            }

            // 5.1 首次执行, 渲染页面
            randers(rootHtml, _data, rootHtmlStr)

            // 6. 返回一个劫持后的数据
            return _data
        }

        function randers(root, _data, str) {
            // 准备一个正则
            const reg = /{{ *(\w+) *}}/g

            // 匹配到节点中所有的花括号, 存放在一个数组中
            const res = str.match(reg)

            // 遍历数组, 拿到每一个花括号
            res.forEach(item => {

                // 拿到花括号内的文本, 因为这是对象中的 key
                const key = reg.exec(str)[1]

                // 将原本花括号与内部文本, 全部替换为对象中实际的值
                str = str.replace(/{{ *(\w+) *}}/, _data[key])
            })

            // 重新将修改完毕的字符串, 渲染到页面
            root.innerHTML = str
        }
    </script>

调用的

    <script>
        // 我们后续使用, 就在这里
        const app = observer({
            root: '#app',
            data: {
                msg: '你好',
                name: 'QF001',
                age: 18,
                abc: '醒醒, 别睡了, 说你呢, 还睡'
            }
        })
    </script>

 五、数据劫持升级

    <div id="root"></div>

1、基本写法

        const obj = {
            name: 'QF001',
            age: 18
        }
        const res = {}
        Object.defineProperty(res, 'name', {
            get() {
                return obj.name;
            },
            set(val) {
                obj.name = val;
            }
        })

        obj.newName = 'QF999';
        console.log(obj);

        document.querySelector('#root').innerHTML = `name: ${obj.name}`;

2、升级版

语法: Object.defineProperties(到那个对象, {

        属性1: 配置项,

        属性2: 配置项

  })

        const obj = {
            name: 'QF001',
            age: 18
        }
        const res = {}
        Object.defineProperties(res, {
            name: {
                get() {
                    return obj.name
                },
                set(val) {
                    obj.name = val
                }
            },
            age: {
                get() {
                    return obj.age
                },
                set(val) {
                    obj.age = val
                }
            }
        })

        obj.newName = 'QF999';
        console.log(obj);
        document.querySelector('#root').innerHTML = `name: ${obj.name}`;

3、升级版(plus) 

        const obj = {
            name: 'QF001',
            age: 18
        }
        const res = {};
        // 升级版(plus)
        for (let k in obj) {
            // k === 'name'    2. k === age
            console.log(k);
            Object.defineProperties(res, {
                // 对象内部直接写 k 会帮当成字符串, 所以可以写成 [k], 将他识别为变量
                [k]: {
                    get() {
                        return obj[k];
                    },
                    set(val) {
                        obj[k] = val;
                    }
                },
            })
        }

        obj.newName = 'QF999';
        console.log(obj);
        document.querySelector('#root').innerHTML = `name: ${obj.name}`;

3、升级版(super plus),   自己劫持自己

        const obj = {
            name: 'QF001',
            age: 18
        }
   
        for (let k in obj) {
            Object.defineProperties(obj, {
                ['_' + k]: {    
                    // 我在我这个对象内把所有属性复制一份, 放在自己这个对象内部
                    value: obj[k],
                    writable: true
                },
                [k]: {
                    get () {
                        return obj['_' + k]
                    },
                    set(val) {
                        obj['_' + k] = val

                        document.querySelector('#root').innerHTML = `name: ${obj.name}`
                    }
                }
            })
        }
        obj.newName = 'QF999'
        console.log(obj)

        document.querySelector('#root').innerHTML = `name: ${obj.name}`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值