Vue数据双向绑定的深入探究


Posted in Javascript onNovember 27, 2018

前言

使用过vue的小伙伴都会感觉,哇,这个框架对开发者这么友好,简直都要笑出声了。

确实,使用过vue的框架做开发的人都会感觉到,以前写一大堆操作dom,bom的东西,现在用不着了,对开发者来说更容易去注重对操作逻辑的思考和实现,省了不少事儿呢!!!

我是直接从原生js,jq的开发用过度到使用vue,对这个框架也是喜爱有加,闲来无事,去看了看它的一些实现原理。

vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。这也算是vue的精髓之处了。值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定。 单向数据绑定是使用状态管理工具(如redux)的前提。

下面来介绍一下vue的一个非常"牛逼"的功能,数据双向绑定,也就是我们在项目里用到的v-model指令。

v-model在vue官方文档上是介绍在"表单输入绑定"那一节。

对于表单,大家肯定用得都已经超级熟练了,对于<input>、<textarea>和<select>标签在项目里面使用都已经没话说了

官方提到的v-model是一个语法糖,为什么这么说呢?下面看个例子:

<div id="test1">
<input v-model="input">
<span>input: {{ input }}</span>
</div>

如上,是一个简单的使用v-model的双向绑定,我们在改变input这个变量的值,即在输入框中去写内容的时候,在span标签内的插值(mustache)会同步更新我们刚刚输入的值

其实上面的也可以这样写:

<div id="test1">
<input v-on:input="input = $event.target.value" v-bind:value='input'>
<span>input: {{ input }}</span>
</div>

好了,前面????掳胩欤?衷诮?胝??br />

想对比react和angular的双向绑定实现,我也不清楚,哈哈哈,直接说vue吧,不扯了

Reactivity 响应式系统

拿尤雨溪大佬做vue测试的的那个例子来说吧(购物车的例子)

<div id='app'>
<div>
<span>价格:</span>
<input v-model.number="price">
</div>
<div>
<span>数量:</span>
<input v-model.number="quantity">
</div>
<p>价格:{{ price }}</p>
<p>数量:{{ quantity }}</p>
<p>总计:{{ total }}</p>
</div>
data() {
  return {
  price: 5,
  quantity: 3
  }
 },
 computed: {
  total() {
  return this.price * this.quantity;
  }
 }

当我们在使用输入框的值的时候,下面的total会更新,我们对应输入值的变量也会更新

哇,好神奇,为什么呢,这不是JavaScript编程常规的工作方式!!!

因为我们用原生js写的时候是这样的:

let price = 5;
let quantity = 3;
let total = price * quantity; // 等于15吧

price = 10; // 改变价格;
console.log(total); // bingo,打印的还是15

我们需要在找一种办法,把要运行计算的total放到别的时候去运行,当我们的价格、数量变化的时候执行

let price = 5;
let quantity = 3;
let total = 0;
let storage = []; // 储存将要计算的操作,等到变量变化的时候去执行

let target= () => { total = price * quantity;}
function record () {
 storage.push(target);
}
function replay() {
 storage.forEach(run => run());
}

record();
target();

price = 10;
console.log(total); // 依然是15
replay();
console.log(total); // 执行结果是30

目的达到,但是这样肯定不是vue用来扩展使用的方式,我们用ES6的class类来做一个可维护的扩展,实现一个标准的观察者模式的依赖类

class Depend {
 constructor () {
 this.subscribers = [];
 }
 depend() {
 if(target && this,this.subscribers.includes(target)) {
  this.subscribers.push(target);
 }
 }
 notify() {
 this.subscribers.forEach(sub => sub());
 }
}

// 来执行上面写的class
const dep = new Depend();
let price = 5;
let quantity = 3;
let total = 0;
let target = () => { total = price * quantity };

dep.depend();
target();

console.log(total); // total是15
price = 10;
console.log(total); // 因为没有执行target,依旧是15
dep.notify();
console.log(total); // 执行了存入的target,total为30

为了给每一个变量都设置一个Depend类。并且很好地控制监视更新的匿名函数的行为,我们把上面的代码做一些调整:

let target = () => { total = price * quantity };
dep.depend();
target();

修改为:

watcher(() => { total = price * quantity });

然后我们在watcher函数里面来做刚刚上面的result的设置和执行的功能

