数据劫持

163 阅读4分钟

数据劫持

  • 将来我在使用框架的时候(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)
    // console.log(obj.age)

数据劫持与渲染页面

  <h1 class="box"></h1>
  <h1 class="box2"></h1>
  <script>
     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 = 100
    console.log(res)
    
    box.innerHTML = `res年龄: ${res.age}`
    box2.innerHTML = `obj年龄: ${obj.age}`
  </script>

封装数据劫持

  <h1 class="box"></h1>
  <h1 class="box2"></h1>
  <script>
     const box = document.querySelector('.box')
     const box2 = document.querySelector('.box2')
     
    // 原始对象
    const obj = {}
    obj.name = '张三'
    obj.age = 18

    // 如果劫持的属性多了,原本的写法不太方便,代码量比较多,所以封装数据劫持
    function observer(origin, callback) {
      // 1. 创建一个对象,将origin内部的属性劫持到这个对象内
      const target = {}
      // 2. 劫持origin上的属性到target中
      for(let key in origin) {
        // console.log(key)
        Object.defineProperty(target, key, {
          enumerable: true,
          get() {
            // console.log('你现在访问了obj的age属性,然后这函数内可以做很多事')
           return origin[key]
          },
          set(val) {
            // console.log('你现在想要修改obj的age属性,所以触发了setter函数,你想要设置的值为:', val)
            // box.innerHTML = `res年龄: ${val}`
            origin[key] = val
            callback(target)
          }
        })
      }
      // 3. 将劫持后的target返回出去
      return target
    }
    // const newObj = observer(obj, () => {console.log('回调函数执行的时候,会打印我这个字符串')})
    const newObj = observer(obj, fn)
    // console.log(newObj)
    // newObj.age = 999
    // newObj.name = '李四'

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

数据劫持升级

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

    const obj = {}
    obj.age = 18
    obj.name = '张三'
    // 将数据劫持后的对象属性存放在res对象中
    const res = {}
    // 基础版
    /*Object.defineProperties(res, {
      age: {
        get() {
          return obj.age
        },
        set(val) {
          box2.innerHTML = `res对象的age属性: ${val}, name属性: ${res.name}`
          obj.age = val
        }
      },
      name: {
        get() {
          return obj.name
        },
        set(val) {
          box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${val}`
          obj.name = val
        }
      }
    })*/

    // 利用循环 优化代码量
    for(let key in obj) {
      Object.defineProperties(res, {
        // 此处的key 我们的需求是当一个变量使用,如果直接写 那么会当成一个字符串,解决方案在key加一个[]包裹起来,当成变量
        [key]: {
          get() {
            return obj[key]
          },
          set(val) {
            
            obj[key] = val
            box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${res.name}`
          }
        },
      })
    }
    // console.log(res)
    // 首次打开页面的时候,给页面做一个赋值
    box1.innerHTML = `obj对象的 age属性: ${obj.age}, name属性: ${obj.name}`
    box2.innerHTML = `res对象的 age属性: ${res.age}, name属性: ${res.name}`
    // 首次渲染完毕页面后 更改两个对象的属性值
    obj.age = 666 //obj的修改不会影响 box1
    obj.name = '李四'
    res.age = 999 //res的修改会触发set函数,set函数内有一行代码会让box2更新,所以res的修改会让页面重新渲染
    res.name = '王五'
    <div class="box"></div>
  <script>
    const box = document.querySelector('.box')

    const obj = {}
    obj.age = 18
    obj.name = '张三'
    // 将数据劫持后的对象属性存放在res对象中
    // const res = {}
    // // 利用循环 优化代码量
    // for(let key in obj) {
    //   Object.defineProperties(res, {
    //     // 此处的key 我们的需求是当一个变量使用,如果直接写 那么会当成一个字符串,解决方案在key加一个[]包裹起来,当成变量
    //     [key]: {
    //       get() {
    //         return obj[key]
    //       },
    //       set(val) {
            
    //         obj[key] = val
    //         box2.innerHTML = `res对象的age属性: ${res.age}, name属性: ${res.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的修改不会影响 box1
    obj.name = '李四'