JavaScript中的计算属性、方法和侦听器详解

前言

在现代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 计算属性的最佳实践和应用场景

计算属性适用于以下场景:

  1. 格式化或转换数据:如名称格式化、日期格式化等
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);
    }
  }
});
  1. 过滤或排序数据
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);
    }
  }
});
  1. 复杂的条件逻辑
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'))
      );
    }
  }
});
  1. 组合多个数据源
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 方法与计算属性的区别

方法和计算属性都可以基于现有数据计算派生值,但它们有几个关键区别:

  1. 缓存机制

    • 计算属性会缓存结果,只有在依赖项变化时才重新计算
    • 方法每次调用都会执行
  2. 调用方式

    • 计算属性像属性一样访问:{ { computedValue }}
    • 方法需要显式调用:{ { method() }}
  3. 适用场景

    • 计算属性适合派生数据值
    • 方法适合执行操作或有副作用的逻辑

示例比较:

const app = Vue.createApp({
   
  data
<think>嗯,我现在需要解决用户关于Vue3中可写计算属性的问题。首先,用户想知道实现方法、使用场景以及相关的示例。我记得在Vue中,计算属性默认是只读的,基于它们的依赖进行缓存。但用户提到的是“可写”计算属性,也就是可以修改的计算属性,这在Vue3中可能存在不同的实现方式。 首先,我应该回顾一下Vue2Vue3中计算属性的基本概念。在Vue2中,计算属性通过`computed`定义,只有getter函数,所以是只读的。如果需要可写,必须显式地提供gettersetter。例如: ```javascript computed: { fullName: { get() { ... }, set(newValue) { ... } } } ``` 这样,当对`fullName`赋值时,setter会被调用。Vue3的组合式API中,使用`computed`函数时,同样可以通过传递一个包含getset的对象来创建可写的计算属性。例如: ```javascript const count = ref(1) const plusOne = computed({ get: () => count.value + 1, set: val => { count.value = val - 1 } }) ``` 这样,当设置`plusOne.value = 5`时,实际上会更新`count.value`为4。这可能就是用户所说的可写计算属性。 接下来,需要确认Vue3中是否还有其他方式实现可写计算属性,或者是否有新的特性。比如,Vue3的响应式系统基于Proxy,而Vue2基于Object.defineProperty。不过,计算属性的实现原理可能类似,都是通过gettersetter来拦截读取设置操作。 然后,使用场景方面,可写计算属性适用于需要双向绑定的情况,例如表单输入,当用户修改一个组合值(如全名)时,需要拆分成名字姓氏并更新相应的数据。或者当某个计算属性需要被外部修改,同时触发相关依赖的更新。 还要考虑用户可能遇到的问题,比如为什么需要显式地设置setter,或者如何处理异步操作。此外,需要确保示例代码的正确性,比如使用Vue3的setup语法,或者组合式API中的写法。 还需要参考用户提供的引用内容。引用[4]提到,计算属性如果被修改,必须写set函数,并且set要引起依赖数据的改变。这与Vue3中的实现方式一致。引用[5]提到Watcherlazy选项,可能涉及到计算属性的惰性求值,但具体在Vue3中的实现可能有所不同。 最后,应该总结实现方法、使用场景,并提供示例代码。同时,生成相关问题,帮助用户进一步理解,比如与Vue2的区别,可写计算属性的原理,或者与侦听器的对比。</think>### Vue3可写计算属性详解 #### 一、实现方法 在Vue3中,可写计算属性通过组合式API的$computed()$函数实现,需显式定义`get``set`方法: ```javascript import { ref, computed } from 'vue' const firstName = ref('张') const lastName = ref('三') // 可写计算属性 const fullName = computed({ get: () => `${firstName.value} ${lastName.value}`, set: (newValue) => { const [first, last] = newValue.split(' ') firstName.value = first || '' lastName.value = last || '' } }) ``` #### 二、核心原理 1. **响应式基础**:基于Proxy实现数据劫持[^3] 2. **依赖追踪**:通过`get`方法自动收集依赖项(如firstName/lastName) 3. **更新触发**:设置值时,`set`方法修改原始数据源触发重新计算[^5] #### 三、使用场景 1. **表单双向绑定**:复合字段拆分存储 ```javascript // 地址拆分示例 const address = computed({ get: () => `${province.value} ${city.value}`, set: (val) => [province.value, city.value] = val.split(' ') }) ``` 2. **数据格式转换**:输入输出格式转换 ```javascript // 价格输入(分<->元) const price = computed({ get: () => (priceCents.value / 100).toFixed(2), set: (val) => priceCents.value = Math.round(val * 100) }) ``` 3. **状态联动更新**:多状态同步修改 ```javascript // 开关组控制 const masterSwitch = computed({ get: () => switches.every(s => s.value), set: (val) => switches.forEach(s => s.value = val) }) ``` #### 四、注意事项 1. **显式依赖**:`set`必须直接修改依赖的ref/reactive对象 2. **避免循环更新**:设置值时需确保不会触发无限递归 3. **性能优化**:复杂计算建议结合`watch`使用[^4] $$ \text{响应式更新公式}:\Delta UI = f_{computed}(get(), set(\Delta)) $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值