1. 原理
1.1 vue双向数据绑定原理,又称vue响应式原理,是vue的核心,双向数据绑定是通过数据劫持结合发布订阅模式的方式来实现的,也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变
1.2 vue双向数据绑定原理的实现,其核心是Object.defineProperty()方法
1.3 Object.defineProperty(obj, prop, descriptor)方法,接收三个参数,分别为obj(定义其上属性的对象)prop(定义或修改的属性)descriptor(具体的改变方法),就是用这个方法来定义一个值,当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法
let obj = {}
Object.defineProperty(obj, 'prop', {
get(){
console.log('调用get方法')
}
set(newValue){
console.log('调用set方法,方法的值是' + newValue)
}
})
obj.prop //调用get方法
obj.prop = 'p' //调用set方法,方法的值是p
2. 简单实现js的双向数据绑定
<!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>Document</title>
</head>
<body>
<div id="app">
<input type="text" id="a">
<span id="b"></span>
</div>
</body>
<script>
var obj = {};
var value = 'p'
Object.defineProperty(obj, 'value', {
get: function () {
return value
},
set: function (newValue) {
value = newValue
document.getElementById('a').value = value
document.getElementById('b').innerHTML = value
}
})
document.addEventListener('keyup', function (e) {
obj.value = e.target.value
})
</script>
</html>
上面代码说明,随着文本框输入文字的变化,span中会同步显示相同的文字内容,从而实现了model到view以及view到model的双向绑定,然后我们通过添加事件监听keyup来触发set方法,而set再修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本
3. 真正的双向数据绑定原理
实现vue的双向数据绑定,是采用数据劫持结合发布者和订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调,getter函数里面执行的任务是watcher订阅者, 而setter函数执行的任务是发布者,双向数据绑定原理的实现主要就是如下几步
1)实现一个数据监听器Observer,能够对数据对象的所有属性进行监听,当对象的属性有变化时可拿到最新值并通知订阅者
2)实现一个指令解析器Compile,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3)实现一个Watcher,作为连接Observer和Compile的中间桥梁,能够订阅并收到每个数据对象属性变动的通知,执行指令绑定的相应回调函数,从而达到更新视图的目的
4)mvvm入口函数,整合以上三者
es中有两种属性,数据属性和访问器属性,数据属性一般用于存储数据的数值,访问器属性对应的是set/get操作,不能直接存储数据值,每种属性又都含有如下四个特性
数据属性
1)Configurable:表示能否通过delete将此属性删除,能否把属性修改为访问器属性,默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置为true
2)Enumerable:表示属性可否被枚举,即是否可以通过for in循环返回,默认false
3)Writable:表示是否可以修改属性的值,默认false
4)Value:该属性的数据值, 默认是undefined
访问器属性
1)Configurable:表示能否通过delete将此属性删除,能否把属性修改为数据属性,默认为false。当把属性Configurable设置为false后,该属性不能通过delete删除,并且也无法再将该属性的Configurable设置为true
2)Enumerable:表示属性可否被枚举,即是否可以通过for in循环返回,默认false
3)Get:读取属性时调用的函数, 默认为undefined
4)Set:写入属性时调用的函数, 默认是undefined
实现数据双向绑定的核心就是利用为每一个属性都创建了订阅者的实例对象, 以便观察, getter函数里面返回一个value值,在setter函数中写入修改后的值并调用update方法更新视图的数据值,其核心代码如下
<input type="text" id="inp" />
<div id="box"></div>
<script>
let obj = {}
let oInp = document.getElementById('inp')
let oBox = document.getElementById('box')
Object.defineProperty(obj, 'name', {
configurable: true,
enumerable: true,
get: function() {
console.log('调用了get方法')
return val
},
set: function(newVal) {
console.log('调用了set方法')
oInp.value = newVal
oBox.innerHTML = newVal
}
})
oInp.addEventListener('input', function(e) {
obj.name = e.target.value
})
obj.name = '你好vue'
function defineReactive (obj, key, val) {
var dep = new Dep() //构造函数 其原型是为属性添加订阅者
Object.defineProperty(obj, key, {
get: function() {
if(Dep.target) {
dep.addSub(Dep.target) //添加订阅者到Dep实例对象
}
return val // 返回监听到的value值
},
set: function (newVal) {
if(newVal === val) return
val = newVal // 写入新的value值
dep.notify() // 作为发布者发出通知,dep会迭代调用各自的update方法更新视图
}
})
}
function observe(obj, vm) {
Object.keys(obj).forEach(function(key) {
defineReactive(vm, key, obj[key])
})
}
</script>