使用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 相关文章推荐
用正则表达式 动态创建/增加css style script 兼容IE firefox
Mar 10 Javascript
读jQuery之四(优雅的迭代)
Jun 20 Javascript
关于延迟加载JavaScript
May 05 Javascript
javascript密码强度校验代码(两种方法)
Aug 10 Javascript
JavaScript中获取HTML元素值的三种方法
Jun 20 Javascript
关于javascript的一些知识以及循环详解
Sep 12 Javascript
浅谈javascript的url参数parse和build函数
Mar 04 Javascript
jQuery实现html table行Tr的复制、删除、计算功能
Jul 10 jQuery
echarts同一页面中四个图表切换的js数据交互方法示例
Jul 03 Javascript
详解在React项目中安装并使用Less(用法总结)
Mar 18 Javascript
JavaScript封闭函数及常用内置对象示例
May 13 Javascript
解决Layui中layer报错的问题
Sep 03 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代码
2007/03/03 PHP
使ecshop模板中可引用常量的实现方法
2011/06/02 PHP
浅谈PHP解析URL函数parse_url和parse_str
2014/11/11 PHP
php+mysqli实现批量执行插入、更新及删除数据的方法
2015/01/29 PHP
php生成HTML文件的类方法
2019/10/11 PHP
php设计模式之状态模式实例分析【星际争霸游戏案例】
2020/03/26 PHP
个人总结的一些关于String、Function、Array的属性和用法
2007/01/10 Javascript
jquery插件jbox使用iframe关闭问题
2009/02/09 Javascript
js获得网页背景色和字体色的方法
2014/03/21 Javascript
jquery中attr和prop的区别分析
2015/03/16 Javascript
Web开发必知Javascript技巧大全
2016/02/23 Javascript
Angularjs material 实现搜索框功能
2016/03/08 Javascript
node.js学习之base64编码解码
2016/10/21 Javascript
利用nodejs监控文件变化并使用sftp上传到服务器
2017/02/18 NodeJs
angularjs中回车键触发某一事件的方法
2017/04/24 Javascript
微信小程序 Animation实现图片旋转动画示例
2018/08/22 Javascript
JavaScript 判断iPhone X Series机型的方法
2019/01/28 Javascript
微信小程序Flex布局用法深入浅出分析
2019/04/25 Javascript
快速对接payjq的个人微信支付接口过程解析
2019/08/15 Javascript
javascript刷新父页面方法汇总详解
2019/10/10 Javascript
Linux下Python获取IP地址的代码
2014/11/30 Python
python 循环while和for in简单实例
2016/08/16 Python
python简单实例训练(21~30)
2017/11/15 Python
Python+matplotlib+numpy绘制精美的条形统计图
2018/01/02 Python
python使用logging模块发送邮件代码示例
2018/01/18 Python
Gauss-Seidel迭代算法的Python实现详解
2019/06/29 Python
Django框架反向解析操作详解
2019/11/28 Python
Python 实现一个计时器
2020/07/28 Python
python绘制分布折线图的示例
2020/09/24 Python
Html5剪切板功能的实现代码
2018/06/29 HTML / CSS
Canvas 帧动画吃苹果小游戏
2020/08/05 HTML / CSS
NEW LOOK官网:英国时装零售巨头之一,快时尚品牌
2017/01/11 全球购物
《满井游记》教学反思
2014/02/26 职场文书
《折线统计图》教学反思
2016/02/22 职场文书
幼儿园2016年圣诞活动总结
2016/03/31 职场文书
MySQL数据库实验之 触发器和存储过程
2022/06/21 MySQL