vue2.x之计算属性

我们一般是在模板中放置的都是一些基础变量或者一些简单的运算,但是对于一些复杂的表达式是不建议写在模板中的,比如这个变量依赖于别的变量,这时候如果将所有的逻辑都放在模板里面就不利于维护也不利于复用,这时候就可以使用计算属性了。简单地说计算属性就是要用的属性不存在,需要通过已有的属性计算得来的。

什么是计算属性

计算属性,字如其名,首先它是属性,其次有计算的“功能”

⏰ 计算属性就是当其依赖属性的值发生变化时,这个属性的值会自动变化,与之相关的DOM部分也会同步自动更新。

基本用法

计算属性都包含有一个getter和一个setter,计算属性会默认使用 getter 函数,如果你要使用 setter 函数,那么你必须要手动写出 setter 函数

🌰示例

<!--template 模版-->
<template>
  <div class="hello">
    <input type="text" v-model="firstName">
    <br>
    <input type="text" v-model="lastName">
    <br>
    <span>{{ fullName }}</span>
  </div>
</template>

<script>
export default {
  name: 'Vue2LearnHelloWorld',

  props: ['title'],

  data() {
    return {
      firstName: '',
      lastName: '',
    };
  },
  computed: {
    fullName: {
      get: function () {
        console.log('你正在获取fullName的值')
        return this.firstName + '-' + this.lastName
      },
      set: function (value) {
        console.log('你正在改变fullName的值')
        let arr = value.split('-')
        this.firstName = arr[0]
        this.lastName = arr[0]
      }
    }
  },
};
</script>

 

从上面的例子中可以看出,get函数在初次读取时会执行一次。当依赖的数据发生改变的时候就会被再次调用。 计算属性最终也会出现在vm上,直接读取使用就可以。如果计算属性需要被修改,就需要定义一个set函数去响应修改

缓存

还是上面的例子,如果我们在模板中多次使用到fullName,那么会发生什么呢?

🌰示例

<!--template 模版-->
<template>
  <div class="hello">
    <input type="text" v-model="firstName">
    <br>
    <input type="text" v-model="lastName">
    <br>
    <h2>计算属性</h2>
    <p>{{ fullName }}</p>
    <p>{{ fullName }}</p>
    <p>{{ fullName }}</p>
    <h2>方法</h2>
    <p>{{ getFullName() }}</p>
    <p>{{ getFullName() }}</p>
    <p>{{ getFullName() }}</p>
  </div>
</template>

<script>
export default {
  name: 'Vue2LearnHelloWorld',

  props: ['title'],

  data() {
    return {
      firstName: '',
      lastName: '',
    };
  },
  computed: {
    fullName: {
      get: function () {
        console.log('你正在获取fullName的值')
        return this.firstName + '-' + this.lastName
      },
      set: function (value) {
        console.log('你正在改变fullName的值')
        let arr = value.split('-')
        this.firstName = arr[0]
        this.lastName = arr[0]
      }
    }
  },

  methods: {
    getFullName() {
      console.log('你在使用method得到fullName')
      return this.firstName + '-' + this.lastName
    },
  },
};
</script>

 

从上面的例子可以看出,不论你使用了多少次fullName,计算属性都只会执行一次,而methods里定义的方法是会执行多次的。

⏰ 这样就说明计算属性是会有缓存的,它是基于它们的响应式依赖进行缓存的。如果所依赖的属性没有改变,那么就不会触发计算属性中get函数的调用。

原理

从我们对计算属性的get和set方法就能看出这个和Object.defineProperty()很像,对,确实是这样,计算属性的底层原理就是借助了Object.defineProperty()方法提供的getter和setter。下面我们来看一下源码

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  // computed properties are just getters during SSR
  const isSSR = isServerRendering()

  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if (process.env.NODE_ENV !== 'production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}".`,
        vm
      )
    }

    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

这段代码是用来初始化关于计算属性的配置项的,核心代码就是其中遍历的部分,这块主要做了三件事:

1. 为computed[key]创建watcher实例,默认是懒执行(new Watcher)
2. 代理computed[key]到vm实例,也就是说可以在实例上获取计算属性(defineComputed)
3. 判断computed中的key有没有和data, prop中的属性重复,如果重复的话就打印提示信息

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : userDef
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : userDef.get
      : noop
    sharedPropertyDefinition.set = userDef.set
      ? userDef.set
      : noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

这段代码是代理computed的key到vm,主要核心就是使用到了Object.defineProperty()方法。

简写形式

如果计算属性中不需要用到set函数,那么就可以使用简写形式。

⏰ 完整写法

// 完整写法
computed: {
  fullName: {
    get: function () {
      console.log('你正在获取fullName的值')
      return this.firstName + '-' + this.lastName
    }
  }
}

⏰ 简写形式

// 简写形式
computed: {
  fullName () {
    console.log('你正在获取fullName的值')
    return this.firstName + '-' + this.lastName
  }
}

⚠️ 注意:计算属性其实是属性,在页面上使用只需要用属性值,不要带(),不然就变成方法了。

 

posted on 2024-07-09 20:53  梁飞宇  阅读(33)  评论(0)    收藏  举报