function watcher(fn) {
 target = fn;
 dep.depend();
 target();
 target = null;   // 重置一下,等待储存和执行下一次
}

这儿就是官方文档提到的订阅者模式:在每次watcher函数执行的时候,把参数fn设置成为我们全局目标属性,调用dep.depend()将目标添加为订阅者,调用然后重置

然后再继续

我们的目标是把每一个变量都设置一个Depend类,但是这儿有个问题:

先存一下数据:

let data = { price: 5, quantity: 3}

假设我们每个属性都有自己的内部Depend类

Vue数据双向绑定的深入探究

当我们运行代码时:

watcher(() => { total = data.price * data.quantity})

由于访问了data.price值,希望price属性的Depend类将我们的匿名函数(存储在目标中)推送到其订阅者数组(通过调用dep.depend())。由于访问了data.quantity,还希望quantity属性Depend类将此匿名函数(存储在目标中)推送到其订阅者数组中。

Vue数据双向绑定的深入探究

如果有另一个匿名函数,只访问data.price,希望只推送到价格属性Depend类。

Vue数据双向绑定的深入探究

什么时候想要在价格订阅者上调用dep.notify()?我希望在设定价格时调用它们。为此,我们需要一些方法来挂钩数据属性(价格或数量),所以当它被访问时我们可以将目标保存到我们的订阅者数组中,当它被更改时,运行存储在我们的订阅者数组中的函数。let's go

Object.defineProperty来解决这个问题

Object.defineProperty函数是简单的ES5 JavaScript。它允许我们为属性定义getter和setter函数。继续啃

let data = { price: 5, quantity: 3};
let value = data.price
Object.defineProperty(data, 'price', {
 getter() {
 console.log(`获取price的值: ${value}`);
 return value;
 },
 setter(newValue) {
 console.log(`更改price的值': ${newValue}`);
 value = newValue;
 }
})
total = data.price * data.quantity;
data.price = 10;  // 更改price的值

上面通过defineProperty方法给price设置了获取和修改值的操作

如何给data对象所有的都加上这个defineProperty方法去设置值

大家还记得Object.keys这个方法吗?返回对象键的数组,咱们把上面的代码改造一下

let data = { price: 5, quantity: 3 };

Object.keys(data).forEach(key => {
 let value = data[key];
 Object.defineProperty(data, key, {
 getter() {
  console.log(`获取 ${key} 的值: ${value}`);
  return value;
 },
 setter(newValue) {
  console.log(`更改 ${key} 值': ${newValue}`);
  value = newValue;
 }
 })
})
total = data.price * data.quantity;
data.price = 10; // 更改price的值

接着上面的东西,在每次运行完获取key的值,我们希望key能记住这个匿名函数(target),这样有key的值变化的时候,它将触发这个函数来重新计算,大致思路是这样的:

  • getter函数执行的时候,记住这个匿名函数,当值在发生变化的时候再次运行它
  • setter函数执行的时候,运行保存的匿名函数,把当前的值存起来

用上面定义的Depend类来说就是:

  • getter执行,调用dep.depend()来保存当前的target
  • setter执行,在key上调用dep.notify(),重新运行所有的target

来来来,把上面的东西结合到一起来

let data = { price: 5, quantity: 3 };
let total = 0;
let target = null;

class Depend {
 constructor() {
 this.subscribers = [];
 }
 depend() {
 if (target && this.subscribers.includes(target)) {
  this.subscribers.push(target);
 }
 }
 notify() {
 this.subscribers.forEach(sub => sub());
 }
}

Object.keys(data).forEach(key => {
 let value = data[key];
 const dep = new Depend();

 Object.defineProperty(data, key, {
 getter() {
  dep.depend();
  return value;
 },
 setter(newValue) {
  value = newValue;
  dep.notify();
 }
 })
});

function watcher(fn) {
 target = fn;
 target();
 target = null;
}

watcher(() => {
 total = data.price * data.quantity;
});

至此,vue的数据双向绑定已经实现,当我们去改变price和quantity的值,total会实时更改

然后咱们来看看vue的文档里面提到的这个插图:

Vue数据双向绑定的深入探究

是不是感觉这个图很熟悉了?对比咱们上面研究的流程,这个图的data和watcher就很清晰了,大致思路如此,可能vue的内部实现和封装远比我这个研究流程内容大得多、复杂得多,不过有了这样的一个流程思路,再去看vue双向绑定源码估计也能看懂个十之八九了。

