147 lines
3.6 KiB
Markdown
147 lines
3.6 KiB
Markdown
|
# Vue2 数据代理机制详解
|
|||
|
|
|||
|
## 一、核心概念
|
|||
|
|
|||
|
Vue2 中的数据代理是指 Vue 实例(vm)代理其 `data` 对象中属性的访问和修改操作。通过这种机制,我们可以直接通过 `vm.xxx` 访问 `data` 中的属性,而不需要写 `vm._data.xxx`。
|
|||
|
|
|||
|
## 二、代码示例解析
|
|||
|
|
|||
|
```html
|
|||
|
<div id="app">
|
|||
|
Vue中的数据代理: <span>{{name}}</span>
|
|||
|
</div>
|
|||
|
|
|||
|
<script>
|
|||
|
const vm = new Vue({
|
|||
|
el: "#app",
|
|||
|
data: {
|
|||
|
name: "代理..."
|
|||
|
}
|
|||
|
});
|
|||
|
</script>
|
|||
|
```
|
|||
|
|
|||
|
## 三、实现原理
|
|||
|
|
|||
|
### 1. 数据存储结构
|
|||
|
- 原始 `data` 对象被存储在 `vm._data` 属性中
|
|||
|
- 通过 `Object.defineProperty` 将 `data` 的属性代理到 Vue 实例上
|
|||
|
|
|||
|
### 2. 验证方法
|
|||
|
```javascript
|
|||
|
// 验证代理关系
|
|||
|
const externalData = { name: "代理..." };
|
|||
|
const vm = new Vue({
|
|||
|
el: "#app",
|
|||
|
data: externalData
|
|||
|
});
|
|||
|
|
|||
|
console.log(vm._data === externalData); // true
|
|||
|
console.log(vm.name === vm._data.name); // true
|
|||
|
```
|
|||
|
|
|||
|
### 3. 代理实现机制
|
|||
|
Vue 内部大致执行了以下操作:
|
|||
|
```javascript
|
|||
|
// 伪代码展示代理原理
|
|||
|
Object.keys(data).forEach(key => {
|
|||
|
Object.defineProperty(vm, key, {
|
|||
|
get() {
|
|||
|
return vm._data[key];
|
|||
|
},
|
|||
|
set(value) {
|
|||
|
vm._data[key] = value;
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
```
|
|||
|
|
|||
|
## 四、数据流图示
|
|||
|
|
|||
|
```
|
|||
|
模板访问
|
|||
|
↓
|
|||
|
vm.name (代理访问)
|
|||
|
↓
|
|||
|
vm._data.name (实际数据存储)
|
|||
|
↓
|
|||
|
数据劫持 (响应式系统)
|
|||
|
```
|
|||
|
|
|||
|
## 五、关键特性
|
|||
|
|
|||
|
| 特性 | 说明 | 示例 |
|
|||
|
| ---------- | ---------------------- | ------------------------------ |
|
|||
|
| 直接访问 | 省略 `_data` 前缀 | `vm.name` 代替 `vm._data.name` |
|
|||
|
| 响应式绑定 | 代理属性也是响应式的 | 修改 `vm.name` 会触发视图更新 |
|
|||
|
| 数据隔离 | `_data` 保存原始数据 | 防止直接修改破坏响应式 |
|
|||
|
| 统一入口 | 提供一致的数据访问方式 | 简化模板和方法的编写 |
|
|||
|
|
|||
|
## 六、与数据劫持的关系
|
|||
|
|
|||
|
1. **数据代理**:
|
|||
|
- 解决的是访问便捷性问题(`vm.xxx` vs `vm._data.xxx`)
|
|||
|
- 通过 `Object.defineProperty` 实现属性转发
|
|||
|
|
|||
|
2. **数据劫持**:
|
|||
|
- 解决的是响应式问题(数据变化触发视图更新)
|
|||
|
- 在 `vm._data` 上实现,通过重写 getter/setter
|
|||
|
|
|||
|
3. **协作流程**:
|
|||
|
- 模板访问 `{{name}}` → 触发代理 getter
|
|||
|
- 代理 getter 访问 `_data.name` → 触发劫持 getter
|
|||
|
- 建立依赖收集关系
|
|||
|
|
|||
|
## 七、注意事项
|
|||
|
|
|||
|
1. **新增属性**:
|
|||
|
- 直接 `vm.newProp = value` 不会成为响应式
|
|||
|
- 必须使用 `Vue.set` 或预先声明
|
|||
|
|
|||
|
2. **性能影响**:
|
|||
|
- 每个代理属性都会产生一定的内存开销
|
|||
|
- 大型项目应考虑分模块管理数据
|
|||
|
|
|||
|
3. **调试技巧**:
|
|||
|
- 通过 `vm._data` 查看原始数据
|
|||
|
- 使用 Vue Devtools 观察数据变化
|
|||
|
|
|||
|
4. **与 Vue3 区别**:
|
|||
|
- Vue3 使用 Proxy 实现更完善的代理
|
|||
|
- 可以检测属性添加/删除操作
|
|||
|
|
|||
|
## 八、扩展应用
|
|||
|
|
|||
|
### 1. 自定义代理逻辑
|
|||
|
```javascript
|
|||
|
// 在Vue实例上添加自定义代理
|
|||
|
created() {
|
|||
|
Object.defineProperty(this, 'fullName', {
|
|||
|
get() {
|
|||
|
return `${this.firstName} ${this.lastName}`;
|
|||
|
},
|
|||
|
set(value) {
|
|||
|
const parts = value.split(' ');
|
|||
|
this.firstName = parts[0];
|
|||
|
this.lastName = parts[1] || '';
|
|||
|
}
|
|||
|
});
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### 2. 代理模式应用
|
|||
|
```javascript
|
|||
|
// 实现组件间的数据代理
|
|||
|
const proxyMixin = {
|
|||
|
methods: {
|
|||
|
proxy(prop) {
|
|||
|
return {
|
|||
|
get: () => this[prop],
|
|||
|
set: (val) => this[prop] = val
|
|||
|
};
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
```
|
|||
|
|