亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

手寫(xiě)實(shí)現(xiàn)Vue計(jì)算屬性

 更新時(shí)間:2022年08月17日 09:18:06   作者:夏日  
這篇文章主要介紹了手寫(xiě)實(shí)現(xiàn)Vue計(jì)算屬性,本文從一個(gè)簡(jiǎn)單的計(jì)算屬性例子開(kāi)始,一步步實(shí)現(xiàn)了計(jì)算屬性。并且針對(duì)這個(gè)例子,詳細(xì)分析了頁(yè)面渲染時(shí)的整個(gè)代碼執(zhí)行邏輯,需要的小伙伴可以參考一下

前言

官網(wǎng)對(duì)計(jì)算屬性的介紹在這里:傳送門(mén)

計(jì)算屬性是Vue中很常用的一個(gè)配置項(xiàng),我們先用一個(gè)簡(jiǎn)單的例子來(lái)講解它的功能:

<div id="app">
  {{fullName}}
</div>
<script>
  const vm = new Vue({
    data () {
      return {
        firstName: 'Foo',
        lastName: 'Bar'
      };
    },
    computed: {
      fullName () {
        return this.firstName + this.lastName;
      }
    }
  });
</script>

在例子中,計(jì)算屬性中定義的fullName函數(shù),會(huì)最終處理為vm.fullNamegetter函數(shù)。所以vm.fullName = this.firstName + this.lastName = 'FooBar'

計(jì)算屬性有以下特點(diǎn):

  • 計(jì)算屬性可以簡(jiǎn)化模板中的表達(dá)式,用戶(hù)可以書(shū)寫(xiě)更加簡(jiǎn)潔易讀的template
  • Vue為計(jì)算屬性提供了緩存功能,只有當(dāng)它依賴(lài)的屬性(例子中的this.firstNamethis.lastName)發(fā)生變化時(shí),才會(huì)重新執(zhí)行屬性對(duì)應(yīng)的getter函數(shù),否則會(huì)將之前計(jì)算好的值返回。

正是由于computed的緩存功能,使得用戶(hù)在使用時(shí)會(huì)優(yōu)先考慮它,而不是使用watch、methods屬性。

在了解了計(jì)算屬性的用法后,我們通過(guò)代碼來(lái)一步步實(shí)現(xiàn)computed,并讓它完成上邊的例子。

初始化計(jì)算屬性

初始化computed的邏輯會(huì)書(shū)寫(xiě)在scr/state.js中:

function initState (vm) {
  const options = vm.$options;
  // some code ...
  if (options.computed) {
    initComputed(vm);
  }
}

initComputed中,可以通過(guò)vm.$options.computed拿到所有定義的計(jì)算屬性。對(duì)于每個(gè)計(jì)算屬性,需要對(duì)其做如下處理:

  • 實(shí)例化計(jì)算屬性對(duì)應(yīng)的Watcher
  • 取到計(jì)算屬性的key,通過(guò)Object.definePropertyvm實(shí)例添加key屬性,并設(shè)置它的get/set方法
function initComputed (vm) {
  const { computed } = vm.$options;
  // 將計(jì)算屬性watcher存儲(chǔ)到vm._computedWatchers屬性中,之后方法直接通過(guò)實(shí)例vm來(lái)獲取
  const watchers = vm._computedWatchers = {};
  for (const key in computed) {
    if (computed.hasOwnProperty(key)) {
      const userDef = computed[key];
      // 計(jì)算屬性key的值有可能是對(duì)象,在對(duì)象中會(huì)設(shè)置它的get set 方法
      const getter = typeof userDef === 'function' ? userDef : userDef.get;
      // 為每一個(gè)計(jì)算屬性創(chuàng)建一個(gè)watcher
      watchers[key] = new Watcher(vm, getter, () => {}, { lazy: true });
      // 將計(jì)算屬性的key添加到實(shí)例vm上
      defineComputed(vm, key, userDef);
    }
  }
}

