vue双向数据绑定知识点总结


Posted in Javascript onApril 18, 2018

1.原理

vue的双向数据绑定的原理相信大家都十分了解;主要是通过ES5的Object对象的defineProperty属性;重写data的set和get函数来实现的

所以接下来不使用ES6进行实际的代码开发;过程中如果函数使用父级this的情况;还是使用显示缓存中间变量和闭包来处理;原因是箭头函数没有独立的执行上下文this;所以箭头函数内部出现this对象会直接访问父级;所以也能看出箭头函数是无法完全替代function的使用场景的;比如我们需要独立的this或者argument的时候

1.2 defineProperty是什么

语法:

Object.defineProperty(obj, prop, descriptor)

参数:

obj:必要的目标对象

prop:必要的需要定义或者修改的属性名

descriptor:必要的目标属性全部拥有的属性

返回值:

返回传入的第一个函数;即第一个参数obj

该方法允许精确的添加或者修改对象的属性;通过赋值来添加的普通属性会创建在属性枚举期间显示(fon...in;object.key);这些添加的值可以被改变也可以删除;也可以给这个属性设置一些特性;比如是否只读不可写;目前提供两种形式:数据描述(set;get;value;writable;enumerable;confingurable)和存取器描述(set;get)

数据描述

当修改或者定义对象的某个属性的时候;给这个属性添加一些特性

var obj = {
 name:'xiangha'
}
// 对象已有的属性添加特性描述
Object.defineProperty(obj,'name',{
 configurable:true | false, // 如果是false则不可以删除
 enumerable:true | false, // 如果为false则在枚举时候会忽略
 value:'任意类型的值,默认undefined'
 writable:true | false // 如果为false则不可采用数据运算符进行赋值
});
但是存在一个交叉;如果wrirable为true;而configurable为false的时候;所以需要枚举处理enumerable为false
--- 我是一个writable栗子 ---
var obj = {};
Object.defineProperty(obj,'val',{
 value:'xiangha',
 writable:false, // false
 enumerable:true,
 configurable:true
});
obj.val = '书记'; // 这个时候是更改不了a的
--- 我是一个configurable栗子 ---
var obj = {};
Object.defineProperty(obj,'val',{
 value:'xiangha',
 writable:true, // true
 enumerable:true,
 configurable:false // false
});
obj.val = '书记'; // 这个时候是val发生了改变
delete obj.val 会返回false;并且val没有删除
--- 我是一个enumerable栗子 --- 
var obj = {};
Object.defineProperty(obj,'val',{
 value:'xiangha',
 writable:true,
 enumerable:false, // false
 configurable:true
});
for(var i in obj){
 console.log(obj[i]) // 没有具体值
}

综上:对于我们有影响主要是configurable控制是否可以删除;writable控制是否可以修改赋值;enumerable是否可以枚举

所以说一旦使用Object.defineProperty()给对象添加属性;那么如果不设置属性的特性;则默认值都为false

var obj = {}; 
Object.defineProperty(obj,'name',{}); // 定义了心属性name后;这个属性的特性的值都为false;这就导致name这个是不能重写不能枚举不能再次设置特性的
obj.name = '书记'; 
console.log(obj.name); // undefined
for(var i in obj){
 console.log(obj[i])
}

