前言:我们从以前的操作DOM元素到现在的操作数据,从以前的MVC模式到现在的MVVM模式,以及现在深入人心的组件化开发,这个转折我觉得真的是太机智了。
现在我们平常开发主要用到的技术里,可以看到React和小程序是单向数据流,Vue和Angular是双向数据绑定,Angular用的是脏检测,Vue则是数据劫持和订阅者模式,今天我们来聊聊vue的双向数据绑定。
双向数据绑定无非就是,视图 => 数据,数据 => 视图的更新过程
借张图:
以下的方案中的实现思路:
- 定义一个Vue的构造函数并初始化这个函数
- 实现数据层的更新:数据劫持,定义一个 obverse 函数重写data的set和get
- 实现视图层的更新:订阅者模式,定义个 Watcher 函数实现对DOM的更新
- 将数据和视图层进行绑定,解析指令v-bind、v-model、v-click
- 创建Vue实例
好了下面我们要开始干正事了
这两天突然看到一个公众号文章,里面介绍了 Object.defineProperty 如何实现vue的双向数据绑定,虽然这类文章看过好多遍,但还是忍不住好奇心撮进去看了一眼(估计是因为养了猫的原因吧)
这一看不得了,还有这么简洁的实现方式
好像再涂涂改改一下我是不是能用 Proxy 来实现一遍???
好吧,说动手就动手,这一动手就是一整天,先修改了下它的代码能少多少少多少,顺便带上自己的注释,方便一会用 Proxy 方法来改造。
Object.defineProperty 实现方式的原文:面试题:你能写一个 Vue 的双向数据绑定吗?
下图就是我们实现双向数据后的页面,很简洁的三个元素
默认值:
修改了值后:
以下是我涂涂改改后用 Object.defineProperty 方式实现的代码,因为我的注释很详细,所以就不说怎么实现的了,如果你之前没有了解过这方面的知识,那么你可以先看看原文再过来看这段代码和注释
因为修改过和原文有些不同,但是思路大概一样
看完后你会发现 Object.defineProperty 只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。Vue里,是通过递归以及遍历data 对象来实现对数据的监控的,如果属性值也是对象那么需要深度遍历
<!DOCTYPE html>
<html>
<head>
<title>myVue</title>
<style>
#app{
text-align: center;
}
</style>
</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>
// 定义一个myVue构造函数
function myVue(option) {
this._init(option)
}
myVue.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 // this.$methods = {increment: function(){}}
// _binding保存着model与view的映射关系,也就是我们前面定义的Watcher的实例。当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
this._binding = {}
this._obsever(this.$data)
this._compile(this.$el)
}
// 数据劫持:更新数据
myVue.prototype._obsever = function (obj) {
let _this = this
Object.keys(obj).forEach((key) => { // 遍历obj对象
if (obj.hasOwnProperty(ke