前言
在现代JavaScript开发中,特别是使用Vue、React等前端框架时,计算属性、方法和侦听器是三个非常重要的概念。这些功能帮助开发者更高效地管理数据和处理逻辑,创建响应式的用户界面。本教程将深入讲解这三个概念,从基础原理到实际应用,帮助新手全面理解和掌握它们。
1. 计算属性(Computed Properties)
1.1 基本概念和原理
计算属性是基于数据的派生值。简单来说,它是一个依赖于其他数据的属性,当依赖数据发生变化时,计算属性会自动更新。
计算属性的核心特点:
- 基于现有数据计算而来
- 具有缓存机制,只有在依赖数据变化时才会重新计算
- 声明式编程,关注"是什么"而非"如何做"
1.2 在JavaScript中实现计算属性
在原生JavaScript中,我们可以使用getter和setter方法来实现类似计算属性的功能:
const person = {
firstName: '张',
lastName: '三',
// 计算属性:fullName
get fullName() {
return this.firstName + this.lastName;
},
// 带setter的计算属性
set fullName(value) {
const parts = value.split(' ');
this.firstName = parts[0];
this.lastName = parts[1];
}
};
console.log(person.fullName); // 输出:张三
// 使用setter修改计算属性
person.fullName = '李 四';
console.log(person.firstName); // 输出:李
console.log(person.lastName); // 输出:四
通过使用Object.defineProperty
,我们还可以动态地添加计算属性:
const product = {
price: 100,
quantity: 2
};
// 动态添加计算属性
Object.defineProperty(product, 'total', {
get() {
return this.price * this.quantity;
},
enumerable: true
});
console.log(product.total); // 输出:200
product.price = 150;
console.log(product.total); // 输出:300
1.3 Vue.js中的计算属性
在Vue.js中,计算属性是一个非常核心的功能,它让模板更加简洁和声明式:
const app = Vue.createApp({
data() {
return {
items: [1, 2, 3, 4, 5],
showEven: true
}
},
computed: {
// 计算属性:filteredItems
filteredItems() {
return this.showEven
? this.items.filter(item => item % 2 === 0)
: this.items;
}
}
});
对应的HTML模板:
<div id="app">
<button @click="showEven = !showEven">
显示{
{ showEven ? '偶数' : '全部' }}
</button>
<ul>
<li v-for="item in filteredItems" :key="item">{
{ item }}</li>
</ul>
</div>
1.4 计算属性的缓存机制
计算属性的一个重要特性是缓存。Vue会缓存计算属性的值,只有当依赖的响应式数据发生变化时,才会重新计算。这在性能上比方法调用更加高效。
const app = Vue.createApp({
data() {
return {
message: 'Hello'
}
},
computed: {
// 计算属性会被缓存
expensiveComputation() {
console.log('计算属性被执行');
return this.message.split('').reverse().join('');
}
},
methods: {
// 方法每次调用都会执行
expensiveMethod() {
console.log('方法被执行');
return this.message.split('').reverse().join('');
}
}
});
HTML模板:
<div id="app">
<p>计算属性: {
{ expensiveComputation }}</p>
<p>计算属性再次访问: {
{ expensiveComputation }}</p>
<p>方法: {
{ expensiveMethod() }}</p>
<p>方法再次调用: {
{ expensiveMethod() }}</p>
</div>
控制台输出:
计算属性被执行 // 只输出一次
方法被执行
方法被执行 // 输出两次
1.5 计算属性的getter和setter
Vue中的计算属性默认只有getter,但我们也可以提供setter:
const app = Vue.createApp({
data() {
return {
firstName: '张',
lastName: '三'
}
},
computed: {
fullName: {
// getter
get() {
return this.firstName + this.lastName;
},
// setter
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[1] || '';
}
}
}
});
使用示例:
// 在组件方法或生命周期钩子中
this.fullName = '李 四'; // 会调用setter
console.log(this.firstName); // 输出:李
console.log(this.lastName); // 输出:四
1.6 在原生JavaScript中实现类似Vue计算属性的缓存功能
我们可以通过闭包和依赖追踪实现类似的功能:
function computed(getter) {
let value;
let dirty = true;
return function() {
if (dirty) {
value = getter();
dirty = false;
}
return value;
}
}
const data = {
a: 1,
b: 2,
// 模拟Vue的响应式系统
set a(val) {
this._a = val;
sum.dirty = true; // 标记计算属性需要重新计算
},
get a() {
return this._a;
}
};
// 初始化
data._a = 1;
// 创建计算属性
const sum = computed(() => data.a + data.b);
sum.dirty = true;
console.log(sum()); // 输出:3
console.log(sum()); // 使用缓存,输出:3
data.a = 2; // 修改依赖,使计算属性失效
console.log(sum()); // 重新计算,输出:4
1.7 计算属性的最佳实践和应用场景
计算属性适用于以下场景:
- 格式化或转换数据:如名称格式化、日期格式化等
const app = Vue.createApp({
data() {
return {
birthday: new Date('1990-01-01')
}
},
computed: {
formattedBirthday() {
const options = {
year: 'numeric', month: 'long', day: 'numeric' };
return this.birthday.toLocaleDateString('zh-CN', options);
}
}
});
- 过滤或排序数据:
const app = Vue.createApp({
data() {
return {
students: [
{
name: '张三', score: 85 },
{
name: '李四', score: 92 },
{
name: '王五', score: 78 }
]
}
},
computed: {
sortedStudents() {
return [...this.students].sort((a, b) => b.score - a.score);
},
passedStudents() {
return this.students.filter(student => student.score >= 80);
}
}
});
- 复杂的条件逻辑:
const app = Vue.createApp({
data() {
return {
user: {
role: 'admin',
permissions: ['read', 'write'],
active: true
}
}
},
computed: {
canEdit() {
return (
this.user.active &&
(this.user.role === 'admin' ||
this.user.permissions.includes('write'))
);
}
}
});
- 组合多个数据源:
const app = Vue.createApp({
data() {
return {
basePrice: 100,
taxRate: 0.1,
discount: 15
}
},
computed: {
finalPrice() {
const taxAmount = this.basePrice * this.taxRate;
const discountAmount = this.basePrice * (this.discount / 100);
return this.basePrice + taxAmount - discountAmount;
}
}
});
计算属性的最佳实践:
- 保持计算属性的纯函数特性,不要有副作用
- 避免在计算属性中进行异步操作或修改DOM
- 计算属性应该是确定性的,相同的输入总是产生相同的输出
- 避免过度依赖,一个计算属性不应依赖过多的数据源
- 对于需要缓存的复杂计算,使用计算属性;对于一次性计算,使用方法
2. 方法(Methods)
2.1 基本概念
方法(Methods)是对象或类中定义的函数,用于执行特定的操作或计算。在JavaScript中,方法是对象的属性,其值为一个函数。方法的主要特点:
- 每次调用都会执行
- 可以接受参数
- 可以有副作用(如修改数据、DOM操作等)
- 可以包含异步操作
2.2 普通JavaScript方法
在原生JavaScript中,方法可以通过多种方式定义:
// 对象字面量中定义方法
const calculator = {
add(a, b) {
return a + b;
},
subtract(a, b) {
return a - b;
},
multiply(a, b) {
return a * b;
}
};
console.log(calculator.add(5, 3)); // 输出:8
console.log(calculator.subtract(5, 3)); // 输出:2
// 类中定义方法
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`你好,我是${
this.name},今年${
this.age}岁`);
}
// 静态方法(属于类而非实例)
static createAnonymous() {
return new Person('匿名', 0);
}
}
const person1 = new Person('张三', 25);
person1.sayHello(); // 输出:你好,我是张三,今年25岁
const anonymous = Person.createAnonymous();
anonymous.sayHello(); // 输出:你好,我是匿名,今年0岁
// 使用原型添加方法
Person.prototype.getYearOfBirth = function() {
const currentYear = new Date().getFullYear();
return currentYear - this.age;
};
console.log(person1.getYearOfBirth()); // 输出:当前年份减25
2.3 Vue.js中的方法
在Vue.js中,方法定义在组件的methods
选项中:
const app = Vue.createApp({
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
reset() {
this.count = 0;
},
addAmount(amount) {
this.count += amount;
}
}
});
对应的HTML模板:
<div id="app">
<p>计数: {
{ count }}</p>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
<button @click="reset">重置</button>
<button @click="addAmount(5)">+5</button>
</div>
2.4 方法与计算属性的区别
方法和计算属性都可以基于现有数据计算派生值,但它们有几个关键区别:
-
缓存机制:
- 计算属性会缓存结果,只有在依赖项变化时才重新计算
- 方法每次调用都会执行
-
调用方式:
- 计算属性像属性一样访问:
{ { computedValue }}
- 方法需要显式调用:
{ { method() }}
- 计算属性像属性一样访问:
-
适用场景:
- 计算属性适合派生数据值
- 方法适合执行操作或有副作用的逻辑
示例比较:
const app = Vue.createApp({
data