响应式原理
Vue 的响应式原理是基于数据劫持 & 发布-订阅模式而实现的。
数据劫持
数据劫持是通过 ES5 的 Object.defineProperty() 方法来实现的
语法:Object.defineProperty( obj, prop, descriptor )
参数:
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称
- descriptor:要定义或修改的属性描述符
详见:MDN
// 这是将要被劫持的对象
const data = {
name: '',
};
function say(name) {
if (name === '古天乐') {
console.log('给大家推荐一款超好玩的游戏');
} else if (name === '渣渣辉') {
console.log('戏我演过很多,可游戏我只玩贪玩懒月');
} else {
console.log('来做我的兄弟');
}
}
// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
console.log('get');
},
set: function(newVal) {
// 当属性值发生变化时我们可以进行额外操作
console.log(`大家好,我系${newVal}`);
say(newVal);
},
});
});
data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月
可以看到,当设置 data 对象的 name 属性时,会触发其上的 setter 。我们可以在 setter 上做一些额外操作,这就是数据劫持。
Dep
用于收集 Watcher 依赖。
每个 data 上的属性都拥有一个 Dep 实例,当数据有变更时,Dep 会通知各个 watcher
Watcher
观察者对象。
实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种
当创建 Vue 实例时,Vue 会遍历 data 对象上的属性,利用 Object.defineProperty() 方法为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。
每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。
一句话总结
Vue.js 采用数据劫持 & 发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter 和 getter,在数据变动时发布消息给订阅者,并触发相应的监听回调