用proxy实现一个更优雅的vue

前言

若是你有读过Vue的源码,或者有了解过Vue的响应原理,那么你必定知道Object.defineProperty(), 那么你也应该知道,Vue 2.x里,是经过 递归 + 遍历 data对象来实现对数据的监控的, 你可能还会知道,咱们使用的时候,直接经过数组的下标给数组设置值,不能实时响应,是由于Object.defineProperty() 没法监控到数组下标的变化,而咱们日常所用的数组方法 push, pop, shift, unshift, splice, sort, reverse, 其实不是真正的数组方法,而是被修改过的,这些都是由于 Object.defineProperty() 提供的能力有限,没法作到完美。javascript

网上看过不少关于Vue的源码解读或者实现一个简易版的Vue的教程,还都是用 Object.defineProperty (大概是为兼容性考虑吧), 而 Object.defineProperty() 确实存在诸多限制, 听说Vue的3.x版本会改用Proxy,那么今天咱们就先来尝尝鲜,用Proxy实现一个简单版的Vuehtml

proxy 介绍

Proxy 用于修改某些操做的默认行为,等同于在语言层面作出修改,因此属于一种“元编程”(meta programming),即对编程语言进行编程。前端

Proxy 能够理解成,在目标对象以前架设一层“拦截”,外界对该对象的访问,都必须先经过这层拦截,所以提供了一种机制,能够对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操做,能够译为“代理器”。vue

以上引用内容来自阮一峰的es6教程的Proxy章节 原文地址请戳这里java

咱们来看看如何用Proxy去定义一个监听数据的函数node

定义 observe

_observe (data){
    var that = this
    
    // 把代理器返回的对象存到 this.$data 里面
    this.$data = new Proxy(data, {
    set(target,key,value){
      // 利用 Reflect 还原默认的赋值操做
      let res =  Reflect.set(target,key,value)
      // 这行就是监控代码了
      that.handles[key].map(item => {item.update()})
      return res
    }
    })
}
复制代码

当触发set的时候,就会执行 that.handles[key].map(item => {item.update()}) ,这句代码的做用就是执行 该属性名下的全部 "监视器"git

那么,监视器怎么来的呢? 请继续看如下代码es6

定义 compile

_compile (root){
       const nodes = Array.prototype.slice.call(root.children)
       let data = this.$data
       nodes.map(node => {
         // 若是不是末尾节点,就递归
         if(node.children.length > 0) this._complie(node)
         // 处理 v-bind 指令
         if(node.hasAttribute('v-bind')) {
           let key = node.getAttribute('v-bind')
           this._pushWatcher(new Watcher(node,'innerHTML',data,key))
         }
         // 处理 v-model 指令
         if(node.hasAttribute('v-model')) {
           let key = node.getAttribute('v-model')
           this._pushWatcher(new Watcher(node,'value',data,key))
           node.addEventListener('input',() => {data[key] = node.value})
         }
         // 处理 v-click 指令
         if(node.hasAttribute('v-click')) {
           let methodName = node.getAttribute('v-click')
           let mothod = this.$methods[methodName].bind(data)
           node.addEventListener('click',mothod)
         }
       })
     }
复制代码

上面这段代码,看起来很长,但是实际上,只作了意见很简单的事情, 就是 "编译" html 模板,把有 v-bindv-modelv-click 都给加上对应的 通知监控github

  1. 通知 就是 this._pushWatcher(...) , 至关因而安装一个监听器,这样只要 this.$data 有发生 set 操做的话,就会执行 this._pushWatcher 括号里面传的函数,来通知节点更新数据编程

  2. 监控 就是 node.addEventListener(...) 监听相应的事件,而后执行对应的 watcher 或者 methods

this._pushWatcher 又作了什么呢?

_pushWatcher (watcher) {
      if (!this._binding[watcher.key]) this._binding[watcher.key] = []
      this._binding[watcher.key].push(watcher)
    }
复制代码

这个就更简单了,若是 this._binding[watcher.key] 为空,就初始化,而后向里面添加一个 监听器

最后,咱们再来看看,监听器是怎么实现的

定义 Watcher

class Watcher {
     constructor (node,attr,data,key) {
       this.node = node
       this.attr = attr
       this.data = data
       this.key = key
     }
     update () {
       this.node[this.attr] = this.data[this.key]
     }
   }
复制代码

Watcher 是一个类,只有一个方法,就是更新数据,怎么知道要更新哪一个节点,更新为何数据呢? 在实例化(new)的时候,传的参数就是定义这些的

这样,咱们就实现初步的双向绑定了,整个代码大概只有50行。其实还能够更少, 可是更少的话,就是继续阉割功能了(虽然目前实现的也是严重阉割版的), 可是我以为实现这些,恰好能够很少很多帮我咱们理解vue的本质。

最后

本文最终实现代码已经放在 github上,想要直接看效果的同窗,能够上去直接copy,运行。

若是以为本文对您有用,请给本文的github加个star,万分感谢

另外,github上还有其余一些关于前端的教程和组件,有兴趣的童鞋能够看看,大家的支持就是我最大的动力。

相关文章
相关标签/搜索