getter 和 方法的区别(数据属性和访问器属性,Vue 3 中,computed 和 getter 的关系和区别)

getter与方法的本质区别在于属性描述符类型和调用方式。


方法属于数据属性,拥有函数值,需加括号调用;


getter是访问器属性,通过get函数定义,像属性一样访问无需括号。


关键差异包括:

  1. 方法可被覆盖赋值,getter需setter才能修改;

  2. Proxy拦截时方法返回函数引用,getter返回执行结果;

  3. 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 关键字 get keyword get 关键字 语法层面的标识符

概念层次图

复制代码
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;
  }
};

Object.defineProperty():JavaScript 中定义或修改对象属性的核心方法

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 定义),但根据实际需求可以组合出多种使用模式:

  1. 只读模式 :只有 get → 适合计算属性和派生值

  2. 只写模式 :只有 set → 适合数据上报、日志记录

  3. 读写模式 :同时有 getset → 适合数据验证、转换

  4. 动态模式:通过 Proxy 实现 → 适合框架层面的元编程


在 Vue 3 中:

  • 计算属性本质就是只有 get 的访问器属性

  • 响应式系统通过 Proxy 为所有属性动态创建访问器行为


Vue 3 中,computed 和 getter 的关系和区别


computed vs getter 在 Vue 3 中的关系和区别


在 Vue 3 中,computedgetter 看起来相似,但有着本质的区别。


1. 快速对比

对比维度 getter computed
定义位置 datasetup 返回的对象、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 时 每次读取时
  1. computed(计算属性)

    • 依赖变化时:标记为 dirty,但不立即执行计算

    • 被读取时:如果 dirty 则重新计算,否则返回缓存值

    • 结果:未读取时不会计算,依赖变化后首次读取会计算

  2. 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 的特性

区别

  1. 缓存:computed 有缓存,getter 没有

  2. 性能:getter 适合轻量计算,computed 适合复杂计算

  3. 使用场景:getter 用于简单派生状态,computed 用于需要优化的计算

  4. 语法: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,适合复杂计算和高频访问场景。

相关推荐
gCode Teacher 格码致知3 小时前
Javascript提高:Promise、Fetch、Axios、XHR、jQuery AJAX 完整对比-由Deepseek产生
javascript·ajax·jquery
计算机学姐3 小时前
基于SpringBoot+Vue的智能民宿预定游玩系统【AI智能客服+数据可视化】
java·vue.js·spring boot·后端·mysql·spring·信息可视化
二月夜3 小时前
Vue项目打包为WAR文件部署Tomcat完整指南
前端·vue.js·tomcat
终端鹿3 小时前
Vue3 核心 API 完结篇:toRaw / markRaw / shallowReactive / shallowRef 等进阶响应式 API 详解
前端·javascript·vue.js
sunxunyong3 小时前
集群增加用户&权限
前端·javascript·vue.js
每天都要进步哦3 小时前
React入门和快速上手
前端·vue.js·react.js·react
wuhen_n3 小时前
组件测试策略:测试 Props、事件和插槽
前端·javascript·vue.js
zhensherlock3 小时前
Protocol Launcher 系列:Pika 取色器的协议控制(上篇)
前端·javascript·macos·typescript·github·mac·view design
inksci3 小时前
推荐动态群聊二维码制作工具
前端·javascript·微信小程序