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 相关文章推荐
javascript form 验证函数 弹出对话框形式
Jun 23 Javascript
js获取url参数值的两种方式
Sep 10 Javascript
使用ajax+jqtransform实现动态加载select
Dec 01 Javascript
基于JQuery制作可编辑的表格特效
Dec 23 Javascript
浅析AngularJs HTTP响应拦截器
Dec 28 Javascript
JavaScript实现时间倒计时跳转(推荐)
Jun 28 Javascript
Javascript vue.js表格分页,ajax异步加载数据
Oct 24 Javascript
xcode中获取js文件的路径方法(推荐)
Nov 05 Javascript
深入理解Vue transition源码分析
Jul 30 Javascript
vue-router动态设置页面title的实例讲解
Aug 30 Javascript
js实现多个倒计时并行 js拼团倒计时
Feb 25 Javascript
Vue使用mixin分发组件的可复用功能
Sep 01 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
phpMyAdmin 安装及问题总结
2009/05/28 PHP
PHP开发微信支付的代码分享
2014/05/25 PHP
配置Nginx+PHP的正确思路与过程
2016/05/10 PHP
PHP7扩展开发教程之Hello World实现方法示例
2017/08/03 PHP
php常用字符串查找函数strstr()与strpos()实例分析
2019/06/21 PHP
Wordpress ThickBox 添加“查看原图”效果代码
2010/12/11 Javascript
通过JS获取用户本地图片路径并显示的代码
2012/02/16 Javascript
Javascript绝句欣赏 一些经典的js代码
2012/02/22 Javascript
JavaScript中的关键字&quot;VAR&quot;使用详解 分享
2013/07/31 Javascript
javascript:文字不间断向左移动的实例代码
2013/08/08 Javascript
jQuery实现点击图片翻页展示效果的方法
2015/02/16 Javascript
JavaScript使用指针操作实现约瑟夫问题实例
2015/04/07 Javascript
jQuery延迟执行的实现方法
2016/12/21 Javascript
微信小程序  http请求封装详解及实例代码
2017/02/15 Javascript
微信小程序自定义toast实现方法详解【附demo源码下载】
2017/11/28 Javascript
vue 动态修改a标签的样式的方法
2018/01/18 Javascript
解决Layui中templet中a的onclick参数传递的问题
2019/09/20 Javascript
Vue实现Layui的集成方法步骤
2020/04/10 Javascript
vue实现淘宝购物车功能
2020/04/20 Javascript
使用JavaScript获取Django模板指定键值数据
2020/05/27 Javascript
vue+element使用动态加载路由方式实现三级菜单页面显示的操作
2020/08/04 Javascript
[01:05]DOTA2完美大师赛趣味视频之选手教你打职业
2017/11/23 DOTA
[00:17]天涯墨客一技能展示
2018/08/25 DOTA
Python实现的简单文件传输服务器和客户端
2015/04/08 Python
Python中支持向量机SVM的使用方法详解
2017/12/26 Python
Python反射的用法实例分析
2018/02/11 Python
Python向MySQL批量插数据的实例讲解
2018/03/31 Python
python 猴子补丁(monkey patch)
2019/06/26 Python
对Python中一维向量和一维向量转置相乘的方法详解
2019/08/26 Python
详解Python 中sys.stdin.readline()的用法
2019/09/12 Python
Python3 + Appium + 安卓模拟器实现APP自动化测试并生成测试报告
2021/01/27 Python
WEB控件及HTML服务端控件能否调用客户端方法?如果能,请解释如何调用?
2015/08/25 面试题
建设幸福中国演讲稿
2014/09/11 职场文书
违章停车检讨书
2014/10/21 职场文书
同事去世追悼词
2015/06/23 职场文书
天谕手游15杯全调酒配方和调酒券的获得方式
2022/04/06 其他游戏