来来来,走进双向绑定的大门
之前了解过一点点angularjs的双向绑定,采用的是脏检查。而我们的vue采用的是数据挟持的方式。
这些双向绑定的方式究竟有什么不同呢?
双向绑定就是说:就是在数据和UI之间建立双向的通信通道,当用户通过Function改变了数据,那么这个改变也会立即反应到UI上;或者说用户通过UI的操作,那么这些操作也会随之引起对应的数据变动。
简介
双向绑定一般有三种方法:
Knockout / Backbone — 发布-订阅模式
Angular — ‘脏检查’
Vue — ‘Object.defineProperty’
Vue新 — es6 proxy
vue实现方式
Object.defineProperty 仅仅是实现了对数据的监控,后续实现对UI的重新渲染并不是它做的,所以这里还涉及到 发布-订阅模式。
过程是,当监控的数据对象被更改后,这个变更会被广播给所有订阅该数据的watcher,然后由该 watcher实现对页面的重新渲染。
- 首先,Vue的Compile模块会对Vue的 template 代码进行编译解析并生成一系列的watcher,也可以称之为“更新函数”,它负责把变更后的相关数据重新渲染到指定的地方。
<input v-model="message">
Compile会解析出 v-model 这个指令并且生成 watcher 并连接数据中的 message 和当前这个Dom对象,一旦收到这个message被变更的通知,watcher就会根据变更对这个Dom进行重新渲染。
observer模块通过object.defineProperty对数据进行监控。
当然一个页面或者一个项目中肯定有很多watcher,因此Vue使用了Dep这个对象来存储每一个watcher,当数据发生变更,Observer会调用Dep的notify方法以通知所有订阅了该数据的watcher
![](/images/db_01.png)
js实现简单的双向绑定1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<body>
<div id="app">
<input type="text" id="txt">
<p id="show"></p>
</div>
</body>
<script type="text/javascript">
var obj = {}
Object.defineProperty(obj, 'txt', {
get: function () {
return obj
},
set: function (newValue) {
document.getElementById('txt').value = newValue
document.getElementById('show').innerHTML = newValue
}
})
document.addEventListener('keyup', function (e) {
obj.txt = e.target.value
})
</script>
下面进行实现完整过程:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Object.defineProperty实现双向绑定</title>
</head>
<body>
<h1 id='h1'></h1>
<input type="text" id="inp" onkeyup="inputChange(event)">
<input type="button" value="加" onclick="btnAdd()" />
</body>
<script src="index.js"></script>
</html>
1 | //实现这个index.js |
object.defineProperty
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
- Object.defineProperty()的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
- Object.defineProperty(obj, prop, desc), prop属性名,desc 属性描述符。
javacript 有三种类型的属性:
- 命名数据属性:拥有一个确定的值的属性。这也是最常见的属性
- 命名访问器属性:通过getter和setter进行读取和赋值的属性
- 内部属性:由JavaScript引擎内部使用的属性,不能通过JavaScript代码直接访问到,不过可以通过一些方法间接的读取和设置。比如,每个对象都有一个内部属性[[Prototype]],你不能直接访问这个属性,但可以通过Object.getPrototypeOf()方法间接的读取到它的值。虽然内部属性通常用一个双吕括号包围的名称来表示,但实际上这并不是它们的名字,它们是一种抽象操作,是不可0见的,根本没有上面两种属性有的那种字符串类型的属性
描述符有:
数据描述符:value, writable
存取描述符:get(一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。)
set(一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined。)
都有这两个描述符:configrable 描述属性是否配置,以及可否删除;enumerable 描述属性是否会出现在for in 或者 Object.keys()的遍历中
(当configrable为true,而writable为false, 可以通过配置方式修改属性值)
1 |
|
es6 proxy
3.0 将带来一个基于 Proxy 的 observer 实现,它可以提供覆盖语言 (JavaScript——译注) 全范围的响应式能力,消除了当前 Vue 2 系列中基于 Object.defineProperty 所存在的一些局限,如:
对属性的添加、删除动作的监测
对数组基于下标的修改、对于 .length 修改的监测
对 Map、Set、WeakMap 和 WeakSet 的支持
还有以下特性:https://juejin.im/post/5bb719b9f265da0ab915dbdd
- 公开的用于创建 observable (即响应式对象——译注) 的 API。就像new Vue({data: {…}})。
- 默认是惰性检测。2.x版本启动时必然被监测。新版本只有应用的初始可见部分所用的数据会被检测。
- 更精准的变动通知:在 2.x 系列中,通过 Vue.set 强制添加一个新的属性,将导致所有依赖于这个对象的 watch 函数都会被执行一次;而在 3.x 中,只有依赖于这个具体属性的 watch 函数会被通知到。
- 不可变监测对象(Immutable observable),可以创建一个对象的“不可变”版本
- 更良好的可调试能力。
proxy与设计模式
Proxy实现前端中3种代理模式的使用场景,分别是:缓存代理、验证代理、实现私有属性。
脏检查
以典型的mvvm框架angularjs为代表,angular通过检查脏数据来进行UI层的操作更新。
关于angular的脏检测,有几点需要了解些:
- 脏检测机制并不是使用定时检测。 只有当UI事件、ajax请求或者timeout延迟事件,才会触发脏检查。
- 脏检测的时机是在数据发生变化时进行。
- angular对常用的dom事件,xhr事件等做了封装,在里面触发进入angular的digest流程。
- 在digest流程里面,会从rootscope开始遍历,检查所有的watcher。
Angular每一个绑定到UI的数据,就会有一个$watch对象。1
2
3
4
5
6
7
8
9
10
11watch = {
name:'', //当前的watch对象观测的数据名
getNewValue:function($scope){ //得到新值
...
return newValue;
},
listener:function(newValue,oldValue){ //当数据发生改变时需要执行的操作
...
}
}
getNewValue() 可以得到当前$scope上的最新值,listener函数得到新值和旧值并进行一些操作。
一次脏检查就是调用一次$apply()或者$digest(),将数据中最新的值呈现在界面上。