Maison >interface Web >js tutoriel >Quel est le principe de mise en œuvre du calcul dans Vue ?

Quel est le principe de mise en œuvre du calcul dans Vue ?

不言
不言original
2018-09-14 16:10:063879parcourir
Le contenu de cet article concerne le principe d'implémentation du calcul dans Vue ? Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

Bien que la pile technologique actuelle ait été transférée de Vue vers React, l'expérience réelle de développement de plusieurs projets à l'aide de Vue est toujours très agréable. La documentation de Vue est claire et standardisée, la conception de l'API est simple et efficace et elle est conviviale. aux développeurs front-end. La mise en route est rapide, et je pense même personnellement qu'utiliser Vue dans de nombreux scénarios est plus efficace que le développement de React. J'ai déjà étudié le code source de Vue par intermittence, mais je ne l'ai jamais résumé, donc je l'ai fait. Je vais faire ici un résumé technique et approfondir ma compréhension de Vue. Ce que je vais donc écrire aujourd'hui, c'est le principe de mise en œuvre de computed, l'une des API les plus couramment utilisées dans Vue.

Introduction de base

Pas grand chose à dire, un exemple de base est le suivant :

<div>
    <p>{{fullName}}</p>
</div>
new Vue({
    data: {
        firstName: 'Xiao',
        lastName: 'Ming'
    },
    computed: {
        fullName: function () {
            return this.firstName + ' ' + this.lastName
        }
    }
})

Dans Vue nous n'avons pas besoin de calculer directement dans le template{{this.firstName + ' ' + this.lastName}}, car mettre trop de logique déclarative dans le modèle rendra le modèle lui-même en surpoids, surtout lorsqu'un grand nombre d'expressions logiques complexes sont utilisées pour traiter les données dans la page, cela aura un grand impact sur la maintenabilité du page. Et computed a été initialement conçu pour résoudre de tels problèmes.

Comparaison de l'écouteurwatch

Bien sûr, souvent lorsque nous utilisons computed, nous le comparons souvent avec une autre API dans Vue, qui est l'écouteur watch, car dans un certain Ils sont cohérents à certains égards et sont basés sur le mécanisme de suivi des dépendances de Vue. Lorsqu'une certaine donnée de dépendance change, toutes les données ou fonctions associées qui dépendent de ces données changeront automatiquement ou seront appelées.

Bien que les propriétés calculées soient plus appropriées dans la plupart des cas, un écouteur personnalisé est parfois nécessaire. C'est pourquoi Vue propose un moyen plus général de répondre aux changements de données via l'option watch. Cette approche est particulièrement utile lorsque vous devez effectuer des opérations asynchrones ou coûteuses lorsque les données changent.

D'après l'explication de watch dans la documentation officielle de Vue, nous pouvons comprendre que l'utilisation de l'option watch nous permet d'effectuer des opérations asynchrones (accès à une API) ou des opérations hautes performances, limiter la fréquence à laquelle nous effectuons le opération, et avant d'obtenir le résultat final, nous définissons des états intermédiaires, ce que les propriétés calculées ne peuvent pas faire.

Ce qui suit résume également quelques différences supplémentaires entre computed et watch :

  1. computed consiste à calculer un nouvel attribut, et montez l'attribut sur la machine virtuelle (instance Vue), et watch consiste à surveiller les données qui existent déjà et qui ont été montées sur vm, donc l'utilisation de watch peut également surveiller l'attribut calculé de computed Change ( d'autres incluent data, props)

  2. computed est essentiellement un observateur évalué paresseusement avec possibilité de cache Seulement lorsque la dépendance change, la première La nouvelle valeur ne sera pas calculée. jusqu'à ce que l'attribut computed soit accédé plusieurs fois, et watch appellera la fonction d'exécution

  3. lorsque les données changent, computed Une donnée est appliquée à. être affecté par plusieurs données, et watch est appliqué à une donnée pour affecter plusieurs données

Ci-dessus, nous comprenons certaines des différences entre computed et watch La différence entre les scénarios d'utilisation, bien sûr, parfois les deux ne sont pas aussi clairs et stricts. En fin de compte, il est quand même nécessaire d'analyser l'activité spécifique.

Analyse des principes

