先说面试答案:
vue.js是采用
数据劫持
结合发布者-订阅者模式
的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。
具体步骤
-
需要
observer
的数据对象进行递归遍历
,包括子属性对象的属性,都加上setter
和getter
这样的话,给这个对象的某个值赋值
,就会触发setter
,那么就能监听到了数据变化 -
compile
解析模板指令,将模板中的变量替换成数据
,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数
,添加监听数据的订阅者
,一旦数据有变动,收到通知,更新视图 -
Watcher
订阅者是Observer
和Compile
之间通信的桥梁,主要做的事情是:
1)、在自身实例化时往属性订阅器(dep
)里面添加自己
2)、自身必须有一个update()
方法
3)、待属性变动dep.notice()
通知时,能调用自身的update()
方法,并触发Compile
中绑定的回调 -
MVVM
作为数据绑定的入口,整合Observer、Compile和Watcher三者
,通过Observer
来监听自己的model数据变化
,通过Compile
来解析编译模板指令,最终利用Watcher
搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。
什么是Object.defineProperty() ?
语法:
Object.defineProperty(obj, prop, descriptor)
参数:
obj:
必需。目标对象
prop:
必需。需定义或修改的属性的名字
descriptor:
必需。目标属性所拥有的特性
返回值:
传入函数的对象,即第一个参数obj
下面让我们来看代码演示:
简单的实现一个js的双向数据绑定来熟悉一下Object.defineProperty()
方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="text" id="in">
<span id="out"></span>
</div>
<script>
var input = document.getElementById('in')
var out = document.getElementById('out')
var obj = {}
Object.defineProperty(obj, 'msg', {
// writable: true,//是否可以被修改
enumerable: true,//是否可以被枚举也就是是否可以遍历
configurable: true,//是否可以被删除
set(newVal) {
out.innerText = newVal
}
})
input.addEventListener('input', function (e) {
obj.msg = e.target.value
})
</script>
</body>
</html>
这样我们就能实现js的双向数据绑定,随着文本框输入文字的变化,span中会同步显示相同的文字内容;这样就实现了 model => view
以及 view => model
的双向绑定。
通过给输入框添加事件监听input来触发obj对象的set方法
,而set再修改了访问器属性的同时,也修改了dom样式,改变了span标签内的文本。