計(jì)算屬性也可以傳入set方法,用于設(shè)置值時(shí)處理的邏輯,此時(shí)計(jì)算屬性的value是一個(gè)對(duì)象:

new Vue({
    // ...
    computed: {
      fullName: {
        // getter
        get: function () {
          return this.firstName + ' ' + this.lastName
        },
        // setter
        set: function (newValue) {
          var names = newValue.split(' ')
          this.firstName = names[0]
          this.lastName = names[names.length - 1]
        }
      }
    }
  }
  //...  
)

defineComputed函數(shù)中,我們會(huì)根據(jù)計(jì)算屬性的類(lèi)型來(lái)確定是否為其定義set方法:

const sharedPropertyDefinition = {
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop
};

function defineComputed (target, key, userDef) {
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = createComputedGetter(key);
  } else {
    sharedPropertyDefinition.get = createComputedGetter(key);
    // 如果是對(duì)象,用戶(hù)會(huì)傳入set方法
    sharedPropertyDefinition.set = userDef.set;
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

// 創(chuàng)建Object.defineProperty的get函數(shù)
function createComputedGetter (key) {
  return function () {
    // 通過(guò)之前保存的_computedWatchers來(lái)取到對(duì)應(yīng)的計(jì)算屬性watcher
    const watcher = this._computedWatchers[key];
    if (watcher.dirty) {
      // 只有在dirty為true的時(shí)候才會(huì)重新執(zhí)行計(jì)算屬性
      watcher.evaluate();
      if (Dep.target) {
        // 此時(shí),如果棧中有渲染watcher,會(huì)為當(dāng)前計(jì)算屬性watcher中收集的所有dep再收集渲染watcher
        // 在watcher收集的dep對(duì)應(yīng)的屬性(this.firstName,this.lastName)更新后,通知視圖更新,從而更新頁(yè)面中的計(jì)算屬性
        watcher.depend();
      }
    }
    return watcher.value;
  };
}

在對(duì)計(jì)算屬性取值時(shí),首先會(huì)調(diào)用它在vm.fullName上定義的get方法,也就是上邊的createComputedGetter執(zhí)行后返回的函數(shù)。在函數(shù)內(nèi)部,只有當(dāng)watcher.dirtytrue 時(shí),才會(huì)執(zhí)行watcher.evaluate

下面我們先看下Watcher中關(guān)于計(jì)算屬性的代碼:

import { popTarget, pushTarget } from './dep';
import { nextTick } from '../shared/next-tick';
import { traverse } from './traverse';
let id = 0;
class Watcher {
  constructor (vm, exprOrFn, cb, options = {}) {
    // some code ...
    // 設(shè)置dirty的初始值為false
    this.lazy = options.lazy;
    this.dirty = this.lazy;
    if (typeof exprOrFn === 'function') {
      this.getter = this.exprOrFn;
    }
    // some code ...
    // 初始化時(shí)計(jì)算屬性的getter不會(huì)執(zhí)行,用到的時(shí)候才會(huì)執(zhí)行
    this.value = this.lazy ? undefined : this.get();
  }
  // 執(zhí)行傳入的getter函數(shù)進(jìn)行求值,將其賦值給this.value
  // 求值完畢后,將dirty置為false,下次將不會(huì)再重新執(zhí)行求值函數(shù)
  evaluate () {
    this.value = this.get();
    this.dirty = false;
  }
  // 為watcher中的dep,再收集渲染watcher
  depend () {
    this.deps.forEach(dep => dep.depend());
  }
  get () {
    pushTarget(this);
    const value = this.getter.call(this.vm);
    if (this.deep) {
      traverse(value);
    }
    popTarget();
    return value;
  }
  update () {
    if (this.lazy) { // 依賴(lài)的值更新后,只需要將this.dirty設(shè)置為true
      // 之后獲取計(jì)算屬性的值時(shí)會(huì)再次執(zhí)行evaluate來(lái)執(zhí)行this.get()方法
      this.dirty = true;
    } else {
      queueWatcher(this);
    }
  }

  // some code ...
}

watcher.evaluate中的邏輯便是執(zhí)行我們?cè)诙x計(jì)算屬性時(shí)傳入的回調(diào)函數(shù)(getter),將其返回值賦值給watcher.value,并在取值完畢后,將watcher.dirty置為false 。這樣再次取值時(shí)便直接將watcher.value返回即可,而不用再執(zhí)行回調(diào)函數(shù)進(jìn)行重復(fù)計(jì)算。