总结特性:

  • value:设置属性的值
  • writable ['raɪtəbl] :值是否可以重写
  • enumerable [ɪ'nju:mərəbəl]:目标属性是否可以被枚举
  • configurable [kən'fɪgərəbl]:目标属性是否可以被删除是否可以再次修改特性

存取器描述

var obj = {};
Object.defineProperty(obj,'name',{
 get:function(){} | undefined,
 set:function(){} | undefined,
 configuracble:true | false,
 enumerable:true | false
})
注意:当前使用了setter和getter方法;不允许使用writable和value两个属性

gettet&& setter

当设置获取对象的某个属性的时候;可以提供getter和setter方法

var obj = {};
var value = 'xiangha';
Object.defineProperty(obj,'name',{
 get:function(){
  // 获取值触发
  return value
 },
 set:function(val){
  // 设置值的时候触发;设置的新值通过参数val拿到
  value = val;
 }
});
console.log(obj.name); // xiangha
obj.name = '书记';
console,.log(obj.name); // 书记

get和set不是必须成对出现对;任写一个就行;如果不设置set和get方法;则为undefined

哈哈;前戏终于铺垫完成了

补充:如果使用vue开发项目;尝试去打印data对象的时候;会发现data内的每一个属性都有get和set属性方法;这里说明一下vue和angular的双向数据绑定不同

angular是用脏数据检测;Model发生改变的时候;会检测所有视图是否绑定了相关的数据;再更新视图

vue是使用的发布订阅模式;点对点的绑定数据

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 xhVue({
  el:'#app',
  data: {
  number: 0
  },
  methods: {
  increment: function() {
   this.number ++;
  },
  }
 })

2.1 定义

首先我们需要定义一个xhVue的构造函数

function xhVue(options){
 
}

2.2 添加

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

function xhVue(options){
 this._init(options);
}
xhVue.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; // increment
}

2.3 改造升级

改造_init函数;并且实现_xhob函数;对data进行处理;重写set和get函数

xhVue.prototype._xhob = function(obj){ // obj = {number:0}
 var value;
 for(key in obj){
  if(obj.hasOwnProperty(ket)){
   value = obj[key];
   if(typeof value === 'object'){
    this._xhob(value);
   }
   Object.defineProperty(this.$data,key,{
    enumerable:true,
    configurable:true,
    get:function(){
     return value;
    },
    set:function(newVal){
     if(value !== newVal){
      value = newVal;
     }
    }
   })
  }
 }
}
xhVue.prototype._init = function(options){
 this.$options = options;
 this.$el = document.querySelector(options.el);
 this.$data = options.data;
 this.$method = options.methods;
 this._xhob(this.$data);
}

2.4 xhWatcher

指令类watcher;用来绑定更新函数;实现对DOM更新

function xhWatcher(name,el,vm,exp,attr){
 this.name = name; // 指令名称;对于文本节点;例如text
 this.el = el; // 指令对应DOM元素
 this.vm = vm; // 指令所属vue实例
 this.exp = exp; // 指令对应的值;例如number
 this.attr = attr; // 绑定的属性值;例如innerHTML
 this.update();
}
xhWatcher.prototype.update = function(){
 this.el[this.attr] = this.vm.$data[this.exp];
 // 例如h3的innerHTML = this.data.number;当numner改变则会触发本update方法;保证对应的DOM实时更新
}

2.5 完善_init和_xhob

继续完善_init和_xhob函数

// 给init的时候增加一个对象来存储model和view的映射关系;也就是我们前面定义的xhWatcher的实例;当model发生变化时;我们会触发其中的指令另其更新;保证了view也同时更新
xhVue.prototype._init = function(options){
 this.$options = options;
 this.$el = document.querySelector(options.el);
 this.$data = options.data;
 this.$method = options.methods;
 
 this._binding = {}; // _binding
 this._xhob(this.$data);
}
// 通过init出来的_binding
xhVue.prototype._xhob = function(obj){ // obj = {number:0}
 var value;
 for(key in obj){
  if(obj.hasOwnProperty(ket)){
   this._binding[key] = {
    // _binding = {number:_directives:[]}
    _directives = []
   }
   value = obj[key];
   if(typeof value === 'object'){
    this._xhob(value);
   }
   var binding = this._binding[key];
   Object.defineProperty(this.$data,key,{
    enumerable:true,
    configurable:true,
    get:function(){
     return value;
    },
    set:function(newVal){
     if(value !== newVal){
      value = newVal;
      // 当number改变时;触发_binding[number]._directives中已绑定的xhWatcher更新
      binding._directives.forEach(function(item){
       item.update(); 
      });
     }
    }
   })
  }
 }
}

2.6 解析指令

怎么才能将view与model绑定;我们定义一个_xhcomplie函数来解析我们的指令(v-bind;v-model;v-clickde)并这这个过程中对view和model进行绑定

xhVue.prototype._xhcompile = function (root) {
 // root是id为app的element的元素;也就是根元素
 var _this = this;
 var nodes = root.children;
 for (var i = 0,len = nodes.length; i < len; i++) {
  var node = nodes[i];
  if (node.children.length) {
   // 所有元素进行处理
   this._xhcompile(node)
  };
  // 如果有v-click属性;我们监听他的click事件;触发increment事件,即number++
  if (node.hasAttribute('v-click')) {
   node.onclick = (function () {
    var attrVal = nodes[i].getAttribute('v-click');
    // bind让data的作用域与methods函数的作用域保持一致
    return _this.$method[attrVal].bind(_this.$data);
   })();
  };
  // 如果有v-model属性;并且元素是input或者textrea;我们监听他的input事件
  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 xhWatcher(
     'input', 
     node, 
     _this,
     attrVal, 
     'value'
    ));
    return function () {
     // 让number的值和node的value保持一致;就实现了双向数据绑定
     _this.$data[attrVal] = nodes[key].value
    }
   })(i));
  };
  // 如果有v-bind属性;我们要让node的值实时更新为data中number的值
  if (node.hasAttribute('v-bind')) {
   var attrVal = node.getAttribute('v-bind');
   _this._binding[attrVal]._directives.push(new xhWatcher(
    'text', 
    node, 
    _this,
    attrVal,
    'innerHTML'
   ))
  }
 }
}

