响应式原理:
vue的响应式实现主要是利用了Object.defineProperty的方法里面的setter 与getter方法的观察者模式来实现。
在组件初始化时会给每一个data属性注册getter和setter,然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。
在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里。当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件。
先来看看 Vue2 中 defineProperty 来操作数据:
const obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
function _isObject(v) {
return typeof v === 'object' && v !== null;
}
function observe(object) {
for (let key in object) {
let v = object[key];
if (_isObject(v)) {
observe(v)
}
Object.defineProperty(object, key, {
get() {
console.log('read ' + key);
return v;
},
set(val) {
if (val !== v) {
console.log('change ' + key);
v = val;
}
}
})
}
}
observe(obj)
// obj.a = 3;
obj.c.a = 4;
所以 Vue2 的缺陷是无法监听到属性的增加和删除,因为只有 getter 和 setter 函数。此外,还通过深度遍历,有效率的损失,将属性变成 getter 和 setter 函数。
Vue2 缺陷总结:
1、无法监听es6的 Set、Map 变化;
2、无法监听 Class 类型的数据;
3、属性的新增或者删除也无法监听;
4、数组元素的增加和删除也无法监听。
而 Vue3 中Proxy 直接监听整个对象的变化:
const obj = {
a: 1,
b: 2,
c: {
a: 1,
b: 2
}
}
function _isObject(v) {
return typeof v === 'object' && v !== null;
}
function observe(obj) {
const proxy = new Proxy(obj, {
get(target, k) {
let v = target[k]
if (_isObject(v)) {
v = observe(v);
}
console.log('read', k)
return v;
},
set(target, k, val) {
if (target[k] !== val) {
target[k] = val;
console.log('change', k)
}
}
// ....
})
return proxy;
}
proxy.a = 3;
proxy.aa;
不监听属性,而是监听整个代理对象。只有当读取到对象属性的时候才会进行遍历监听。
总结:
- proxy的优势如下:
- Proxy 可以直接监听对象而非属性,可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等。是Object.defineProperty 不具备的;
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
- Object.defineProperty 的优势如下:
- 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill(垫片)来弥补