1. Vue 2的响应式原理
Vue.js 一个核心思想是数据驱动。所谓数据驱动是指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据。vue.js里面只需要改变数据,Vue.js通过Directives指令去对DOM做封装,当数据发生变化,会通知指令去修改对应的DOM,数据驱动DOM的变化,DOM是数据的一种自然的映射。vue.js还会对View操作做一些监听(DOM Listener),当我们修改视图的时候,vue.js监听到这些变化,从而改变数据。这样就形成了数据的双向绑定。
Vue 2官方文档中这样解释其响应式原理:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
2. Object.defineProperty
这里需要说明的是,在Vue 2中Vue基于Object.defineProperty
来实现Vue的响应时更新,在Vue 3中,Vue是通过Proxy
代理一个对象来实现Vue的响应式更新的。
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
var vm = new Vue({
data:{
a:1
}
})
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的
我们在初始化Vue实例的时候,会遍历Vue的data,并递归式的为每一个data数据执行Object.defineProperty
使其拥有响应性,但是,如果我们为vm添加一个属性(如上文中的vm.b),这是就需要主动调用Object.defineProperty
来使该属性具有响应性。
3. 手写实现Vue2 响应式原理
3.1 使用Object.defineProperty实现简单的双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>手写实现Vue的响应式更新</title>
</head>
<body>
<div id="app">
<input id="input" type="text">
<span id="content"></span>
</div>
</body>
<script type="text/javascript">
let data = {
}
Object.defineProperty(data, 'text', {
set: function (v) {
this.value = v //当我们为data.text赋值时,实际上更改的是this.value,
//这里的this.value是我们添加的一个变量,用于保存data.text的内容,
//也可以是任意的其他变量,比如this.a = v,效果也是一样的。
console.log('set', this.value)
document.getElementById('content').innerText = this.value
},
get: function () {
console.log('get', this.value)
return this.value //当我们获取data.text时,其实也是获取到this.value的值
}
})
//所以,如果我们通过相同的方法为data再增加一个name属性,执行data.name=3,修改的也是this.value的值
//所以这时候我们获取data.text的值时,得到的也是3。如果希望data的属性有独立的值,我们可以使用闭包,如下:
// !function (obj, property, value) {
// Object.defineProperty(obj, property, {
// set: function (v) {
// value = v //当我们为data.text赋值时,实际上更改的是this.value,
// //这里的this.value是我们添加的一个变量,用于保存data.text的内容,
// //也可以是任意的其他变量,比如this.a = v,效果也是一样的。
// console.log('set', value)
// document.getElementById('content').innerText = value
// },
// get: function () {
// console.log('get', value)
// return value //当我们获取data.text时,其实也是获取到this.value的值
// }
// })
// } (data, 'text')
document.getElementById('input').addEventListener('input', function (e) {
data.text = e.target.value
})
</script>
</html>
控制台输出:
上面操作直接使用了DOM操作改变了文本节点的值,而且是在知道id的情况下,使用document.getELementById()获取到响应的文本节点,然后修改文本节点的值。
封装成一个框架,肯定不能使用这种做法。所以需要一个可以解析DOM并且能修改DOM中相应变量的模块。
3.2 实现简单Compiler
- 首先,获取文本中真实的DOM节点
- 然后,分析节点的类型
- 最后