欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

<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ù),會最終處理為vm.fullNamegetter函數(shù)。所以vm.fullName = this.firstName + this.lastName = 'FooBar'

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

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

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

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

初始化計(jì)算屬性

初始化computed的邏輯會書寫在scr/state.js中:

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

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

  • 實(shí)例化計(jì)算屬性對應(yīng)的Watcher
  • 取到計(jì)算屬性的key,通過Object.definePropertyvm實(shí)例添加key屬性,并設(shè)置它的get/set方法
function initComputed (vm) {
  const { computed } = vm.$options;
  // 將計(jì)算屬性watcher存儲到vm._computedWatchers屬性中,之后方法直接通過實(shí)例vm來獲取
  const watchers = vm._computedWatchers = {};
  for (const key in computed) {
    if (computed.hasOwnProperty(key)) {
      const userDef = computed[key];
      // 計(jì)算屬性key的值有可能是對象,在對象中會設(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è)對象:

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ù)中,我們會根據(jù)計(jì)算屬性的類型來確定是否為其定義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);
    // 如果是對象,用戶會傳入set方法
    sharedPropertyDefinition.set = userDef.set;
  }
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

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

在對計(jì)算屬性取值時(shí),首先會調(diào)用它在vm.fullName上定義的get方法,也就是上邊的createComputedGetter執(zhí)行后返回的函數(shù)。在函數(shù)內(nèi)部,只有當(dāng)watcher.dirtytrue 時(shí),才會執(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不會執(zhí)行,用到的時(shí)候才會執(zhí)行
    this.value = this.lazy ? undefined : this.get();
  }
  // 執(zhí)行傳入的getter函數(shù)進(jìn)行求值,將其賦值給this.value
  // 求值完畢后,將dirty置為false,下次將不會再重新執(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) { // 依賴的值更新后,只需要將this.dirty設(shè)置為true
      // 之后獲取計(jì)算屬性的值時(shí)會再次執(zhí)行evaluate來執(zhí)行this.get()方法
      this.dirty = true;
    } else {
      queueWatcher(this);
    }
  }

  // some code ...
}

watcher.evaluate中的邏輯便是執(zhí)行我們在定義計(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ì)算屬性的依賴屬性(this.firstNamethis.lastName)發(fā)生變化后,我們要更新視圖,讓計(jì)算屬性重新執(zhí)行getter函數(shù)獲取到最新值。所以代碼中判斷Dep.target(此時(shí)為渲染watcher) 是否存在,如果存在會為依賴屬性收集對應(yīng)的渲染watcher。這樣在依賴屬性更新時(shí),便會通過渲染watcher來通知視圖更新,獲取到最新的計(jì)算屬性。

依賴屬性更新

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

用文字來描述:

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

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

vm.firstName = 'F'

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

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

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

總結(jié)

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

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

相關(guān)文章

  • vue-router子路由的實(shí)現(xiàn)方式

    vue-router子路由的實(shí)現(xiàn)方式

    這篇文章主要介紹了vue-router子路由的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • vue項(xiàng)目實(shí)現(xiàn)圖片懶加載的簡單步驟

    vue項(xiàng)目實(shí)現(xiàn)圖片懶加載的簡單步驟

    懶加載的好處在于減少服務(wù)器的壓力,在網(wǎng)絡(luò)比較慢的情況下,可以提前給這張圖片添加一個(gè)占位圖片,提高用戶的體驗(yàn),這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目實(shí)現(xiàn)圖片懶加載的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • vue項(xiàng)目如何實(shí)現(xiàn)Echarts在label中獲取點(diǎn)擊事件

    vue項(xiàng)目如何實(shí)現(xiàn)Echarts在label中獲取點(diǎn)擊事件

    這篇文章主要介紹了vue項(xiàng)目如何實(shí)現(xiàn)Echarts在label中獲取點(diǎn)擊事件,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能

    富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能

    這篇文章主要介紹了富文本編輯器vue2-editor實(shí)現(xiàn)全屏功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-05-05
  • vue3雙向綁定實(shí)現(xiàn)原理解讀

    vue3雙向綁定實(shí)現(xiàn)原理解讀

    這篇文章主要介紹了vue3雙向綁定實(shí)現(xiàn)原理解讀,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • 拿來就用vue-gird-layout組件封裝示例

    拿來就用vue-gird-layout組件封裝示例

    這篇文章主要介紹了vue-gird-layout組件封裝示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-08-08
  • vue使用monaco?editor漢化右鍵菜單示例

    vue使用monaco?editor漢化右鍵菜單示例

    這篇文章主要為大家介紹了vue使用?monaco?editor?漢化右鍵菜單實(shí)現(xiàn)漢化示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • 不依任何賴第三方,單純用vue實(shí)現(xiàn)Tree 樹形控件的案例

    不依任何賴第三方,單純用vue實(shí)現(xiàn)Tree 樹形控件的案例

    這篇文章主要介紹了不依任何賴第三方,單純用vue實(shí)現(xiàn)Tree 樹形控件的案例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • vue props對象validator自定義函數(shù)實(shí)例

    vue props對象validator自定義函數(shù)實(shí)例

    今天小編就為大家分享一篇vue props對象validator自定義函數(shù)實(shí)例,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • 一篇文章搞懂Vue3中如何使用ref獲取元素節(jié)點(diǎn)

    一篇文章搞懂Vue3中如何使用ref獲取元素節(jié)點(diǎn)

    過去在Vue2中,我們采用ref來獲取標(biāo)簽的信息,用以替代傳統(tǒng) js 中的 DOM 行為,下面這篇文章主要給大家介紹了關(guān)于如何通過一篇文章搞懂Vue3中如何使用ref獲取元素節(jié)點(diǎn)的相關(guān)資料,需要的朋友可以參考下
    2022-11-11

最新評論