Skip to content

js实现监测一个普通对象的变化 #2

@z2014

Description

@z2014

刚开始学习vue的时候,之前总是听别人说vue实现了双向绑定,主要凸显就是表单那里,后来在学习完vue了以后,我发现vue实现的双向绑定的效果react也能实现实时更新视图的效果,也就一直陷入了react也是双向绑定的怪圈。后来看了和两篇博客之后也对双向绑定有了进一步的理解

对于如何实现vue的双向绑定,网上已经有很多的博客去介绍了,大致思路就是依靠Object.defineProperty来在set和get里面去执行回调函数,虽然网上已经有了很多的博客,但还是要自己去实现一遍才能深入理解这其中的问题。

  • 首先是如果变量是一个基本数据类型,那么它是基于值进行传递的,我们可以直接进行Object.defineProperty
  • 如果变量是一个对象类型的话,那么我们需要做一次递归

基于此,我们就可以检测一个普通对象的变化,但是做到这里,我们却没有办法对数组进行监测,在vue中,如果你是将一个值push进入数组中,vue是可以在页面上实时更新的,但你用arr[3] = 4这种方式去更新数组时,数组确实得到了改变,但是却不能更新视图。也就是说当我们调用Array.prototype里面的方法时,他才会进行数据的双向绑定。当然我们可以重新写Array.prototype.push = funvction(){},但是这对于用这个框架的人而言就是一件很恶心的事情了,而且速度也没法和原生的进行比较。一个数组对象的__proto__本身是应该指向Array.prototype,但是在这中间,我们可以再加一层,也就是fakePrototype,我们在这个里面去调用原生的Array.prototype.
具体代码实现如下

const OP = Object.prototype
const oam = ['push', 'pop']

class observer {
  constructor(obj, callback) {
      if (OP.toString.call(obj) !== '[object Object]') {
          console.error(obj + ' is not object')
      }
      this.$callback = callback
      this.observe(obj)
  }
  observe(obj) {
      if (OP.toString.call(obj) === '[object Array]') {
          this.overwriteArray(obj)
      }
      Object.keys(obj).forEach(function(key,index,array){
           let val = obj[key]
  	   Object.defineProperty(obj, key, {
  	        get: function() {
  		    console.log('get', val)
  		    return val
  		},
  		set: (function(newVal) {
  		    console.log('set', this)
  		    this.$callback(val)
  		    val = newVal
  		}).bind(this)
  	    })
  	    if (OP.toString.call(obj[key]) === '[object Object]' || OP.toString.call(obj[key]) === '[object Array]')
 {
                this.observe(obj[key])
  	    }
  	},this)
  }
  overwriteArray(array) {
      let original = Array.prototype,
  	   result,
  	   self = this,
  	   overwrite = Object.create(Array.prototype)
      Object.keys(oam).forEach(function(key,index,array) {
           let method = oam[index],
  		oldVal = []
  	    Object.defineProperty(overwrite, method, {
  		 value: function () {
                     oldVal = this.slice(0)
                     let arg = [].slice.apply(arguments)
                          result = original[method].apply(this, arg)
                          self.observe(this)
                          self.$callback(this)
                          return result
  		      },
  		  writable: true,
  		  enumerable: true,
  		  configurable: true
  	      })
  	    },this)
  	 console.log('overwrite', overwrite)
  	 array.__proto__ = overwrite
   }
}

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions