javascript之属性监听的实现

1,670 阅读3分钟

思考一个问题

我们现在很多人都在使用大火的mvvm框架,(vue,react)等。会对页面上的一些属性做双向绑定处理,我们修改数据的时候,页面的DOM也会随着自动更新。是不是很方便。

涉及到的知识点

仔细研究一下javascript,其实原生的javascript就已经提供了监听对象的属性变化的方法,那就是我们非常熟悉的getter & setter,对的你没听错,不要以为这个特性只存在于后端语言,其实javascript中也存在着这个特性,而且没有浏览器的兼容性问题,只不过我们很少用。

Object.defineProperty(obj,prop,descriptor) 目前兼容性做的不错,移动端上兼容绝大部分的浏览器,PC上也能够兼容到IE9及以上。注意,该方法在IE8上也是能够使用的,但是其只能够传入DOM对象,如果传入其他对象会报错。可能有人会对该方法的作用产生疑问,其实该方法极大的优化了对象的获取和修改属性方式。

本文并没有使用到configurable``writable``enumerable等配置,有兴趣的话可以自行查阅资料。

那怎么实现呢

我们接下来就拿Object.defineProperty举个栗子 首先我们给obj对象添加一个可以监听的变化的属性:

let obj = {}
obj._show = false
Object.defineProperty(obj, 'show', {
    get: function() {
        alert(this._show)
        return this._show
    },
    set: function(val) {
        console.log(val)
        this._show = val
    }
})

如果我们执行obj.show = "true", 那么就会在控制台打印true。 如果我们执行let a = obj.show,则会在页面弹出显示为false的警告框。

其实讲到这里,我们已经实现了我们自己想要的结果。

不过细心的童鞋可能会问:我们不是已经有一个show属性么,为什么要再额外定义一个变量_show呢,这不是多余的么 满足该同学的好奇心,我们把代码修改成下面这样。

let obj = {}
Object.defineProperty(obj, 'show', {
    get: function() {
        alert(this.show)
        return this.show
    },
    set: function(val) {
        console.log(val)
        this.show = val
    }
})

我们运行一下这个代码。

什么情况,栈溢出。为啥陷入了一个打印show属性值的死循环呢?我们明明只执行了一次的赋值操作。 因为在set的内部,我们又执行了一次this.show = val的操作,他又会触发set,陷入了死循环当中无法自拔。

所以我们引入了一个额外的_show参数。 其实我们在使用很多第三方类库的时候会看到很多_开头的属性,其实这些很大一部分就是来自于get/set

ES6中的Proxy?

ES6中的Proxy也可以实现同样的功能。本质原理其实都是一样的。 我们熟悉的vue框架是用Object.defineProperty实现的数据双向绑定,vue的开发者也明确表示,在下一个大版本,将用Proxy,全面代替Object.defineProperty

具体Proxy相关的用法,网上有很多很多,可以随便看看。

自定义属性监听器

export class ProxyDispatcher {
  static apply (object) {
    object.addPropertyListener = ProxyDispatcher.prototype.addPropertyListener
  }

  addPropertyListener(propertyName, listener) {
    const _this = this
    _this[`_${propertyName}`] = _this[propertyName]
    Object.defineProperty(_this, propertyName, {
      get: function() {
        return _this[`_${propertyName}`]
      },
      set: function(newValue) {
        _this[`_${propertyName}`] = newValue
        listener(newValue)
      }
    })
  }
}

然后我们在想用的地方添加属性监听就好了

ProxyDispatcher.apply(this)
this.addPropertyListener('show', () => {
  this.showChange();
})

我们就可以在自己的组件轻松的添加属性监听,并定义自己的个性回调了。 再也不需要暴露什么show() hide() blabla的方法了。是不是很简单方便。

小哥哥小姐姐转载希望附个出处哟^_^