所谓数据响应式就是当数据发生变化时页面视图会自动更新
。
1. vue2.0数据响应
1.1 Object.defineProperty
vue2.0实现方式是利用原生的方法Object.defineProperty
实现对象属性的读写
劫持。举个例子:
let data = {
name: 'mengmeng'
}
Object.defineProperty(data, name, {
get(target, key) {
// 收集依赖
return target[key]
},
set(target, key, val) {
target[key] = val;
// 值发生改变时,通知更新
}
});
data.name = 'meng'
vue2.0主要是使用Object.defineProperty
把data的对象的属性全部转为getter/setter,让Vue能够追踪依赖
,在属性被访问和修改时通知触发视图更新
。
1.2 存在问题
vue2.0数据响应式主要存在如下问题:
- 不能监听数组变化
Object.defineProperty本身是可以监听数组变化的,但是由于性能问题
,数组变化没有直接用Object.defineProperty进行监听。
我们开发时认为可以更新是因为vue2.0对数组常用的7个方法(push、pop、unshift、shift、sort、reverse和splice)通过原型链进行了拦截
修改。
但是对于数组长度变化
和下标值修改内容
是无法监听的,vue提供了Vue.set(要修改的值,索引值,修改后的值)
进行响应式。不得不说大神考虑就是周到,原来是性能和用户体验做了权衡才这么处理的。
- 不能监听对象属性的新增和删除 因为vue2.0是提前遍历数据监听响应,动态的属性没有被监听所以不能响应式。
- 嵌套层次较深时,性能消耗大 因为Object.defineProperty会一开始就会遍历data、methods、props、computed、watch、mixins… 里的一系列变量全都绑定在this上,当嵌套层次比较深时会影响性能和占内存比较大。
基于以上问题,vue3.0放弃了这种方式,采用了proxy
代理方式进行监听,可以优雅的解决响应式。
2. vue3.0数据响应
2.1 Proxy代理
先来看下MND描述:
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。 使用方法如下:
new Proxy(data, {
// 拦截对象的属性读取
get(target, key) {
// 为了更好的兼容性,源码使用Reflect.get(target[key])
return target[key]
},
// 拦截对象的属性新增、修改
set(target, key, val) {
target[key] = val;
// todo 检测到变化时通知更新
},
// 拦截对象的属性删除
deleteProperty(target, key) {
delete target[key]
},
...
})
2.2 实例验证
接着上篇文章,我们继续为Vue添加一个reactive函数,主要用来处理引用类型,基本类型还需要一个函数ref,基本就是reactive的基础上封装的,相当于整体包裹为一个对象,增加了一个value属性。
const Vue = {
// 数据响应
reactive(data) {
return new Proxy(data, {
// 拦截对象的属性读取
get(target, key) {
return target[key]
},
// 拦截对象的属性新增、修改
set(target, key, val) {
target[key] = val;
// 检测到变化时通知更新
},
// 拦截对象的属性删除
deleteProperty(target, key) {
delete target[key]
}
})
},
...
}
通过测试用例验证数组通过下标改变内容和改变数组长度、对象嵌套的属性、新增属性和删除属性是否可以监听。
<script>
const {createApp, reactive} = Vue;
const app = createApp({
setup() {
const state = reactive({
title: 'hi, vue3',
});
setTimeout(() => {
// 删除对象属性
delete state.title
// 验证是否可以监听新增属性
state.name = 'you da da~~~'
}, 1000);
// 注意这里的对象return时不能解构 ...state,否则会失去响应式
return {
state
}
}
});
app.mount('#app');
</script>
附上完整html
<html>
<body>
<div id="app">
</div>
<script>
const Vue = {
// 数据响应(引用类型)
reactive(data) {
return new Proxy(data, {
// 拦截对象的属性读取
get(target, key) {
return target[key]
},
// 拦截对象的属性新增、修改
set(target, key, val) {
target[key] = val;
// 检测到变化时通知更新,为了简化先直接调用实例的更新方法
app.update();
},
// 拦截对象的属性删除
deleteProperty(target, key) {
delete target[key]
}
})
},
// 创建应用程序实例
createApp(options) {
return {
mount(selector) {
const parent = document.querySelector(selector);
if (!options.render) {
// 如果没有配置渲染函数,使用自定义的编译函数得到渲染函数
options.render = this.compile(parent.innerHTML);
}
//
this.setupData = options.setup();
// 给app实例增加一个更新方法,为了更好利用parent,通过变量声明函数
this.update = function() {
// 通过call执行函数,并将setup的返回值作为this
const el = options.render.call(this.setupData);
// 清空宿主元素的内容
parent.innerHTML = '';
// 将el追加到宿主元素
parent.appendChild(el);
}
this.update();
},
compile(template){
// template暂时不处理
return function render() {
const div = document.createElement('div');
// 这里的this就是setup函数的返回值,可以通过解构方式获取值
let {title='', name=''} = this.state;
div.innerHTML = title +'<br/>' + name;
return div
}
}
}
}
}
</script>
<script>
const {createApp, reactive} = Vue;
const app = createApp({
setup() {
const state = reactive({
title: 'hi, vue3',
});
setTimeout(() => {
// 删除对象属性
delete state.title
// 验证是否可以监听新增属性
state.name = 'you da da~~~'
}, 1000);
return {
state,
list
}
}
});
app.mount('#app');
</script>
</body>
</html>
浏览器运行,proxy可以实现对象新增属性和删除属性的监听,是不是很香~ 对于嵌套对象和数组监听的变化还需要特殊处理,下篇文章再细说
对于源码还涉及依赖收集
和触发副作用函数
的逻辑,我们这里为了理解整体流程做了简化。
3. 总结
vue2.0对于数组和对象发生变化时的处理方式不同,使用vue3后发现再也不需要对某些场景数据变化视图没更新烦恼了,所以还是要拥抱变化。
感谢点赞支持~
附上历史文章
- vue3系列
【vue3系列】快速入门vue3,通过实例对比vue3和vue2区别
【vue3系列】一步步实现vue3.0简易版的createApp功能
- webpack系列
【webpack系列】从源码角度分析webpack打包产出及核心流程
【webpack系列】从源码角度分析loader是如何触发和执行的
【webpack系列】webpack是如何解析模块的
【webpack系列】webpack的plugin插件是如何运行的
【webpack系列】从源码角度分析webpack热更新流程
【webpack系列】从源码角度深度剖析html-webpack-plugin执行过程