vue怎么根据一个数据改变触发事件_Object.defineProperty实现一个简单的双向绑定

a736d2cd4e39c97354b4aca1d7f2e22b.png

好久好久好久都没有更新过文章了。无论怎样,要开始加油了!切入正题ing~~~

使用过vue的人都知道,vue的双向绑定是靠Object.defineProperty来实现的。抛开vue,如果让我们通过Object.defineProperty来实现一个双向绑定,应该如何实现呢?

先看一段vue使用实例:

 
"app"> <form> <input type="text" v-model="number"> <button type="button" v-click="increment">增加button> form> <h3 v-bind="number">h3>  div> var app = new Vue({  el:'#app',  data: {   number: 0  },  methods: {   increment: function() {      this.number ++;    }  } })

通过这段代码,我们可以看到其涉及到的功能点有

v-model

v-bind

v-click

那么怎么来实现呢?我们根据代码拆分一下功能点吧

Vue是一个构造函数,因此,首先声明一个带有配置参数的构造函数

监听data中数据变化

监听到data变化后,去触发view的渲染

view中数据更新触发data变更(v-model)和事件(onclick)绑定

根据上面的功能点,我们一个个来实现它

一、声明一个构造函数

function Vue(options) {  this.$el = options.el  this.$data = options.data  this.$methods = options.methods  this._observe(this.$data) // 监听数据变化}

二、监听data中数据变化

我们都知道,监听数据变化有两种方法Object.defineProperty和Proxy(下篇文章来介绍),vue2.0使用了前者。我们先来简单熟悉一下Object.defineProperty的用法。

let value = 'dandan'Object.defineProperty(window, 'name', {  configurable:true,  enumerable: true,  get: function() {  return value  },  set: function(val) {    console.log('变更啦---')    if (val!==value) {      value = val    }  }})console.log(name) // dandanvalue = 'xiaoqi' // 变更啦---console.log(name) // xiaoqi

通过这段代码,相信大家已经大概知道Object.defineProperty如何监听数据变化了吧

接下来,我们监听下data中数据变化,监听data数据变化,无非就是递归循环data中所有属性,并通过Object.defineProperty来定义它。从而实现数据监听。我们就不递归每一层了,只循环遍历下data第一层数据。

Vue.prototype._observe = function(obj) {    for(let key in obj) {      let value = obj[key]      if(obj.hasOwnProperty(key)) {        Object.defineProperty(this.$data, key, {          enumerable: true,          configurable: true,          get: function (param) {             return value          },          set: function(newV) {            if (value !== newVal) {              value = newVal;              // todo  数据变化后,触发dom渲染,从而实现mv绑定            }          }        })      }    }  }

三、监听到data变化后,去触发view的渲染

第二步我们已经监听到数据的变化了,那么怎么去触发dom更新呢?我们首先需要拿到dom和data&methods之间的关系

那么问题来了,怎么拿?仔细看下我们的代码,data和dom之间的映射关系其实是存在我们的视图view中,因此,我们通过遍历视图代码,即可拿到对应关系(v-model=**,v-bind=**,v-click=**)

代码如下:

function Vue(options) {  this.$el = options.el  this.$data = options.data  this.$methods = options.methods  this.$binding = {}  this._observe(this.$data) // 监听数据变化}Vue.prototype._complie = function (root) { // root 为 id为app的Element元素,也就是我们的根元素    var _this = this;    var nodes = root.children;    for (var i = 0; i < nodes.length; i++) {      var node = nodes[i];      if (node.children.length) {  // 对所有元素进行遍历,并进行处理        this._complie(node);      }      if (node.hasAttribute('v-click')) {  // 如果有v-click属性,我们监听它的onclick事件,触发increment事件,即number++        node.onclick = (function () {          var attrVal = nodes[i].getAttribute('v-click');          return _this.$methods[attrVal].bind(_this.$data);  //bind是使data的作用域与method函数的作用域保持一致        })();      }      if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) { // 如果有v-model属性,并且元素是INPUT或者TEXTAREA,我们监听它的input事件        node.addEventListener('input', (function(key) {            var attrVal = node.getAttribute('v-model');           //_this._binding['number']._directives = [一个Watcher实例]           // 其中Watcher.prototype.update = function () {           //    node['vaule'] = _this.$data['number'];  这就将node的值保持与number一致           // }          _this._binding[attrVal]._directives.push(new Watcher(              'input',            node,            _this,            attrVal,            'value'          ))          return function() {            _this.$data[attrVal] =  nodes[key].value; // 使number 的值与 node的value保持一致,已经实现了双向绑定          }        })(i));      }       if (node.hasAttribute('v-bind')) { // 如果有v-bind属性,我们只要使node的值及时更新为data中number的值即可        var attrVal = node.getAttribute('v-bind');        _this._binding[attrVal]._directives.push(new Watcher(          'text',          node,          _this,          attrVal,          'innerHTML'        ))      }    }     function Watcher(name, el, vm, exp, attr) {    this.name = name;         //指令名称,例如文本节点,该值设为"text"    this.el = el;             //指令对应的DOM元素    this.vm = vm;             //指令所属myVue实例    this.exp = exp;           //指令对应的值,本例如"number"    this.attr = attr;         //绑定的属性值,本例为"innerHTML"    this.update();  }  Watcher.prototype.update = function () {    this.el[this.attr] = this.vm.$data[this.exp]; //比如 H3.innerHTML = this.data.number; 当number改变时,会触发这个update函数,保证对应的DOM内容进行了更新。  }

拿到指令与data或methods的关系后,我们就可以在set中,更新dom了,从而实现双向绑定,更新的代码如下:

 this.binding[key]._directives.forEach(function (item) {  // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新    item.update(); }) 

好了,一个简单的双向绑定实现了。

拖了一周的文章,终于完成了

再去看会儿react去!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值