并且将解析函数也加到_init函数中

xhVue.prototype._init = function(options){
 this.$options = options;
 this.$el = document.querySelector(options.el);
 this.$data = options.data;
 this.$method = options.methods;
 
 this._binding = {}; // _binding
 this._xhob(this.$data);
 this._xhcompile(this.$el);
}

最后

<!DOCTYPE html>
<html lang="en">

<head>
 <meta charset="UTF-8">
 <title>Document</title>
</head>
<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 xhVue(options) {
  this._init(options);
 }
 xhVue.prototype._init = function (options) {
  this.$options = options;
  this.$el = document.querySelector(options.el);
  this.$data = options.data;
  this.$method = options.methods;

  this._binding = {}; // _binding
  this._xhob(this.$data);
  this._xhcompile(this.$el);
 }

 xhVue.prototype._xhob = function (obj) {
  var value;
  for (key in obj) {
   if (obj.hasOwnProperty(key)) {
    this._binding[key] = {
     _directives: []
    }
    value = obj[key];
    if (typeof value === 'object') {
     this._xhob(value);
    }
    var binding = this._binding[key];
    Object.defineProperty(this.$data, key, {
     enumerable: true,
     configurable: true,
     get: function () {
      console.log(`get${value}`)
      return value;
     },
     set: function (newVal) {
      if (value !== newVal) {
       value = newVal;
       console.log(`set${newVal}`)
       // 当number改变时;触发_binding[number]._directives中已绑定的xhWatcher更新
       binding._directives.forEach(function (item) {
        item.update();
       });
      }
     }
    })
   }
  }
 }

 xhVue.prototype._xhcompile = function (root) {
  // root是id为app的element的元素;也就是根元素
  var _this = this;
  var nodes = root.children;
  for (var i = 0, len = nodes.length; i < len; i++) {
   var node = nodes[i];
   if (node.children.length) {
    // 所有元素进行处理
    this._xhcompile(node)
   };
   // 如果有v-click属性;我们监听他的click事件;触发increment事件,即number++
   if (node.hasAttribute('v-click')) {
    node.onclick = (function () {
     var attrVal = node.getAttribute('v-click');
     console.log(attrVal);
     // bind让data的作用域与method函数的作用域保持一致
     return _this.$method[attrVal].bind(_this.$data);
    })();
   };
   // 如果有v-model属性;并且元素是input或者textrea;我们监听他的input事件
   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 xhWatcher(
      'input',
      node,
      _this,
      attrVal,
      'value'
     ));
     return function () {
      // 让number的值和node的value保持一致;就实现了双向数据绑定
      _this.$data[attrVal] = nodes[key].value
     }
    })(i));
   };
   // 如果有v-bind属性;我们要让node的值实时更新为data中number的值
   if (node.hasAttribute('v-bind')) {
    var attrVal = node.getAttribute('v-bind');
    _this._binding[attrVal]._directives.push(new xhWatcher(
     'text',
     node,
     _this,
     attrVal,
     'innerHTML'
    ))
   }
  }
 }

 function xhWatcher(name, el, vm, exp, attr) {
  this.name = name; // 指令名称;对于文本节点;例如text
  this.el = el; // 指令对应DOM元素
  this.vm = vm; // 指令所属vue实例
  this.exp = exp; // 指令对应的值;例如number
  this.attr = attr; // 绑定的属性值;例如innerHTML
  this.update();
 }
 xhWatcher.prototype.update = function () {
  this.el[this.attr] = this.vm.$data[this.exp];
  // 例如h3的innerHTML = this.data.number;当numner改变则会触发本update方法;保证对应的DOM实时更新
 }
 var app = new xhVue({
  el: '#app',
  data: {
   number: 0
  },
  methods: {
   increment: function () {
    this.number++;
   }
  }
 });
