getter与方法的本质区别在于属性描述符类型和调用方式。
方法属于数据属性,拥有函数值,需加括号调用;
getter是访问器属性,通过get函数定义,像属性一样访问无需括号。
关键差异包括:
方法可被覆盖赋值,getter需setter才能修改;
Proxy拦截时方法返回函数引用,getter返回执行结果;
Vue3中getter自动追踪依赖适合计算属性,方法需手动调用。
性能上getter每次访问都计算,方法调用才执行。
使用场景建议:方法用于主动操作,getter处理派生属性。
方法强调动作执行,getter侧重属性访问的自动计算机制。
getter 是原生能力,适合简单派生;
computed 是 Vue 的优化 API,适合复杂计算和高频访问场景。
getter 和 方法的区别
先搞懂getter概念
get vs getter 的区别
维度 get (Proxy 陷阱) getter (访问器属性) 所属层级 Proxy 的拦截器方法 对象属性的定义方式 作用 拦截所有属性的读取操作 定义某个特定属性的读取行为 作用范围 代理对象的所有属性 对象的单个属性 定义位置 Proxy 的 handler 对象中 对象字面量或类中 触发时机 访问代理对象的任何属性时 访问该特定属性时 是否必须 可选(不定义则透传) 可选(普通属性不需要) 两者关系图解
┌─────────────────────────────────────────────┐ │ Proxy 代理层 │ │ ┌───────────────────────────────────────┐ │ │ │ get 陷阱(拦截所有属性) │ │ │ │ - 可以访问任何属性 │ │ │ │ - 可以决定返回什么 │ │ │ │ - 可以调用 Reflect.get │ │ │ └───────────────────────────────────────┘ │ │ ↓ │ │ Reflect.get(target, key, receiver) │ │ ↓ │ │ ┌───────────────────────────────────────┐ │ │ │ 原始对象 (target) │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ getter(特定属性的行为) │ │ │ │ │ │ - 只控制这一个属性 │ │ │ │ │ │ - 在属性被读取时执行 │ │ │ │ │ └─────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ 普通数据属性 │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └───────────────────────────────────────┘ │ └─────────────────────────────────────────────┘
一句话总结
get 是 Proxy 的"守门人",控制所有属性的访问;
getter 是属性的"自定义读取器",只控制这一个属性的读取行为。
get在 JavaScript 中的多重含义
用法 示例 含义 getter get prop() {}定义访问器属性的关键字 Proxy get get(target, prop) {}Proxy 陷阱的名称 Reflect.get Reflect.get(obj, prop)Reflect 对象的方法名 Map.get map.get(key)Map 实例的方法名 URLSearchParams.get params.get('key')URLSearchParams 的方法名
区分称呼,避免歧义
getter 的
get比 Proxy 的get早了 6 年出现!
术语对照表
getter 是标准术语,指代访问器属性
术语 英文全称 中文译名 使用场景 getter Getter 访问器/getter 定义属性的读取行为 Proxy get Proxy get trap Proxy 的 get 陷阱 拦截属性读取 Reflect.get Reflect.get method Reflect 的 get 方法 反射式属性读取 get 关键字 getkeywordget 关键字 语法层面的标识符
概念层次图
JavaScript 属性访问机制
│
├── 语法层面
│ ├── 普通属性: obj.prop
│ └── 访问器属性:
│ ├── getter (get 关键字) ← 定义读取行为
│ └── setter (set 关键字) ← 定义写入行为
│
├── 元编程层面
│ ├── Proxy:
│ │ └── get 陷阱 ← 拦截所有读取
│ └── Reflect:
│ └── get 方法 ← 执行默认读取
│
└── 实例方法层面
├── Map.get()
├── URLSearchParams.get()
└── 等等...
| 为什么不说"原生 get" | 原因 |
|---|---|
| 历史原因 | getter 的 get 关键字(2009)早于 Proxy 的 get 陷阱(2015) |
| 语义冲突 | get 在不同上下文有不同含义(关键字、陷阱名、方法名) |
| 避免歧义 | "原生 get" 无法确定具体指代哪个概念 |
| 专业术语 | getter 是标准术语,指代访问器属性 |
| 沟通效率 | 精确的术语能避免理解偏差 |
一句话总结:
说"getter"能精确指代访问器属性,说"原生 get"则可能混淆为关键字、陷阱或方法,因此专业术语中必须区分开来。
getter 和方法虽然看起来相似,但本质上有重要区别。
1. 定义方式的区别
javascript
const obj = {
// 方法:普通函数属性
method1() {
return '我是方法';
},
// 方法:函数表达式
method2: function() {
return '我也是方法';
},
// getter:使用 get 关键字
get getter1() {
return '我是 getter';
},
// getter:使用 Object.defineProperty
_value: 0,
get getter2() {
return this._value;
},
set setter2(val) {
this._value = val;
}
};
2. 调用方式的区别
javascript
const obj = {
// 方法:需要加括号调用
myMethod() {
return '方法被调用';
},
// getter:不需要括号,像访问属性一样
get myGetter() {
return 'getter 被访问';
}
};
// 方法调用
console.log(obj.myMethod()); // "方法被调用" ← 有括号
// getter 访问
console.log(obj.myGetter); // "getter 被访问" ← 无括号
3. 本质区别:属性描述符
javascript
const obj = {
myMethod() {},
get myGetter() {}
};
// 查看方法的数据描述符
const methodDesc = Object.getOwnPropertyDescriptor(obj, 'myMethod');
console.log(methodDesc);
// {
// value: [Function: myMethod],
// writable: true,
// enumerable: true,
// configurable: true
// }
// 查看 getter 的数据描述符
const getterDesc = Object.getOwnPropertyDescriptor(obj, 'myGetter');
console.log(getterDesc);
// {
// get: [Function: get myGetter],
// set: undefined,
// enumerable: true,
// configurable: true
// }
关键区别:
-
方法 :数据属性,
value是函数 -
getter :访问器属性,有
get函数,没有value
4. 赋值行为的区别
javascript
const obj = {
// 方法可以被覆盖
method: function() {
return '原始方法';
},
// getter 不能被直接赋值(除非定义了 setter)
get getter() {
return '原始 getter';
}
};
// 方法可以被重新赋值
obj.method = function() {
return '新方法';
};
console.log(obj.method()); // "新方法" ✓
// getter 赋值不会改变 getter 本身
obj.getter = '尝试修改';
console.log(obj.getter); // "原始 getter" ← 不变!
console.log(obj.getter); // 仍然是 "原始 getter"
5. 在 Proxy 中的表现差异
javascript
const target = {
// 方法
method() {
console.log('method this:', this === proxy);
return 'method';
},
// getter
get getter() {
console.log('getter this:', this === proxy);
return 'getter';
},
// 普通数据
data: 'data'
};
const proxy = new Proxy(target, {
get(target, key, receiver) {
console.log(`Proxy get 拦截: ${key}`);
const result = Reflect.get(target, key, receiver);
console.log(`返回值类型: ${typeof result}`);
return result;
}
});
// 访问方法
console.log('=== 调用方法 ===');
proxy.method();
// 输出:
// Proxy get 拦截: method
// 返回值类型: function
// method this: true
// (方法内部的 this 指向 proxy)
// 访问 getter
console.log('\n=== 访问 getter ===');
proxy.getter;
// 输出:
// Proxy get 拦截: getter
// 返回值类型: string
// getter this: true
// (getter 函数内部 this 也指向 proxy)
// 访问数据
console.log('\n=== 访问数据 ===');
proxy.data;
// 输出:
// Proxy get 拦截: data
// 返回值类型: string
// (数据属性没有函数调用,不涉及 this)
关键发现: 在 Proxy 的 get 陷阱中,两者都被拦截,但:
-
方法返回的是函数引用
-
getter 返回的是函数执行结果
6. 性能和使用场景的区别
javascript
// 方法:每次调用都执行
const obj1 = {
getCurrentTime() {
return Date.now();
}
};
console.log(obj1.getCurrentTime()); // 每次不同
console.log(obj1.getCurrentTime()); // 每次不同
// getter:每次访问都执行
const obj2 = {
get currentTime() {
return Date.now();
}
};
console.log(obj2.currentTime); // 每次不同
console.log(obj2.currentTime); // 每次不同
// 但 getter 更适合表示"派生属性"
const user = {
firstName: 'Alice',
lastName: 'Lee',
// getter:派生属性,看起来像属性
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// 方法:执行动作
sayHello() {
return `Hello, I'm ${this.fullName}`;
}
};
console.log(user.fullName); // "Alice Lee" - 像访问属性
console.log(user.sayHello()); // "Hello, I'm Alice Lee" - 像调用动作
7. 在 Vue 3 响应式中的区别
javascript
const data = {
firstName: 'Alice',
lastName: 'Lee',
// getter:计算属性
get fullName() {
// Vue 3 会自动追踪 firstName 和 lastName 的依赖
return `${this.firstName} ${this.lastName}`;
},
// 方法:普通函数
getFullName() {
// Vue 3 不会自动追踪依赖
// 需要手动调用才能获取最新值
return `${this.firstName} ${this.lastName}`;
}
};
const proxy = new Proxy(data, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
}
});
// 访问 getter:自动收集依赖
console.log(proxy.fullName);
// track 会记录:fullName 依赖 firstName 和 lastName
// 当 firstName 变化时,fullName 自动更新
// 调用方法:不会自动收集依赖
console.log(proxy.getFullName());
// track 只记录 getFullName 被访问
// 但不会记录它内部依赖了 firstName 和 lastName
8. 总结对比表
| 特性 | 方法 (Method) | Getter |
|---|---|---|
| 定义方式 | method() {} 或 method: function() {} |
get prop() {} |
| 调用方式 | obj.method() 加括号 |
obj.prop 不加括号 |
| 属性描述符类型 | 数据属性(有 value) |
访问器属性(有 get) |
| 能否被赋值覆盖 | ✓ 可以 | ✗ 不能(除非有 setter) |
| 返回值 | 函数执行的返回值 | getter 函数执行的返回值 |
| 在 Proxy get 陷阱中 | 返回函数引用 | 返回执行结果 |
| 语义 | 执行动作/操作 | 获取派生/计算值 |
| Vue 3 使用场景 | 事件处理、异步操作 | 计算属性 |
9. 关键理解
最本质的区别:
方法是一个动作(动词),需要调用才能执行;
getter 是一个属性(名词),访问时自动计算,看起来像普通属性。
在 Vue 3 中:
-
getter 天然适合做计算属性,自动追踪依赖
-
方法适合做事件处理、数据转换等需要参数或主动调用的场景
javascript
// ✅ 适合用 getter
computed: {
fullName() { // 实际上是 getter
return this.firstName + ' ' + this.lastName;
}
}
// ✅ 适合用方法
methods: {
formatDate(date, format) { // 需要参数
return dayjs(date).format(format);
}
}
Vue中:getter function 详解(附:为什么props对象需要使用getter function不能直接watch和ref的特殊性)
使用表格总结:数据属性和访问器属性的区别
数据属性 vs 访问器属性 完整对比
| 对比维度 | 数据属性 (Data Property) | 访问器属性 (Accessor Property) |
|---|---|---|
| 定义方式 | { key: value } |
{ get key() {} } 或 { set key() {} } |
| 本质 | 存储实际值 | 不存储值,通过函数计算或控制访问 |
| 属性描述符 | value, writable, enumerable, configurable |
get, set, enumerable, configurable |
| 是否有值 | ✓ 有 value 存储实际数据 |
✗ 没有 value,通过 get/set 函数操作 |
| 读取方式 | obj.prop |
obj.prop(触发 getter) |
| 赋值方式 | obj.prop = value |
obj.prop = value(触发 setter) |
| 能否直接赋值覆盖 | ✓ 可以(如果 writable: true) |
✗ 不可以(需要 setter 来响应赋值) |
| 赋值时是否执行代码 | ✗ 直接存储值 | ✓ 执行 setter 函数 |
| 读取时是否执行代码 | ✗ 直接返回值 | ✓ 执行 getter 函数 |
| 动态计算 | ✗ 只能存储静态值 | ✓ 可以动态计算返回值 |
| 验证逻辑 | ✗ 无法在赋值时验证 | ✓ 可在 setter 中添加验证 |
| 副作用 | ✗ 无法触发副作用 | ✓ 可在 get/set 中添加日志、通知等 |
| 性能 | 快(直接内存访问) | 较慢(函数调用开销) |
| Vue 3 响应式 | 通过 Proxy 的 get/set 陷阱拦截 |
通过 Proxy 的 get/set 陷阱拦截,内部可能嵌套访问其他属性 |
| 使用场景 | 普通数据存储 | 计算属性、数据验证、私有属性控制 |
代码示例对比
javascript
// 1. 数据属性
const dataProp = {
name: 'Alice', // 数据属性
age: 25 // 数据属性
};
// 2. 访问器属性
const accessorProp = {
_name: 'Alice', // 实际存储的私有属性
_age: 25,
// getter
get name() {
console.log('读取 name');
return this._name;
},
// getter + setter
get age() {
console.log('读取 age');
return this._age;
},
set age(value) {
console.log(`设置 age = ${value}`);
if (value < 0 || value > 150) {
throw new Error('年龄无效');
}
this._age = value;
},
// 计算属性(getter 无 setter)
get info() {
return `${this.name}, ${this.age}岁`;
}
};
// 使用对比
console.log('=== 数据属性 ===');
console.log(dataProp.name); // "Alice" - 直接读取
dataProp.age = 26; // 直接赋值
console.log(dataProp.age); // 26
console.log('\n=== 访问器属性 ===');
console.log(accessorProp.name); // 触发 getter → "Alice"
accessorProp.age = 26; // 触发 setter(验证通过)
console.log(accessorProp.age); // 触发 getter → 26
console.log(accessorProp.info); // 触发 getter → "Alice, 26岁"
// 尝试直接赋值给只有 getter 的属性
accessorProp.info = 'test'; // 无效(没有 setter)
console.log(accessorProp.info); // 仍是 "Alice, 26岁"
属性描述符对比
javascript
const obj = {
// 数据属性
dataProp: 'value',
// 访问器属性
get accessorProp() {
return 'value';
}
};
const dataDesc = Object.getOwnPropertyDescriptor(obj, 'dataProp');
const accessorDesc = Object.getOwnPropertyDescriptor(obj, 'accessorProp');
console.log('数据属性描述符:', dataDesc);
// {
// value: 'value',
// writable: true,
// enumerable: true,
// configurable: true
// }
console.log('访问器属性描述符:', accessorDesc);
// {
// get: [Function: get accessorProp],
// set: undefined,
// enumerable: true,
// configurable: true
// }
在 Vue 3 响应式中的表现差异
javascript
const data = {
// 数据属性
firstName: 'Alice',
lastName: 'Lee',
// 访问器属性(getter)
get fullName() {
// 依赖追踪:访问 this.firstName 和 this.lastName 时
// 会触发 Proxy 的 get 陷阱,自动收集依赖
return `${this.firstName} ${this.lastName}`;
}
};
const proxy = new Proxy(data, {
get(target, key, receiver) {
console.log(`收集依赖: ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`触发更新: ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
}
});
// 访问 fullName 时
console.log(proxy.fullName);
// 输出:
// 收集依赖: fullName
// 收集依赖: firstName ← getter 内部访问触发
// 收集依赖: lastName ← getter 内部访问触发
// Alice Lee
// 修改 firstName
proxy.firstName = 'Bob';
// 输出:
// 触发更新: firstName = Bob
// (fullName 会自动重新计算)
关键总结
| 维度 | 数据属性 | 访问器属性 |
|---|---|---|
| 存储 | 直接存储值 | 不存储值,通过函数控制 |
| 时机 | 读写立即完成 | 读写时执行函数逻辑 |
| 灵活性 | 低(只能存取值) | 高(可添加验证、计算、日志) |
| 复杂度 | 简单 | 较复杂 |
| 适用场景 | 90% 的普通数据 | 计算属性、数据验证、私有属性 |
核心理解:
-
数据属性:存储真实数据的"容器"
-
访问器属性:控制数据访问的"门卫",可以在读取/写入时执行自定义逻辑
访问器属性有哪些
访问器属性的类型
访问器属性本质上只有一种,但根据定义方式可以分为以下几种形式。
1. 只有 getter(只读属性)
最常见的访问器属性形式,只能读取,不能赋值。
javascript
const obj = {
// 只有 getter
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
firstName: 'Alice',
lastName: 'Lee'
};
console.log(obj.fullName); // "Alice Lee"
obj.fullName = 'Bob Smith'; // 无效,不会报错但也不生效
console.log(obj.fullName); // 仍然是 "Alice Lee"
2. 只有 setter(只写属性)
比较少见,只能赋值,不能读取。
javascript
const obj = {
_data: null,
// 只有 setter
set data(value) {
this._data = value;
console.log(`数据已更新: ${value}`);
}
};
obj.data = 'hello'; // 触发 setter → "数据已更新: hello"
console.log(obj.data); // undefined(因为没有 getter)
console.log(obj._data); // "hello"(只能通过内部变量访问)
3. 同时有 getter 和 setter(读写属性)
最常见且实用的形式,可以读取和赋值。
javascript
const obj = {
_age: 0,
// 同时有 getter 和 setter
get age() {
return this._age;
},
set age(value) {
if (value < 0) throw new Error('年龄不能为负数');
if (value > 150) throw new Error('年龄超出范围');
this._age = value;
}
};
obj.age = 25; // 触发 setter
console.log(obj.age); // 触发 getter → 25
4. 计算属性(特殊的 getter)
用于动态计算值的访问器属性,通常只有 getter。
javascript
const cart = {
items: [
{ name: '苹果', price: 5, quantity: 2 },
{ name: '香蕉', price: 3, quantity: 3 }
],
// 计算总价
get totalPrice() {
return this.items.reduce((sum, item) =>
sum + item.price * item.quantity, 0
);
},
// 计算商品数量
get totalItems() {
return this.items.reduce((sum, item) =>
sum + item.quantity, 0
);
}
};
console.log(cart.totalPrice); // 19 (5*2 + 3*3)
console.log(cart.totalItems); // 5
5. 懒加载属性
利用 getter 实现延迟计算,计算后缓存结果。
javascript
const obj = {
_data: null,
get data() {
if (!this._data) {
console.log('首次访问,加载数据...');
this._data = heavyComputation(); // 模拟耗时计算
}
return this._data;
}
};
function heavyComputation() {
// 模拟耗时操作
return { result: '复杂计算结果' };
}
console.log(obj.data); // 第一次:加载数据...
console.log(obj.data); // 第二次:直接返回缓存
6. 使用 Object.defineProperty 定义
除了字面量语法,还可以通过 Object.defineProperty 定义访问器属性。
javascript
const obj = {};
Object.defineProperty(obj, 'name', {
get() {
console.log('读取 name');
return this._name;
},
set(value) {
console.log(`设置 name = ${value}`);
this._name = value;
},
enumerable: true,
configurable: true
});
obj.name = 'Alice'; // 设置 name = Alice
console.log(obj.name); // 读取 name → Alice
7. 类中的访问器属性
在 ES6 类中也可以定义访问器属性。
javascript
class User {
constructor(firstName, lastName) {
this._firstName = firstName;
this._lastName = lastName;
}
// getter
get fullName() {
return `${this._firstName} ${this._lastName}`;
}
// setter
set fullName(value) {
[this._firstName, this._lastName] = value.split(' ');
}
// 只读属性
get initials() {
return `${this._firstName[0]}.${this._lastName[0]}.`;
}
}
const user = new User('Alice', 'Lee');
console.log(user.fullName); // "Alice Lee"
user.fullName = 'Bob Smith';
console.log(user.fullName); // "Bob Smith"
console.log(user.initials); // "B.S."
8. 私有字段 + 访问器(ES2022)
使用私有字段配合访问器属性实现更安全的封装。
# 是 ES2022 (ES13) 引入的 私有字段(Private Fields) 语法,用于在类中定义真正的私有属性。
javascript
class BankAccount {
#balance = 0; // 私有字段
get balance() {
console.log('查询余额');
return this.#balance;
}
set balance(value) {
if (value < 0) {
throw new Error('余额不能为负数');
}
console.log(`更新余额: ${value}`);
this.#balance = value;
}
deposit(amount) {
this.balance = this.#balance + amount;
}
}
const account = new BankAccount();
account.deposit(100);
console.log(account.balance); // 100
account.balance = -50; // Error: 余额不能为负数
9. 动态访问器属性
使用 Proxy 动态创建访问器属性。
javascript
const handler = {
get(target, prop) {
// 动态 getter
if (prop === 'random') {
return Math.random();
}
return target[prop];
},
set(target, prop, value) {
// 动态 setter
if (prop === 'timestamp') {
target[prop] = Date.now();
} else {
target[prop] = value;
}
return true;
}
};
const obj = new Proxy({}, handler);
console.log(obj.random); // 0.123... (每次不同)
obj.timestamp = 'anything';
console.log(obj.timestamp); // 1701234567890 (实际存储的是时间戳)
完整对比表格
| 类型 | getter | setter | 读操作 | 写操作 | 常见用途 |
|---|---|---|---|---|---|
| 只读属性 | ✓ | ✗ | ✓ | 无效(不报错) | 计算属性、派生值 |
| 只写属性 | ✗ | ✓ | undefined | ✓ | 日志记录、数据发送 |
| 读写属性 | ✓ | ✓ | ✓ | ✓(验证后) | 数据验证、转换 |
| 计算属性 | ✓ | ✗(可选) | ✓ | 通常无效 | 动态计算结果 |
| 懒加载属性 | ✓ | ✗ | ✓ | 无效 | 延迟初始化、缓存 |
| 类访问器 | ✓ | ✓ | ✓ | ✓ | 封装私有字段 |
| 动态访问器 | ✓(Proxy) | ✓(Proxy) | ✓ | ✓ | 元编程、响应式系统 |
核心理解
访问器属性本质上只有一种 (通过 get 和/或 set 定义),但根据实际需求可以组合出多种使用模式:
-
只读模式 :只有
get→ 适合计算属性和派生值 -
只写模式 :只有
set→ 适合数据上报、日志记录 -
读写模式 :同时有
get和set→ 适合数据验证、转换 -
动态模式:通过 Proxy 实现 → 适合框架层面的元编程
在 Vue 3 中:
-
计算属性本质就是只有
get的访问器属性 -
响应式系统通过 Proxy 为所有属性动态创建访问器行为
Vue 3 中,computed 和 getter 的关系和区别
computed vs getter 在 Vue 3 中的关系和区别
在 Vue 3 中,computed 和 getter 看起来相似,但有着本质的区别。
1. 快速对比
| 对比维度 | getter | computed |
|---|---|---|
| 定义位置 | 在 data、setup 返回的对象、reactive 对象中 |
在 computed() 函数中定义 |
| 本质 | JavaScript 原生访问器属性 | Vue 响应式系统的 API |
| 缓存机制 | ❌ 无缓存,每次访问都执行 | ✅ 有缓存,依赖未变时不重新计算 |
| 依赖追踪 | 自动追踪(通过 Proxy) | 自动追踪且更高效 |
| 可写性 | 默认只读,可定义 setter | 可定义可写 computed |
| 性能 | 每次访问都重新计算 | 缓存结果,性能更好 |
| 使用场景 | 简单的派生状态 | 复杂计算、需要缓存的场景 |
2. 核心区别:缓存机制
这是两者最本质的区别:
html
<script setup>
import { ref, computed, reactive } from 'vue'
// 1. 使用 getter(在 reactive 中)
const state = reactive({
firstName: 'Alice',
lastName: 'Lee',
get fullName() {
console.log('getter 执行了')
return `${this.firstName} ${this.lastName}`
}
})
// 2. 使用 computed
const firstName = ref('Alice')
const lastName = ref('Lee')
const fullNameComputed = computed(() => {
console.log('computed 执行了')
return `${firstName.value} ${lastName.value}`
})
// 测试多次访问
console.log(state.fullName) // getter 执行了 → "Alice Lee"
console.log(state.fullName) // getter 执行了 → "Alice Lee" (再次执行)
console.log(state.fullName) // getter 执行了 → "Alice Lee" (每次都执行)
console.log(fullNameComputed.value) // computed 执行了 → "Alice Lee"
console.log(fullNameComputed.value) // 不执行,直接返回缓存 → "Alice Lee"
console.log(fullNameComputed.value) // 不执行,直接返回缓存 → "Alice Lee"
// 修改依赖后
firstName.value = 'Bob'
console.log(fullNameComputed.value) // computed 执行了 → "Bob Lee" (重新计算)
</script>
3. 依赖追踪的差异
html
<script setup>
import { ref, computed, reactive, watchEffect } from 'vue'
// getter 的依赖追踪
const data = reactive({
count: 0,
get double() {
console.log('getter 计算 double')
return this.count * 2
}
})
// computed 的依赖追踪
const count = ref(0)
const doubleComputed = computed(() => {
console.log('computed 计算 double')
return count.value * 2
})
// 观察副作用
watchEffect(() => {
console.log('getter 被读取:', data.double)
})
// 输出:getter 计算 double → getter 被读取: 0
watchEffect(() => {
console.log('computed 被读取:', doubleComputed.value)
})
// 输出:computed 计算 double → computed 被读取: 0
// 修改依赖
data.count++ // 触发 getter 重新计算
count.value++ // 触发 computed 重新计算
</script>
两者在"依赖变化时"和"被读取时"的行为完全不同:
行为 computed getter 依赖变化时 标记为 dirty(脏数据),不立即执行 无变化(不执行任何操作) 被读取时 如果 dirty 则重新计算,否则返回缓存 每次读取都立即执行
computed(计算属性)
-
懒执行:只在被读取时才计算
-
缓存机制:依赖未变化时返回缓存值
-
标记 dirty:依赖变化时只标记为 dirty,不立即计算
getter(访问器属性)
-
每次执行:每次读取都重新计算
-
无缓存:没有缓存机制
-
无标记:依赖变化时不触发任何操作
更直观的对比表
| 行为维度 | computed | getter |
|---|---|---|
| 依赖变化时(未读取) | 标记为 dirty,不执行计算 | 无任何操作 |
| 第一次读取 | 执行计算,返回结果 | 执行计算,返回结果 |
| 第二次读取(依赖未变) | 返回缓存,不执行计算 | 执行计算,返回结果 |
| 依赖变化后读取 | 执行计算(因为 dirty),返回新结果 | 执行计算,返回新结果 |
| 是否缓存 | ✅ 有缓存 | ❌ 无缓存 |
| 计算时机 | 读取时且 dirty 时 | 每次读取时 |
-
computed(计算属性)
-
依赖变化时:标记为 dirty,但不立即执行计算
-
被读取时:如果 dirty 则重新计算,否则返回缓存值
-
结果:未读取时不会计算,依赖变化后首次读取会计算
-
-
getter(访问器属性)
-
依赖变化时:不做任何处理
-
被读取时:每次都重新计算
-
结果:无论是否读取,依赖变化本身不触发计算;只有在读取时才计算
-
关键差异
computed 有缓存机制,依赖变化时只标记,读取时判断是否需要重新计算;
getter 无缓存,每次读取都重新计算,与依赖是否变化无关。
4. 性能对比
html
<script setup>
import { ref, computed, reactive } from 'vue'
// 模拟复杂计算
function expensiveCalculation() {
console.log('执行复杂计算...')
let sum = 0
for (let i = 0; i < 10000000; i++) sum += i
return sum
}
// getter:每次访问都重新计算
const state = reactive({
data: expensiveCalculation(),
get result() {
console.log('getter 重新计算')
return this.data + expensiveCalculation()
}
})
// computed:缓存结果
const data = ref(expensiveCalculation())
const resultComputed = computed(() => {
console.log('computed 重新计算')
return data.value + expensiveCalculation()
})
// 多次访问
console.log('=== getter ===')
console.log(state.result) // 执行复杂计算 → getter 重新计算
console.log(state.result) // 再次执行复杂计算 (重复计算)
console.log(state.result) // 再次执行复杂计算 (重复计算)
console.log('=== computed ===')
console.log(resultComputed.value) // 执行复杂计算 → computed 重新计算
console.log(resultComputed.value) // 直接返回缓存,不计算
console.log(resultComputed.value) // 直接返回缓存,不计算
</script>
5. 可写 computed vs getter setter
html
<script setup>
import { ref, computed, reactive } from 'vue'
// getter + setter(原生)
const state = reactive({
firstName: 'Alice',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(' ')
}
})
// 可写 computed
const firstName = ref('Alice')
const lastName = ref('Lee')
const fullNameComputed = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(value) {
[firstName.value, lastName.value] = value.split(' ')
}
})
// 两者使用方式相同
state.fullName = 'Bob Smith'
console.log(state.fullName) // "Bob Smith"
fullNameComputed.value = 'Bob Smith'
console.log(fullNameComputed.value) // "Bob Smith"
</script>
6. 响应式更新机制
html
<script setup>
import { ref, computed, reactive } from 'vue'
// getter:依赖变化时,通过 Proxy 自动更新
const user = reactive({
firstName: 'Alice',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`
}
})
// computed:依赖变化时,标记为 dirty,下次访问时重新计算
const firstName = ref('Alice')
const lastName = ref('Lee')
const fullNameComputed = computed(() => {
return `${firstName.value} ${lastName.value}`
})
// 模板中使用
// 两者都能响应式更新
</script>
<template>
<div>
<!-- getter 在模板中每次渲染都会重新计算 -->
<p>{{ user.fullName }}</p>
<!-- computed 只在依赖变化时重新计算 -->
<p>{{ fullNameComputed }}</p>
<button @click="user.firstName = 'Bob'">修改 getter</button>
<button @click="firstName = 'Bob'">修改 computed</button>
</div>
</template>
7. 使用场景建议
html
<script setup>
import { ref, computed, reactive } from 'vue'
// ✅ getter 适用场景:简单、轻量、访问频率低
const user = reactive({
firstName: 'Alice',
lastName: 'Lee',
get fullName() {
// 简单字符串拼接,每次访问开销小
return `${this.firstName} ${this.lastName}`
},
get ageGroup() {
// 简单的条件判断
if (this.age < 18) return '未成年'
if (this.age < 60) return '成年'
return '老年'
}
})
// ✅ computed 适用场景:复杂计算、访问频率高、需要缓存
const items = ref([])
const totalPrice = computed(() => {
// 复杂计算:遍历数组,求和
return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
})
const filteredList = computed(() => {
// 数组过滤和排序
return items.value
.filter(item => item.price > 10)
.sort((a, b) => a.price - b.price)
})
// ❌ 不适合用 getter 的场景
const state = reactive({
items: [],
get totalPrice() {
// 每次访问都重新计算整个数组,性能差
return this.items.reduce((sum, item) => sum + item.price, 0)
}
})
// ✅ 应该用 computed
const totalPriceComputed = computed(() => {
return state.items.reduce((sum, item) => sum + item.price, 0)
})
</script>
8. 深度对比表
| 特性 | getter | computed |
|---|---|---|
| 定义方式 | get prop() {} 在对象中 |
computed(() => value) |
| 返回值 | 直接返回 | Ref 对象(需要 .value) |
| 缓存 | ❌ 无缓存 | ✅ 依赖未变时缓存 |
| 计算时机 | 每次访问时 | 依赖变化且被访问时 |
| 依赖追踪 | 通过 Proxy 自动追踪 | Vue 响应式系统追踪 |
| 性能 | 适合简单计算 | 适合复杂计算 |
| 模板中使用 | {``{ user.fullName }} |
{``{ fullName }}(自动解包) |
| 调试 | 较难追踪 | DevTools 支持更好 |
| TypeScript | 类型推断好 | 需要明确返回类型 |
| 副作用 | 不应有副作用 | 不应有副作用 |
| 可写性 | 可定义 setter | 可定义可写 computed |
9. 核心总结
关系
-
getter 是 JavaScript 原生特性,Vue 3 通过 Proxy 让其具备响应式能力
-
computed 是 Vue 提供的 API,基于响应式系统实现,底层利用了 getter 的特性
区别
-
缓存:computed 有缓存,getter 没有
-
性能:getter 适合轻量计算,computed 适合复杂计算
-
使用场景:getter 用于简单派生状态,computed 用于需要优化的计算
-
语法:getter 在对象中定义,computed 是独立 API
最佳实践
javascript
// ✅ 简单拼接用 getter
const user = reactive({
firstName: 'Alice',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`
}
})
// ✅ 复杂计算用 computed
const total = computed(() => {
return items.value
.filter(item => item.active)
.reduce((sum, item) => sum + item.price, 0)
})
// ✅ 需要缓存的用 computed
const expensiveResult = computed(() => {
return expensiveCalculation(dep.value)
})
// ✅ 模板中多次使用的用 computed
const displayName = computed(() => {
return `${user.firstName} ${user.lastName}`
})
一句话总结:
getter 是原生能力,适合简单派生;
computed 是 Vue 的优化 API,适合复杂计算和高频访问场景。