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 相关文章推荐
jquery easyui的tabs使用时的问题
Mar 23 Javascript
jQuery+css+html实现页面遮罩弹出框
Mar 21 Javascript
包含中国城市的javascript对象实例
Aug 03 Javascript
JS基于myFocus库实现各种功能的tab选项卡切换效果
Sep 19 Javascript
js遮罩效果制作弹出注册界面效果
Jan 25 Javascript
angularjs中的$eval方法详解
Apr 24 Javascript
vue组件父子间通信之综合练习(聊天室)
Nov 07 Javascript
vuejs使用axios异步访问时用get和post的实例讲解
Aug 09 Javascript
基于 jQuery 实现键盘事件监听控件
Apr 04 jQuery
基于layui的下拉列表的数据回显方法
Sep 24 Javascript
Vue 3.0双向绑定原理的实现方法
Oct 23 Javascript
vue 把二维或多维数组转一维数组
Apr 24 Vue.js
微信小程序带动画弹窗组件使用方法详解
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 时间转换Unix时间戳代码
2010/01/22 PHP
PHP 开发环境配置(测试开发环境)
2010/04/28 PHP
批量获取memcache值并按key的顺序返回的实现代码
2011/06/14 PHP
记录mysql性能查询过程的使用方法
2013/05/02 PHP
Thinkphp的volist标签嵌套循环使用教程
2014/07/08 PHP
php读取文件内容的方法汇总
2015/01/24 PHP
PHP使用strtotime计算两个给定日期之间天数的方法
2015/03/18 PHP
php编写批量生成不重复的卡号密码代码
2015/05/14 PHP
PHP可变函数学习小结
2015/11/29 PHP
超棒的javascript页面顶部卷动广告效果
2007/12/01 Javascript
基于Jquery的文字滚动跑马灯插件(一个页面多个滚动区)
2010/07/26 Javascript
jQuery编写widget的一些技巧分享
2010/10/28 Javascript
JQuery for与each性能比较分析
2013/05/14 Javascript
解析dom中的children对象数组元素firstChild,lastChild的使用
2013/07/10 Javascript
SeaJS入门教程系列之使用SeaJS(二)
2014/03/03 Javascript
javascript中Array()数组函数详解
2015/08/23 Javascript
JS实现的多张图片轮流播放幻灯片效果
2016/07/22 Javascript
vue.js组件vue-waterfall-easy实现瀑布流效果
2017/08/22 Javascript
vue toggle做一个点击切换class(实例讲解)
2018/03/13 Javascript
Vue父子组建的简单通信之控制开关Switch的实现
2018/06/04 Javascript
vue使用技巧及vue项目中遇到的问题
2018/06/04 Javascript
jQuery实现的响应鼠标移动方向插件用法示例【附源码下载】
2018/08/28 jQuery
微信小程序实现下拉菜单切换效果
2020/03/30 Javascript
Nodejs处理异常操作示例
2018/12/25 NodeJs
Ant design vue中的联动选择取消操作
2020/10/31 Javascript
Python实现转换图片背景颜色代码
2020/04/30 Python
Python turtle库的画笔控制说明
2020/06/28 Python
英国足球店:UK Soccer Shop
2017/11/19 全球购物
印尼在线购买隐形眼镜网站:Lensza.co.id
2019/04/27 全球购物
机械设计及其自动化专业推荐信
2013/10/31 职场文书
班组长安全职责
2014/01/05 职场文书
物流管理专业推荐信
2014/09/06 职场文书
家庭贫困证明范本(经典版)
2014/09/22 职场文书
2015年汽车销售经理工作总结
2015/04/27 职场文书
Html5生成验证码的示例代码
2021/05/10 Javascript
JS前端宏任务微任务及Event Loop使用详解
2022/07/23 Javascript