数据劫持

86 阅读4分钟

数据劫持

以原始数据为基础,对数据进行一份复刻,复刻出来的数据是不允许被修改的,值从原始数据里面获取。

  • 语法:Object.defineProperty('哪一个对象','属性','{配置项}')。
  • 配置项:
  1. value:这个属性对应的值。
  2. writable:该属性是否可以被重写,默认是false不允许被修改。
  3. enumerable:该属性是否可以被枚举,默认是false,不能被枚举到。
  4. get:是一个函数,叫做getter 获取器,可以决定当前该属性的值,不能与 value writable 同时出现。
  5. set:是一个函数,叫做setter 设置器,当你需要修改这个属性的时候,会触发该函数。
const obj = {}
    obj.name = '张三'
Object.defineProperty(obj,name,{
    enumerable:true,
    get(){
        console.log('你现在访问了 obj 的 age 属性, 然后这函数内可以做很多事')
        return 66
    }
    set () {
        console.log('你现在想要修改 obj 的 age 属性, 所以触发了 setter 函数, 你要想要设置的值为: ', val)
    }
})
obj.age = 88
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
        
        //封装数据劫持的函数
        function observer(origin,callback){
            const targer = {}
            for(let key in origin){
            Object.defineProperty(target,key,{
                enumerable:true,
                get(){
                    return origin[key]
                },
                set(val){
                    origin[key] = val
                    callback(target)
                    
                }
            })
            }
            return target
            
        }
        const box = document.querySelector('.box')
        const box2 = document.querySelector('.box2')
        const obj = {}
        obj.name = '张三'
        obj.age = 18
        const newObj = new observer(obj,fn)
        function fn (){
            box.innerHTML = `年龄: ${res.age}; 名字: ${res.name}`
        }  
        newObj.name = '王武'
        newObj.age = 88

数据劫持升级

  • 基础版数据劫持语法: Object.defineProperty(那个对象, 属性, {配置项})。
  • 升级版数据劫持语法: Object.defineProperties('那个对象', '配置项')。
const box1 = document.querySelector('.box1')
const box2 = document.querySelector('.box2')

        // 原始对象
const obj = {}
obj.age = 18
obj.name = '张三'
//利用循环 优化代码量
const res = {}
for (let key in obj) {
    Object.defineProperties(res,{
    [key]:{
    get () {
        return obj[key]
    },
    set (val) {
        obj[key] = val
        box2.innerHTML = `res 对象的 age 属性: ${res.age}, name 属性: ${res.name}`
    }
    },
    })
}
box1.innerHTML = `obj 对象的age 属性:${obj.age},name属性:${obj.name}`
box2.innerHTML = `res 对象的age 属性:${res.age},name属性:${res.name}`
obj.name = '李四'
obj.age = 666
res.name = '王五'
res.age = 888
//数据劫持升级版
 <h1 class="box"></h1>
  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 = '李四'

数据代理

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

  • proxy 是 ES6以后官方推出的 是一个内置构造函数
const obj = {
            name: '张三',
            age: 18
        }
const res = new Proxy(obj,{
    get (target,property) {
        return target[property]
    },
    set (target,property,val) {
        target[property] = val
        console.log('你想要修改形参target的${property}属性,值为${val}')
    }
})
// 在代理完成后给原始对象新加一个属性, 此时代理对象依然能够访问到   (Proxy 独有的功能)
        obj.abc = 'qwer'
        console.log(res.abc)
        res.age =888

回调函数

回调函数 本质上就是一个普通函数

  • 一个函数A以实参的形式传递到函数B中,在函数B中,以形参的方式调用函数A,此时函数A就可以称为函数B的回调函数,回调函数的使用场景为异步代码的解决方案
// 网络请求模拟
        function fn(chenggong, shibai) {
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')

            setTimeout(() => {
                if (timer > 2500) {
                    console.log('买水失败, 用时: ', timer)
                    shibai()
                } else {
                    console.log('买水成功, 用时: ', timer)
                    chenggong()
                }
            }, timer)
        }

        fn(
            () => { console.log('谢谢班长, 我和你开玩笑的, 退了吧!') },
            () => { console.log('买不到就别回来了') }
        )

回调地狱

这不是我们写代码的时候出现的某个漏洞,只是我们在利用回调函数解决问题的时候, 代码量多了之后的一个视觉体验;回调地狱的代码不利于我们去维护或者管理, 所以后续再处理异步任务的时候我们需要一些更加简洁的方法,此时出现了一个东西 叫做 promise 他也是一个异步代码的解决方案

 function fn(chenggong, shibai) {
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')

            setTimeout(() => {
                if (timer > 2900) {
                    console.log('买水失败, 用时: ', timer)
                    shibai()
                } else {
                    console.log('买水成功, 用时: ', timer)
                    chenggong()
                }
            }, timer)
        }

        /**
         *  需求: 
         *      在班长买水失败后, 让他再次去买水(重新调用 fn 函数)
         * 
         *  新需求: 如果班长 第二次也失败了, 让他继续去买水
         * 
         *  新需求: 第一次买水成功的时候, 让班长再去买一箱饮料
        */
        fn(
            () => {
                fn(
                    () => { console.log('班长买完水后, 又买了一箱饮料') },
                    () => { console.log('班长就买了一瓶水, 他不愿意给你们买饮料') }
                )
            },
            () => {
                fn(
                    () => { console.log('班长第二次买水 成功了') },
                    () => {
                        fn(
                            () => { console.log('班长第三次买水 成功了') },
                            () => { console.log('班长第三次买水 又失败了, 确实不争气') }
                        )
                    }
                )
            }
        )

promise

作用: 一种新的异步代码封装方案, 用来代替 回调函数的

  1. promise 的三个状态
  • 持续: pending
  • 成功: fulfilled
  • 失败: rejected
  1. promise 只会在持续状态转换为 成功;或者 从持续状态转换为 失败
  2. promise 的基本语法: promise 是 JS 内置的一个构造函数
  3. const p = new Promise()
  4. new Promise 得到的对象 我们叫做 promise 对象
  5. promise 对象上 有一些方法
  • => then 方法 (会在 promise 状态成功的时候 执行)
  • => catch 方法 (会在 promise 状态失败的时候 执行)
const p = new Promise(function (resolve, reject) {
            /**
             *  形参名无所谓
             *      第一个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 成功
             *      第二个形参: 内部的值是一个函数, 调用之后可以将当前这个 promise 的状态设置为 失败
            */

            // 书写我们的异步代码
            const timer = Math.ceil(Math.random() * 3000)
            console.log('班长, 去帮我买瓶水')

            setTimeout(() => {
                if (timer > 1500) {
                    console.log('买水失败, 用时: ', timer)
                    // shibai()
                    reject()
                } else {
                    console.log('买水成功, 用时: ', timer)
                    // chenggong()
                    resolve()
                }
            }, timer)
        })

        // console.log('打印 变量 p: ', p)

        p.then(() => {
            console.log('如果我这行内容打印, 说明 promise 的状态为 成功')
        })
        p.catch(() => {
            console.log('如果我这行内容打印, 说明 promise 的状态为 失败')
        })