1.基本原理
数据在变化时不需要开发者去手动更新视图,视图会根据变化的数据自动进行更新。
想完成这个过程,需要做到:知道收集视图依赖了哪些数据、感知被依赖数据的变化、数据变化时自动通知需要更新的视图部分并进行更新。对应的技术为:依赖收集、数据劫持/数据代理、发布/订阅模式。
可以使用Object.defineProperty和Proxy实现拦截,对比之下:
- Object.defineProperty不能监听数据的变化,需要对数组方法进行重写。
- Object.defineProperty必须遍历对象的每个属性,且需要对嵌套结构进行深层遍历。
- Proxy的代理是针对整个对象的,只需要做一层代理就可以监听同级结构下的所有属性变化,当然对于深层结构,递归还是需要进行的。
- Proxy支持代理数组的变化。
- Proxy的第二个参数除了可以使用set和get,还可以使用13种拦截方法。
- 使用Proxy时,性能将会被底层持续优化。
2.模板编译原理
vue中模板变量是通过{{}}表达式输出的,最终的HTML内容应该被合适的数据进行填充替换,因此需要进行编译;比如react中JSX会被编译为React.createElement,并在生成虚拟DOM时进行数据填充。
模板编译是使用正则+遍历的方式,以及一些算法,对#app节点下的内容进行替换,通过正则表达式识别出模板变量,获取对应的数据。双向绑定则是在编译时添加组件的事件监听,并更改数据源。
3.发布订阅模式
js本身就是事件驱动型语言,比如,应用中对一个button进行了事件绑定,用户点击之后就会触发按钮对应的click事件,这是因为此时有特定程序正在监听这个事件,随之触发了相关的处理程序。
这个模式一个好处在于能够对代码进行解耦,实现“高内聚,低耦合”的理念,同样适用于框架设计。
class Notify {
constructor () {
this.subscribers = []
}
add (handler) {
this.subscribers.push(handler)
}
emit () {
this.subscribers.forEach(subscriber => subscriber())
}
}
let notify = new Notify()
notify.add(() => {
console.log('emit')
})
notify.emit()
4.MVVM
首先对数据进行深度拦截或代理,对每一个属性的getter和setter进行加工。我们需要在模板初次编译时,解析指令(如v-model)并进行依赖收集,订阅数据的变化。
依赖收集的具体过程:当调用替换方法时,读取数据进行模板变量的替换,读取数据时需要做一个标记,用来表示我依赖这一项数据,并订阅这个属性值的变化。比如,vue中通过定义一个Watcher类来表示观察订阅依赖的,模板编译过程会读取数据,进而触发数据源属性值的getter,数据代理的加工就是在数据监听的getter中记录这个依赖,同时在setter触发数据变化时执行依赖对应的相关操作,最终触发模板中的数据变化的。
5.虚拟DOM
用数据结构表示DOM结构,并没有真实挂载到DOM上。好处是操作数据结构远比通过和浏览器交互去操作DOM快,每次通过DOM diff计算出视图前后更新的最小差异,再把最小差异应用到真实DOM上,性能更有保障。