使用Vue如何写一个双向数据绑定(面试常见)


Posted in Javascript onApril 20, 2018

1、原理

Vue的双向数据绑定的原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里对原理不做过多描述,主要还是来实现一个实例。为了使代码更加的清晰,这里只会实现最基本的内容,主要实现v-model,v-bind 和v-click三个命令,其他命令也可以自行补充。

添加网上的一张图

使用Vue如何写一个双向数据绑定(面试常见)

2、实现

页面结构很简单,如下

<div id="app">
 <form>
  <input type="text" v-model="number">
  <button type="button" v-click="increment">增加</button>
 </form>
 <h3 v-bind="number"></h3>
 </div>

包含:

 1. 一个input,使用v-model指令
 2. 一个button,使用v-click指令
 3. 一个h3,使用v-bind指令。

我们最后会通过类似于vue的方式来使用我们的双向数据绑定,结合我们的数据结构添加注释

var app = new myVue({
  el:'#app',
  data: {
  number: 0
  },
  methods: {
  increment: function() {
   this.number ++;
  },
  }
 })

首先我们需要定义一个myVue构造函数:

function myVue(options) {
}

为了初始化这个构造函数,给它添加一 个_init属性

function myVue(options) {
 this._init(options);
}
myVue.prototype._init = function (options) {
 this.$options = options; // options 为上面使用时传入的结构体,包括el,data,methods
 this.$el = document.querySelector(options.el); // el是 #app, this.$el是id为app的Element元素
 this.$data = options.data; // this.$data = {number: 0}
 this.$methods = options.methods; // this.$methods = {increment: function(){}}
 }

接下来实现_obverse函数,对data进行处理,重写data的set和get函数

并改造_init函数

myVue.prototype._obverse = function (obj) { // obj = {number: 0}
 var value;
 for (key in obj) { //遍历obj对象
  if (obj.hasOwnProperty(key)) {
  value = obj[key]; 
  if (typeof value === 'object') { //如果值还是对象,则遍历处理
   this._obverse(value);
  }
  Object.defineProperty(this.$data, key, { //关键
   enumerable: true,
   configurable: true,
   get: function () {
   console.log(`获取${value}`);
   return value;
   },
   set: function (newVal) {
   console.log(`更新${newVal}`);
   if (value !== newVal) {
    value = newVal;
   }
   }
  })
  }
 }
 }
 myVue.prototype._init = function (options) {
 this.$options = options;
 this.$el = document.querySelector(options.el);
 this.$data = options.data;
 this.$methods = options.methods;
 this._obverse(this.$data);
 }

接下来我们写一个指令类Watcher,用来绑定更新函数,实现对DOM元素的更新

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内容进行了更新。
 }

更新_init函数以及_obverse函数

myVue.prototype._init = function (options) {
 //...
 this._binding = {}; //_binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
 //...
 }
 myVue.prototype._obverse = function (obj) {
 //...
  if (obj.hasOwnProperty(key)) {
  this._binding[key] = { // 按照前面的数据,_binding = {number: _directives: []}                                     
   _directives: []
  };
  //...
  var binding = this._binding[key];
  Object.defineProperty(this.$data, key, {
   //...
   set: function (newVal) {
   console.log(`更新${newVal}`);
   if (value !== newVal) {
    value = newVal;
    binding._directives.forEach(function (item) { // 当number改变时,触发_binding[number]._directives 中的绑定的Watcher类的更新
    item.update();
    })
   }
   }
  })
  }
 }
 }

那么如何将view与model进行绑定呢?接下来我们定义一个_compile函数,用来解析我们的指令(v-bind,v-model,v-clickde)等,并在这个过程中对view与model进行绑定。

myVue.prototype._init = function (options) {
 //...
 this._complie(this.$el);
 }
myVue.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'
  ))
  }
 }
 }

至此,我们已经实现了一个简单vue的双向绑定功能,包括v-bind, v-model, v-click三个指令。效果如下图

使用Vue如何写一个双向数据绑定(面试常见)

附上全部代码,不到150行

<!DOCTYPE html>
<head>
 <title>myVue</title>
</head>
<style>
 #app {
 text-align: center;
 }
</style>
<body>
 <div id="app">
 <form>
  <input type="text" v-model="number">
  <button type="button" v-click="increment">增加</button>
 </form>
 <h3 v-bind="number"></h3>
 </div>