當(dāng)計(jì)算屬性的依賴(lài)屬性(this.firstNamethis.lastName)發(fā)生變化后,我們要更新視圖,讓計(jì)算屬性重新執(zhí)行getter函數(shù)獲取到最新值。所以代碼中判斷Dep.target(此時(shí)為渲染watcher) 是否存在,如果存在會(huì)為依賴(lài)屬性收集對(duì)應(yīng)的渲染watcher。這樣在依賴(lài)屬性更新時(shí),便會(huì)通過(guò)渲染watcher來(lái)通知視圖更新,獲取到最新的計(jì)算屬性。

依賴(lài)屬性更新

以文章開(kāi)始時(shí)的demo為例,首次執(zhí)行時(shí)的邏輯如下圖:

用文字來(lái)描述:

  • 初始化計(jì)算屬性,為vm添加fullName屬性,并設(shè)置其get方法
  • 首次渲染頁(yè)面,stack中存儲(chǔ)了渲染watcher。由于頁(yè)面中用到了fullName屬性,所以在渲染時(shí)會(huì)觸發(fā)fullNameget方法
  • fullName執(zhí)行get會(huì)通過(guò)依賴(lài)屬性firstNamelastName來(lái)求值,computed watcher會(huì)進(jìn)入stack
  • 此時(shí)又會(huì)觸發(fā)firstNamelastNameget方法,收集computed watcher
  • fullName求值方法執(zhí)行完成,computed watcher出棧,Dep.target為渲染watcher
  • 此時(shí)為fullName對(duì)應(yīng)的computed watcher中的dep(也就是firstNamelastName對(duì)應(yīng)的dep)收集渲染watcher
  • 完成fullName的取值過(guò)程,此時(shí)firstNamelastNamedep中分別收集的watcher[computed watcher, render watcher]

假設(shè)我們更新了依賴(lài),會(huì)通知收集的watcher進(jìn)行更新:

vm.firstName = 'F'

firstName屬性更新后,會(huì)觸發(fā)其對(duì)應(yīng)的set方法,執(zhí)行dep中收集的computed watcherrender watcher

  • computed watcher: 將this.dirty設(shè)置為true,fullName之后取值時(shí)需要重新執(zhí)行用戶(hù)傳入的getter函數(shù)
  • render watcher: 通知視圖更新,獲取fullName的最新值

到這里我們實(shí)現(xiàn)的computed屬性便能正常工作了!

總結(jié)

本文從一個(gè)簡(jiǎn)單的計(jì)算屬性例子開(kāi)始,一步步實(shí)現(xiàn)了計(jì)算屬性。并且針對(duì)這個(gè)例子,詳細(xì)分析了頁(yè)面渲染時(shí)的整個(gè)代碼執(zhí)行邏輯。希望小伙伴們?cè)谧x完本文后,能夠從源碼的角度,分析自己代碼中對(duì)應(yīng)計(jì)算屬性相關(guān)代碼的執(zhí)行流程,體會(huì)一下Vue 的computed屬性到底幫我們做了些什么。

到此這篇關(guān)于手寫(xiě)實(shí)現(xiàn)Vue計(jì)算屬性的文章就介紹到這了,更多相關(guān)Vue計(jì)算屬性?xún)?nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論