</script>

</html>

所有的代码;复制到编辑器就可查看效果了~~

Javascript 相关文章推荐
jquery 简单导航实现代码
Sep 11 Javascript
JavaScript中的变量声明早于赋值分析
Mar 01 Javascript
JavaScript动态操作表格实例(添加,删除行,列及单元格)
Nov 25 Javascript
js Array操作的最简短最容易理解方法
Dec 09 Javascript
jQuery插件WebUploader实现文件上传
Nov 07 Javascript
实例解析jQuery工具函数
Dec 01 Javascript
jquery二级目录选中当前页的css样式
Dec 08 Javascript
微信小程序开发的四十个技术窍门总结(推荐)
Jan 23 Javascript
基于JQuery及AJAX实现名人名言随机生成器
Feb 10 Javascript
简单谈谈关于Angular Cli打包的事
Sep 05 Javascript
Vue filter介绍及详细使用
Apr 04 Javascript
基于Vue2x的图片预览插件的示例代码
May 14 Javascript
浅析vue中常见循环遍历指令的使用 v-for
Apr 18 #Javascript
关于Angularjs中跨域设置白名单问题
Apr 17 #Javascript
JS实现二维数组横纵列转置的方法
Apr 17 #Javascript
redux中间件之redux-thunk的具体使用
Apr 17 #Javascript
Vue进度条progressbar组件功能
Apr 17 #Javascript
微信小程序如何像vue一样在动态绑定类名
Apr 17 #Javascript
Angular数据绑定机制原理
Apr 17 #Javascript
You might like
解析php中如何调用用户自定义函数
2013/08/06 PHP
PHP中的闭包(匿名函数)浅析
2015/02/07 PHP
php删除txt文件指定行及按行读取txt文档数据的方法
2017/01/30 PHP
针对thinkPHP5框架存储过程bug重写的存储过程扩展类完整实例
2018/06/16 PHP
yii2.0框架使用 beforeAction 防非法登陆的方法分析
2019/09/11 PHP
jquery $.getJSON()跨域请求
2011/12/21 Javascript
jQuery常见开发技巧详细整理
2013/01/02 Javascript
javascript中数组的concat()方法使用介绍
2013/12/18 Javascript
JS清除文本框内容离开在恢复及鼠标离开文本框时触发js的方法
2016/01/12 Javascript
js实现界面向原生界面发消息并跳转功能
2016/11/22 Javascript
基于jQuery的表单填充实例
2017/08/22 jQuery
elementUI select组件默认选中效果实现的方法
2019/03/25 Javascript
node.js中process进程的概念和child_process子进程模块的使用方法示例
2020/02/11 Javascript
原生js实现点击轮播切换图片
2020/02/11 Javascript
如何使用three.js 制作一个三维的推箱子游戏
2020/07/29 Javascript
使用vue编写h5公众号跳转小程序的实现代码
2020/11/27 Vue.js
[01:07:19]DOTA2-DPC中国联赛 正赛 CDEC vs XG BO3 第一场 1月19日
2021/03/11 DOTA
Python开发的单词频率统计工具wordsworth使用方法
2014/06/25 Python
Python实现的圆形绘制(画圆)示例
2018/01/31 Python
Python 使用 attrs 和 cattrs 实现面向对象编程的实践
2019/06/12 Python
python实现在函数中修改变量值的方法
2019/07/16 Python
快速查找Python安装路径方法
2020/02/06 Python
Python闭包与装饰器原理及实例解析
2020/04/30 Python
解析HTML5的存储功能和web SQL的相关操作方法
2016/02/19 HTML / CSS
英国在线发型和美容产品商店:Beauty Cutie
2019/04/27 全球购物
英语道歉信范文
2014/01/09 职场文书
工作室成员个人发展规划范文
2014/01/24 职场文书
致接力运动员广播稿
2014/02/17 职场文书
应聘销售主管的求职信
2014/04/26 职场文书
药店促销活动策划方案
2014/08/24 职场文书
离婚协议书范本(2014版)
2014/09/28 职场文书
网络工程专业大学生求职信
2014/10/01 职场文书
python爬取豆瓣电影TOP250数据
2021/05/23 Python
Redis命令处理过程源码解析
2022/02/12 Redis
使用CSS连接数据库的方式
2022/02/28 HTML / CSS
如何vue使用el-table遍历循环表头和表体数据
2022/04/26 Vue.js