听说vue3.0准备把这个数据劫持的操作用ES6提供的proxy来做,效率更高,期待!!!!

参考和学习原文(可能需要翻墙,毕竟是外站啊)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
用javascript获取textarea中的光标位置
May 06 Javascript
jQueryUI的Dialog的简单封装
Jun 07 Javascript
jquery中对表单的基本操作代码
Jul 29 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
Jan 29 Javascript
SwfUpload在IE10上不出现上传按钮的解决方法
Jun 25 Javascript
jQuery实现渐变下拉菜单的简单方法
Mar 11 Javascript
Javascript随机标签云代码实例
Jun 21 Javascript
vuex的简单使用教程
Feb 02 Javascript
SVG实现时钟效果
Jul 17 Javascript
vuejs实现ready函数加载完之后执行某个函数的方法
Aug 31 Javascript
vue自定义指令的创建和使用方法实例分析
Dec 04 Javascript
vue 授权获取微信openId操作
Nov 13 Javascript
微信小程序带动画弹窗组件使用方法详解
Nov 27 #Javascript
微信小程序实现日历功能
Nov 27 #Javascript
微信小程序实现打卡日历功能
Sep 21 #Javascript
微信小程序实现时间预约功能
Nov 27 #Javascript
微信小程序使用component自定义toast弹窗效果
Nov 27 #Javascript
微信小程序自定义底部导航带跳转功能
Nov 27 #Javascript
koa2使用ejs和nunjucks作为模板引擎的使用
Nov 27 #Javascript
You might like
php检测用户是否用手机(Mobile)访问网站的类
2014/01/09 PHP
php+Mysqli利用事务处理转账问题实例
2015/02/11 PHP
php用户注册信息验证正则表达式
2015/11/12 PHP
Laravel中Facade的加载过程与原理详解
2017/09/22 PHP
ThinkPHP5.0多个文件上传后找不到临时文件的修改方法
2018/07/30 PHP
检查输入的是否是数字使用keyCode配合onkeypress事件
2014/01/23 Javascript
JS实现静止元素自动移动示例
2014/04/14 Javascript
jquery图片切换插件
2015/03/16 Javascript
JavaScript判断前缀、后缀是否是空格的方法
2015/04/15 Javascript
javascript十六进制及二进制转化的方法
2015/05/06 Javascript
JavaScript过滤字符串中的中文与空格方法汇总
2016/03/07 Javascript
基于JS代码实现实时显示系统时间
2016/06/16 Javascript
jQuery电话号码验证实例
2017/01/05 Javascript
bootstrap datetimepicker日期插件使用方法
2017/01/13 Javascript
easyUI combobox实现联动效果
2017/01/17 Javascript
jquery在启动页面时,自动加载数据的实例
2018/01/22 jQuery
js自定义trim函数实现删除两端空格功能
2018/02/09 Javascript
JS中Promise函数then的奥秘探究
2018/07/30 Javascript
基于VUE实现的九宫格抽奖功能
2018/09/30 Javascript
js实现动态增加文件域表单功能
2018/10/22 Javascript
基于JS实现数字动态变化显示效果附源码
2019/07/18 Javascript
javascript设计模式 ? 模板方法模式原理与用法实例分析
2020/04/23 Javascript
python连接mongodb操作数据示例(mongodb数据库配置类)
2013/12/31 Python
详解Python中的日志模块logging
2015/06/19 Python
深入解析Python编程中JSON模块的使用
2015/10/15 Python
python pandas中对Series数据进行轴向连接的实例
2018/06/08 Python
python实现两个dict合并与计算操作示例
2019/07/01 Python
使用PyInstaller将Pygame库编写的小游戏程序打包为exe文件及出现问题解决方法
2019/09/06 Python
wxPython绘图模块wxPyPlot实现数据可视化
2019/11/19 Python
Python读取表格类型文件代码实例
2020/02/17 Python
解决keras backend 越跑越慢问题
2020/06/18 Python
大专学生推荐信范文
2013/11/19 职场文书
总经理职责
2013/12/22 职场文书
地陪导游欢迎词
2015/01/26 职场文书
2015年大学班级工作总结
2015/04/28 职场文书
详解Java实践之抽象工厂模式
2021/06/18 Java/Android