Revenons au sujet de l'articlecomputed Afin de mieux comprendre le mécanisme interne des propriétés calculées, explorons-le étape par étape. le code source de Vue. Le principe d'implémentation.

Avant d'analyser le computed code source, nous devons d'abord avoir une compréhension de base du système réactif de Vue. Vue l'appelle un système réactif non intrusif. Le modèle de données n'est qu'un objet JavaScript ordinaire. eux, la vue est mise à jour automatiquement.

Quel est le principe de mise en œuvre du calcul dans Vue ?

Lorsque vous transmettez un objet JavaScript normal à l'option data d'une instance de Vue, Vue parcourra toutes les propriétés de cet objet et utilisera Object.defineProperty Convertissez toutes ces propriétés en getter/setter. Ces getter/setter sont invisibles pour l'utilisateur, mais en interne, elles permettent à Vue de suivre les dépendances et de notifier les changements lorsque les propriétés sont accédées et modifiées. Chaque instance de composant a un objet d'instance watcher correspondant. enregistrera les propriétés en tant que dépendances lors du rendu des composants. Plus tard, lorsque le setter de la dépendance sera appelé, il demandera à watcher de recalculer, provoquant la mise à jour de ses composants associés.

Le système de réponse Vue comporte trois points essentiels : observe, watcher, dep :

  1. observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持;

  2. dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象;

  3. watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。

对响应式系统有一个初步了解后,我们再来分析计算属性。
首先我们找到计算属性的初始化是在 src/core/instance/state.js 文件中的 initState 函数中完成的

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  // computed初始化
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

调用了 initComputed 函数(其前后也分别初始化了 initDatainitWatch )并传入两个参数 vm 实例和 opt.computed 开发者定义的 computed 选项,转到 initComputed 函数:

const computedWatcherOptions = { computed: true }

function initComputed (vm: Component, computed: Object) {
  // $flow-disable-line
  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. 获取计算属性的定义 userDefgetter 求值函数

    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    定义一个计算属性有两种写法,一种是直接跟一个函数,另一种是添加 setget 方法的对象形式,所以这里首先获取计算属性的定义 userDef,再根据 userDef 的类型获取相应的 getter 求值函数。

  2. 计算属性的观察者 watcher 和消息订阅器 dep

    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
    )

    这里的 watchers 也就是 vm._computedWatchers 对象的引用,存放了每个计算属性的观察者 watcher 实例(注:后文中提到的“计算属性的观察者”、“订阅者”和 watcher 均指代同一个意思但注意和 Watcher 构造函数区分),Watcher 构造函数在实例化时传入了 4 个参数:vm 实例、getter 求值函数、noop 空函数、computedWatcherOptions 常量对象(在这里提供给 Watcher 一个标识 {computed:true} 项,表明这是一个计算属性而不是非计算属性的观察者,我们来到 Watcher 构造函数的定义:

    class Watcher {
      constructor (
        vm: Component,
        expOrFn: string | Function,
        cb: Function,
        options?: ?Object,
        isRenderWatcher?: boolean
      ) {
        if (options) {
          this.computed = !!options.computed
        } 
    
        if (this.computed) {
          this.value = undefined
          this.dep = new Dep()
        } else {
          this.value = this.get()
        }
      }
      
      get () {
        pushTarget(this)
        let value
        const vm = this.vm
        try {
          value = this.getter.call(vm, vm)
        } catch (e) {
          
        } finally {
          popTarget()
        }
        return value
      }
      
      update () {
        if (this.computed) {
          if (this.dep.subs.length === 0) {
            this.dirty = true
          } else {
            this.getAndInvoke(() => {
              this.dep.notify()
            })
          }
        } else if (this.sync) {
          this.run()
        } else {
          queueWatcher(this)
        }
      }
    
      evaluate () {
        if (this.dirty) {
          this.value = this.get()
          this.dirty = false
        }
        return this.value
      }
    
      depend () {
        if (this.dep && Dep.target) {
          this.dep.depend()
        }
      }
    }

    为了简洁突出重点,这里我手动去掉了我们暂时不需要关心的代码片段。
    观察 Watcherconstructor ,结合刚才讲到的 new Watcher 传入的第四个参数 {computed:true} 知道,对于计算属性而言 watcher 会执行 if 条件成立的代码 this.dep = new Dep(),而 dep 也就是创建了该属性的消息订阅器。

    export default class Dep {
      static target: ?Watcher;
      subs: Array<watcher>;
    
      constructor () {
        this.id = uid++
        this.subs = []
      }
    
      addSub (sub: Watcher) {
        this.subs.push(sub)
      }
    
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this)
        }
      }
    
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i <p><code>Dep</code> 同样精简了部分代码,我们观察 <code>Watcher</code> 和 <code>Dep</code> 的关系,用一句话总结</p>
    <blockquote>
    <code>watcher</code> 中实例化了 <code>dep</code> 并向 <code>dep.subs</code> 中添加了订阅者,<code>dep</code> 通过 <code>notify</code> 遍历了 <code>dep.subs</code> 通知每个 <code>watcher</code> 更新。</blockquote></watcher>
  3. defineComputed 定义计算属性

    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)
      }
    }

    因为 computed 属性是直接挂载到实例对象中的,所以在定义之前需要判断对象中是否已经存在重名的属性,defineComputed 传入了三个参数:vm 实例、计算属性的 key 以及 userDef 计算属性的定义(对象或函数)。
    然后继续找到 defineComputed 定义处:

    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)
    }

    在这段代码的最后调用了原生 Object.defineProperty 方法,其中传入的第三个参数是属性描述符sharedPropertyDefinition,初始化为:

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

    随后根据 Object.defineProperty 前面的代码可以看到 sharedPropertyDefinitionget/set 方法在经过 userDef 和  shouldCache 等多重判断后被重写,当非服务端渲染时,sharedPropertyDefinitionget 函数也就是 createComputedGetter(key) 的结果,我们找到 createComputedGetter 函数调用结果并最终改写  sharedPropertyDefinition 大致呈现如下:

    sharedPropertyDefinition = {
        enumerable: true,
        configurable: true,
        get: function computedGetter () {
            const watcher = this._computedWatchers && this._computedWatchers[key]
            if (watcher) {
                watcher.depend()
                return watcher.evaluate()
            }
        },
        set: userDef.set || noop
    }

    当计算属性被调用时便会执行 get 访问函数,从而关联上观察者对象 watcher 然后执行 wather.depend() 收集依赖和 watcher.evaluate() 计算求值。