</body>
<script>
 function myVue(options) {
 this._init(options);
 }
 myVue.prototype._init = function (options) {
 this.$options = options;
 this.$el = document.querySelector(options.el);
 this.$data = options.data;
 this.$methods = options.methods;
 this._binding = {};
 this._obverse(this.$data);
 this._complie(this.$el);
 }
 myVue.prototype._obverse = function (obj) {
 var value;
 for (key in obj) {
  if (obj.hasOwnProperty(key)) {
  this._binding[key] = {                                       
   _directives: []
  };
  value = obj[key];
  if (typeof value === 'object') {
   this._obverse(value);
  }
  var binding = this._binding[key];
  Object.defineProperty(this.$data, key, {
   enumerable: true,
   configurable: true,
   get: function () {
   console.log(`获取${value}`);
   return value;
   },
   set: function (newVal) {
   console.log(`更新${newVal}`);
   if (value !== newVal) {
    value = newVal;
    binding._directives.forEach(function (item) {
    item.update();
    })
   }
   }
  })
  }
 }
 }
 myVue.prototype._complie = function (root) {
 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')) {
  node.onclick = (function () {
   var attrVal = nodes[i].getAttribute('v-click');
   return _this.$methods[attrVal].bind(_this.$data);
  })();
  }
  if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
  node.addEventListener('input', (function(key) {
   var attrVal = node.getAttribute('v-model');
   _this._binding[attrVal]._directives.push(new Watcher(
   'input',
   node,
   _this,
   attrVal,
   'value'
   ))
   return function() {
   _this.$data[attrVal] = nodes[key].value;
   }
  })(i));
  } 
  if (node.hasAttribute('v-bind')) {
  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];
 }
 window.onload = function() {
 var app = new myVue({
  el:'#app',
  data: {
  number: 0
  },
  methods: {
  increment: function() {
   this.number ++;
  },
  }
 })
 }
</script>

总结

以上所述是小编给大家介绍的使用Vue如何写一个双向数据绑定(面试常见),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
JS Excel读取和写入操作(模板操作)实现代码
Apr 11 Javascript
JavaScript 面向对象的之私有成员和公开成员
May 04 Javascript
学习js在线html(富文本,所见即所得)编辑器
Dec 18 Javascript
JavaScript Promise启示录
Aug 12 Javascript
浅谈JSON中stringify 函数、toJosn函数和parse函数
Jan 26 Javascript
JQuery包裹DOM节点的方法
Jun 11 Javascript
javascript格式化日期时间方法汇总
Jun 19 Javascript
JavaScript创建闭包的两种方式的优劣与区别分析
Jun 22 Javascript
浅谈redux以及react-redux简单实现
Aug 28 Javascript
Vue实现移动端左右滑动效果的方法
Nov 27 Javascript
Angular Excel 导入与导出的实现代码
Apr 17 Javascript
JavaScript通如何过RGraph实现动态仪表盘
Oct 15 Javascript
Vue中如何实现proxy代理
Apr 20 #Javascript
React diff算法的实现示例
Apr 20 #Javascript
vue中子组件向父组件传递数据的实例代码(实现加减功能)
Apr 20 #Javascript
node实现登录图片验证码的示例代码
Apr 20 #Javascript
vue项目中api接口管理总结
Apr 20 #Javascript
通过jquery获取上传文件名称、类型和大小的实现代码
Apr 19 #jQuery
js Element Traversal规范中的元素遍历方法
Apr 19 #Javascript
You might like
PHP中通过fopen()函数访问远程文件示例
2014/11/18 PHP
PHP操作MySQL的mysql_fetch_* 函数的常见用法教程
2015/12/25 PHP
js下获取div中的数据的原理分析
2010/04/07 Javascript
jQuery中实现动画效果的基本操作介绍
2013/04/16 Javascript
JS中for循序中延迟加载动态效果的具体实现
2013/08/18 Javascript
jquery动态添加元素事件失效问题解决方法
2014/05/23 Javascript
基于jQuery插件实现环形图标菜单旋转切换特效
2015/05/15 Javascript
创建一个类Person的简单实例
2016/05/17 Javascript
Bootstrap布局方式详解
2016/05/27 Javascript
ros::spin() 和 ros::spinOnce()函数的区别及详解
2016/10/01 Javascript
微信小程序自定义轮播图
2018/11/04 Javascript
使用Easyui实现查询条件的后端传递并自动刷新表格的两种方法
2019/09/09 Javascript
jQuery--遍历操作实例小结【后代、同胞及过滤】
2020/05/22 jQuery
Python使用metaclass实现Singleton模式的方法
2015/05/05 Python
Python实现的最近最少使用算法
2015/07/10 Python
python中的字典使用分享
2016/07/31 Python
Python模拟登录的多种方法(四种)
2018/06/01 Python
Pyorch之numpy与torch之间相互转换方式
2019/12/31 Python
python读取csv文件指定行的2种方法详解
2020/02/13 Python
Python中remove漏删和索引越界问题的解决
2020/03/18 Python
基于django和dropzone.js实现上传文件
2020/11/24 Python
css3中background新增的4个新的相关属性用法介绍
2013/09/26 HTML / CSS
HTML5 WebGL 实现民航客机飞行监控系统
2019/07/25 HTML / CSS
国贸专业个人求职信范文
2014/01/08 职场文书
CAD制图人员的自荐信
2014/02/07 职场文书
欢送退休感言
2014/02/08 职场文书
《翻越远方的大山》教学反思
2014/04/13 职场文书
节水标语大全
2014/06/11 职场文书
给校长的一封检讨书
2014/09/20 职场文书
幼儿园见习报告
2014/10/30 职场文书
初中成绩单评语
2014/12/29 职场文书
学习十八大的感悟
2015/08/11 职场文书
小学生优秀作文范文(六篇)
2019/07/10 职场文书
python+opencv实现视频抽帧示例代码
2021/06/11 Python
浅谈@Value和@Bean的执行顺序问题
2021/06/16 Java/Android
vue选项卡切换的实现案例
2022/04/11 Vue.js