Vue2.x响应式分析:
核心API:Object.defineProperty
响应式原理:数据改变的时候,视图会跟着更新
简单分析:
- 遍历data对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter
- 对象:递归遍历
- 数组:函数拦截,覆盖数组原型方法
缺点:
- 深度监听,需要递归到底,一次性计算量大
- 无法原生监听数组,需要特殊处理
- 无法监听新增属性和删除属性需要使用Vue.set、Vue.delete处理
代码简单演示:
// 发布订阅模式
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
// 通知更新视图
notify() {
this.subs.forEach(item => {
item.update();
});
}
}
// 观察者
class Watch {
constructor() {
Dep.target = this;
}
update() {
console.log("更新视图");
}
}
function render() {
console.log("更新视图");
}
function defineReactive(obj, key, val) {
const dep = new Dep();
observal(val);
Object.defineProperty(obj, key, {
get: function() {
console.log("get", val);
dep.addSub(Dep.target);
return val;
},
set: function(newVal) {
if (newVal != val) {
console.log("set", val);
// 新设置的值也需要深度监听
observal(val);
val = newVal;
dep.notify();
}
}
});
}
// 重写原型
const nativeArrayProperty = Array.prototype;
// 新创建一个对象且不影响原型
const arrayPro = Object.create(nativeArrayProperty);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
methodsToPatch.forEach(method => {
arrayPro[method] = function() {
nativeArrayProperty[method].call(this, ...arguments);
// 更新视图
render();
};
});
// 监听对象属性
function observal(target) {
if (typeof target != "object" || target == null) {
return target;
}
if (Array.isArray(target)) {
target.__proto__ = arrayPro;
}
for (let key in target) {
defineReactive(target, key, target[key]);
}
}
class TVue {
constructor(options) {
this.data = options.data;
observal(this.data);
new Watch();
}
}
let vue = new TVue({
data: {
leftNode: 1,
rightNode: "20",
info: {
// 深度监听
name: "luck",
age: 10
},
nums: [10, 20, 30]
}
});
console.log(vue.data.leftNode);
vue.data.info.age = 20
vue.data.nums.push(40);
复制代码
Vue3.0响应式原理
vue3中使用ES6的Proxy特性来实现响应式
Proxy
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
proxy基本应用:
const data = {
name: "luck",
age: 21
};
// 数组操作
// const data = [1,2,3,4]
/**
* get(target, propKey, receiver) 拦截对象属性的读取
* set(target, propKey, value, receiver):拦截对象属性的设置
* has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值
* deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
* ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、
Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名
* ...
*/
let proxy = new Proxy(data, {
/**
*
* @param {*} target:目标对象
* @param {*} key:属性名
* @param {*} receiver:proxy 实例本身(可选)
* @returns
*/
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// const result = target[key]
return result;
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const result = Reflect.set(target, key, val, receiver);
// const result = target[key] = value;
console.log("set", key, val);
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log("delete property", key);
return result;
}
});
// get
const age = proxy.age;
// set
proxy.name = "code";
// delete
const result = delete proxy.name;
// push
proxy.push(5)
复制代码
Reflect
- Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法
- 让Object操作更加规范化、标准化、函数式
- 将Object对象的一些工具函数(比如Object.defineProperty),放到Reflect对象上。
// 老写法
'name' in data // true
delete data.name
// 新写法
Reflect.has(data, 'name') // true
Reflect.deleteProperty(data,'name')
复制代码
proxy总结:
优点:能规避Object.defineProperty的问题
- 深度监听:性能更好--不会递归到底而是在get到这个层级才会去递归
- 可监听数组变化
- 可监听新增、删除属性
缺点:无法兼容所有浏览器,并无法polyfill
响应式简单处理:
function reactive(target = {}) {
if (typeof target !== "object" || target == null) {
return target;
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
const result = Reflect.get(target, key, receiver);
// 深度监听
// 性能提升: 什么时候get到这个层级才会去递归
return reactive(result);
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true;
}
const ownKeys = Reflect.ownKeys(target);
if (ownKeys.includes(key)) {
console.log("已有的 key", key);
} else {
console.log("新增的 key", key);
}
const result = Reflect.set(target, key, val, receiver);
console.log("set", key, val);
return result;
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key);
console.log("delete property", key);
return result; // 是否删除成功
}
};
// 生成代理对象
const observed = new Proxy(target, proxyConf);
return observed;
}
let data = {
name: "luck",
age: 33,
address: {
city: "sz"
},
arr: [1, 2, 3, 4]
};
const proxyData = reactive(data);
复制代码