Après avoir analysé toutes les étapes, résumons l'ensemble du processus :

  1. Lorsque le composant est initialisé, computed et data créeront le leur Le système de réponse, Observer parcourt chaque paramètre d'attribut dans data get/set interception de données

  2. initialisation computed appellera la initComputed fonction

    1. Enregistrez une instance watcher et instanciez un abonné au message Dep à l'intérieur pour les dépendances de collection ultérieures (telles que watcher de la fonction de rendu ou autre watcher qui observe les changements dans la propriété calculée) )

    2. déclenchera la fonction d'accès Object.defineProperty de son get

    3. pour appeler sa propre méthode watcher.depend() Ajouter un autre attribue à depsubswatcher

    4. de l'abonné au message
    5. et appelle la méthode

      de watcher (qui à son tour appelle la méthode evaluate de watcher ) Devenez abonné des autres abonnés au message get. Attribuez d'abord watcher à watcher, puis exécutez la fonction d'évaluation Dep.target lors de l'accès aux propriétés dans la fonction d'évaluation (comme depuis getter , ou autre data), leur fonction accesseur props sera également déclenchée pour ajouter le computed de la propriété calculée à l'abonné au message get du watcher de la propriété dans la fonction d'évaluation, lorsque ces opérations seront terminées. , fermez enfin watcher et attribuez-le à dep et renvoyez le résultat de la fonction d'évaluation. Dep.targetnull

    Lorsqu'un certain attribut change, déclenchez la fonction d'interception
  3. , puis appelez la méthode

    de son propre abonné au message set pour parcourir le Enregistrez le tableau dep de tous les abonnés notify et appelez la méthode dep une par une pour terminer la mise à jour de la réponse. wathcersubswatcherupdateRecommandations associées :

javascript - Principe d'implémentation du ticket brusher

contrôleur thinkphp Principe de implémentation de l'étape display()

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn