Object.defineProperty 数据劫持加订阅发布者模式

124 阅读1分钟

Object.defineProperty

Object.defineProperty()  方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。

参数

  • obj 要定义属性的对象。
  • prop 要定义或修改的属性的名称 。
  • descriptor 要定义或修改的属性描述符
    • configurable 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false
    • enumerable 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。 默认为 false
    • value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined
    • writable 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符 (en-US)改变。 默认为 false
    • get 当访问该值时会调用此方法,方法需要 return 一个值,默认为 undefined
    • set 当值修改时会调用此方法,方法会传入一个新值。
let value = ''
Object.defineProperty(obj, key, {
  configurable: false,
  enumerable: true,
  writable: true,
  value: '1',
  // 取值
  get: function () {
    console.log(`key: ${key}, value: ${value}`)
    return value
  },
  // 设置值
  set: function (val) {
    value = val
  }
})

发布订阅

let dep = {
    clientList: {}, // 事件容器
    // 订阅
    on: (key, fn) => {
        // key 事件名 fn 事件
        if (this.clientList[key]) {
            this.clientList[key].push(fn)
        } else {
            this.clientList[key] = []
            this.clientList[key].push(fn)
        }
    },
    // 发布
    emit: () => {
        const key = Array.prototype.shift.call(arguments)
        const fns = this.clientList[key]
        // 判断是否有订阅
        if (!fns || fns.length === 0) {
          // 没有则什么都不做
          return false
        }
        for (let i = 0; i < fns.length; i++) {
          const fn = fns[i]
          fn.apply(this, arguments)
        }
    }
}

vm 数据劫持

  • data 数据对象
  • tag 发布订阅名称
  • dataKey 对象属性
  • selector 元素选择器
  • valueChange 对象属性变化时触发的函数
  • on 元素事件
let mv = function ({ data, tag, dataKey, selector, valueChange, on }) {
  let value = data[dataKey] || ''
  const el = document.querySelector(selector)
  // 数据劫持
  Object.defineProperty(data, dataKey, {
    // 取值
    get: function () {
      console.log(`key: ${dataKey}, value: ${value}`)
      return value
    },
    // 设置值
    set: function (val) {
      value = val
      // 设置值的时候触发发布
      dep.emit(tag, el, value, data)
    }
  })
  // 事件绑定
  if (on && typeof on === 'object') {
    for (const key in on) {
      if (on.hasOwnProperty(key) && typeof on[key] === 'function') {
        el.addEventListener(key, on[key])
      }
    }
  }
  // 订阅
  dep.on(tag, function (ele, value, data) {
    console.log('触发订阅')
    if (valueChange && typeof valueChange === 'function') {
      valueChange(ele, value, data)
    }
  })
}

测试

<div id="box1">box1</div>
const obj = {}
mv({
  data: obj,
  tag: 'onBox1',
  dataKey: 'name',
  selector: '#box1',
  valueChange: function (el, value, data) {
    console.log('name变化了')
    console.log(el, value, data)
  },
  on: {
    // 绑定元素点击事件
    click: function () {
      console.log('box点击事件')
    }
  }
})
obj.name = '小明' // 触发valueChange 方法
console.log(obj.name)