前言
Vue3历时两年开发,99位贡献者,2600次提交,628次PR
Vue3支持2的大多数特性。
性能方面的提升:
1.打包大小减少41%
2.初次渲染快55%,更新快133%
3.内存使用减少54%
新增了Composition API以及其他特性,而且有更好的typescript支持。
学习和使用Vue3势在必行。
这篇文章,先来简单的对比一下Vue2和Vue3的响应式原理。
Vue2.0响应式原理
1.初识Object.defineProperty
Vue2借助原生js的api中Object.defineProperty,拦截对data对象的name属性的访问。
当访问时,执行get函数。属性变化时,监听这个变化,通过set函数。
举个栗子:
const data = {};
let name = 'Vue';
Object.defineProperty(data, 'name',{
get: function(){
console.log('get');
return name;
},
set: function (newValue){
console.log('set');
name = newValue;
// 视图重新渲染
}
})
复制代码
2.基本的响应式实现
我还有很多栗子,响应式的过程,代码如下:
const data = {
name: 'OrzR3',
age: 30
}
// 变成响应式数据
observer(data);
function observer(target){
if(typeof target !== 'object' || target === null){
return target;
}
for(let key in target){
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value){
Object.defineProperty(target, key,{
get(){
return value;
},
set(newValue){
if(newValue !== value){
value = newValue();
console.log('更新视图');
}
}
})
}
data.name = 'Test';
// 控制台打印,更新视图
复制代码
Vue源码更加复杂一点,判断更多情况,但是核心逻辑,就是这个逻辑。
3.处理值为复杂对象情况
如果对象中的属性,还是一个对象,继续调用observe,在set方法中,监听新设置的value
const data = {
name: 'OrzR3',
age: 30,
friend:{
friendName: 'sven'
}
}
// 变成响应式数据
observer(data);
function observer(target){
if(typeof target !== 'object' || target === null){
return target;
}
for(let key in target){
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value){
// 如果对象中的属性,还是一个对象,继续调用observe
observer(value);
Object.defineProperty(target, key,{
get(){
return value;
},
set(newValue){
// 监听新设置的value
observer(newValue);
if(newValue !== value){
value = newValue();
console.log('更新视图');
}
}
})
}
data.name = 'Test';
data.age = { number: 40 }
// 控制台打印,更新视图
复制代码
4.处理值为数组的情况
根据索引改变时,会更新视图。
使用push之类的方法,操作数组时,不会更新视图。
需要重写原本的数组的方法。
const oldArrayProto = Array.prototype;
const newArrProto = Object.create(oldArrayProto);
console.log('old', oldArrayProto);
console.log('new', newArrProto);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName =>{
newArrProto[methodName] = function(){
console.log('更新视图');
oldArrayProto[methodName].call(this, ...arguments);
}
})
复制代码
const data = {
name: 'OrzR3',
age: 30,
friend:{
friendName: 'sven'
},
colors: ['red', 'orange', 'green']
}
// 保存数组原型
const oldArrayProto = Array.prototype;
const newArrProto = Object.create(oldArrayProto);
console.log('old', oldArrayProto);
console.log('new', newArrProto);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName =>{
newArrProto[methodName] = function(){
console.log('更新视图');
oldArrayProto[methodName].call(this, ...arguments);
}
})
// 变成响应式数据
observer(data);
function observer(target){
if(typeof target !== 'object' || target === null){
return target;
}
// 把数据变成响应式数据的时候判断
// 如果数据是数组,修改原型为新创建的原型
if(Array.isArray(target)){
target.__proto__ = newArrProto;
}
for(let key in target){
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value){
// 如果对象中的属性,还是一个对象,继续调用observe
observer(value);
Object.defineProperty(target, key,{
get(){
return value;
},
set(newValue){
// 监听新设置的value
observer(newValue);
if(newValue !== value){
value = newValue();
console.log('更新视图');
}
}
})
}
data.name = 'Test';
data.age = { number: 40 }
// 根据索引改变时,会更新视图
data.colors[0] = 'blue';
// 使用push之类的方法,操作数组时,不会更新视图
// 需要重写原本的数组的方法
data.colors.push('blue');
// 控制台打印,更新视图
复制代码
缺点
-
Object.defineProperty深度监听,性能差。
-
使用Object.defineProperty做响应式数据时的问题:
-
如果数据为对象,而且层级很深,则不断的进行深度监听,直到属性是个普通的值。当数据复杂的时候,会卡死。
因此,在vue3中使用proxy来解决。proxy在使用到数据时,才启用监听。
-
此外,使用Object.defineProperty进行监听,当数据删除时,在data上新增属性,视图不会更新。
-
Object.defineProperty没有办法处理数据删除和新增属性。
-
因此,当数据删除时,使用Vue.delete方法。数据新增属性时,使用Vue.set方法。
这一系列问题,在Vue3.0中得到了优化解决方案。
Vue3.0响应式原理
Vue3.0 使用proxy代替了vue2.0版本中的defineProperty,监测机制改变,性能更好。弥补了Vue2.0的一些缺点。
举个例子,用proxy实现响应式
// proxy代理存放在WeakMap中
// toProxy存放代理后的对象
const toProxy = new WeakMap();
// toProxy存放代理前的对象
const toRaw = new WeakMap();
function trigger() {
console.log("触发视图更新");
}
function isObject(target) {
return typeof target === "object" && target !== null;
}
function reactive(target) {
if (!isObject(target)) {
return target;
}
// 如果代理表中已经存在了,就把这个结果返回
let proxy = toProxy.get(target);
if (proxy) {
return proxy;
}
// 如果这个对象已经被代理过了,则原封不动返回
if (toRaw.get(target)) {
return target;
}
const handlers = {
set(target, key, value, receiver) {
// 如果触发的是私有属性,可以直接触发视图更新
if (target.hasOwnProperty(key)) {
trigger();
}
return Reflect.set(target, key, value, receiver);
},
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver);
if (isObject(target[key])) {
// 如果属性是对象,递归调用
return reactive(res);
}
return res;
},
deleteProperty(target, key) {
return Reflect.deleteProperty(target, key);
},
};
// proxy es6方法
let observed = new Proxy(target, handlers);
toProxy.set(target, observed); //原对象,代理后的结果
toRaw.set(observed, target);
return observed;
}
let obj = {
name: "OrzR3",
list: [1, 2, 3],
};
let p = reactive(obj);
p.name = "sven";
p.list.push(4);
复制代码
附录
WeakMap
WeakMap 是es6语法,表示弱引用的对象。
在 JavaScript 中,一般我们创建一个对象,都是建立一个强引用:
var obj = new Object();
只有当我们手动设置 obj = null 的时候,才有可能回收 obj 所引用的对象。
而如果我们能创建一个弱引用的对象:
// 假设可以这样创建一个
var obj = new WeakObject();
我们什么都不用做,只用静静的等待垃圾回收机制执行,obj 所引用的对象就会被回收。
WeakMap 可以帮你省掉手动删除对象关联数据的步骤,所以当你不能或者不想控制关联数据的生命周期时就可以考虑使用 WeakMap。
Reflect 与 Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
详见文档: