几种数据绑定实现方式
发布者-订阅者模式(backbone.js)
脏检查(angular.js)
数据劫持(vue.js)
本文主要讲述vue双向数据绑定原理分析:
vue.js采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Object.defineProperty 方法提供了一种直接的方式来定义对象属性或者修改已有对象属性。其方法原型如下,Object.defineProperty(obj, prop, descriptor)
- obj :待修改的对象
- prop :带修改的属性名称
- descriptor :待修改属性的相关描述
<script>
var obj = {}
var returnVal = ""
// 利用defineProperty去劫持更新
// 第三个参数 数据描述 存取器的描述
Object.defineProperty(obj, "age", {
get:function () {
return returnVal
},
set:function (newValue) {
returnVal = newValue
}
})
</script>
对象obj获取属性key的值时,会触发上面的get方法,得到的是变量returnVal的值,然后当重新设置key的值时,触发set方法,会将变量returnVal的值改变为设置的值,如此就实现了一个简单的双向绑定:改变returnVal,obj.age得到的值也会改变,重新设置obj.age,returnVal一样会随之改变。
例子
实现随文本框输入文字的变化,txtspan 中会同步显示相同的文字内容;在js或控制台显式的修改 obj 中 name 的值,视图会相应更新。这样就实现了 model => view 以及 view => model 的双向绑定。
<script type="text/javascript" src="MyVue.js"></script>
<body>
<div id="app">
<input type="text" id="txt"></input>
输入:<span id="txtspan"></span>
</div>
</body>
当我们在输入框输入数据的时候,触发 keyup 事件,获取键盘的value 并赋值给 vm 实例的 name 属性。
var txt = document.getElementById("txt")
var txtspan = document.getElementById("txtspan")
var vm = new MyVue({
el: "#app",
data: {
count: 0,
name: "Cassie",
age: 18
}
})
txt.addEventListener("keyup", function (e) {
vm._data.name = e.target.value
})
// 接受set的值
function callBack (val) {
txtspan.innerHTML = val
}
MyVue.js
将需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter和getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
- Observer:监听数据和数据变化通知订阅者的功能
- defineReactive:利用了Object.defineProperty()这个方法来劫持了vm实例对象的属性的读写权,使读写vm实例的属性转成读写了vm._data的属性值
// 数据监听
function Observer (value) {
if (!value || (typeof value !== 'Object')) {
return
}
// 获取data所有属性
Object.keys(value).forEach((key) => {
// 拦截
defineReactive(value, key, value[key])
})
}
// 拦截数据
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
get: function () {
return val
},
set: function (newValue) {
if (newValue === val) {
return
}
val = newValue
callBack(val)
}
})
}
function MyVue (options) {
this._data = options.data
Observer(this._data)
}