一.vue双向绑定原理
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。
js实现简单的双向绑定:
<html>
<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>
</html>
二.Virtual DOM
若想了解它是如何工作的,就要先认清这几个概念:
1.更新DOM是非常昂贵的操作
当我们使用Javascript来修改我们的页面,浏览器已经做了一些工作,以找到DOM节点进行更改,例如:
document.getElementById('myId').appendChild(myNewNode);
在现代的应用中,会有成千上万数量个DOM节点。所以因更新的时候产生的计算非常昂贵。琐碎且频繁的更新会使页面缓慢,同时这也是不可避免的。
2.我们可以用JavaScript对象来代替DOM节点
DOM节点在HTML文档中的表现通常是这样的:
<ul id='myId'>
<li>Item 1</li>
<li>Item 2</li>
<ul>
DOM节点也可以表示为一个JavaScript对象,就像这样:
//用Javascript代码表示DOM节点的伪代码
Let domNode = {
tag: 'ul'
attributes: { id: 'myId' }
children: [
//这里是 li
]
};
这就是虚拟的DOM节点,很好理解吧。
3.更新虚拟节点, 并不昂贵
//更新虚拟DOM的代码
domNode.children.push('<ul>Item 3</ul>');
如果我们用一个虚拟DOM,而不是直接调用像.getElementById的方法,这样只操作JavaScript对象,这样是相当便宜的。
然后,再把更改的部分更新到真正的DOM,方法如下:
//这个方法是调用DOM API来更改真正DOM的
//它会分批执行从而获取更高的效率
sync(originalDomNode, domNode);
4.Vue
实现响应式并不是数据发生变化之后 DOM
立即变化,而是按一定的策略进行 DOM
的更新。
这是什么意思呢?也就是说并不修改数据之后vue会立刻更新DOM,vue是异步更新DOM的,这里就需要了解js的事件循环机制,我在前面已经介绍过,如果不了解,可以去看看。vue是使用promise来更新DOM的。这也就是说我们在改变数据后并不能立刻获取到更新后的DOM。
//改变数据
vm.message = 'changed'
//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新
console.log(vm.$el.textContent) // 并不会得到'changed'
//这样可以,nextTick里面的代码会在DOM更新后执行
Vue.nextTick(function(){
console.log(vm.$el.textContent) //可以得到'changed'
})
这里我们需要使用nextTick来获取到更新后节点,或者使用setTimeout(fn,0)也可以。