vue中created、watch和computed的执行顺序详解

目录
  • 前言
  • 为什么?
  • 1、关于initComputed
  • 2、关于initWatch
  • 总结

前言

面试题:vuecreatedwatch(immediate: true)computed的执行顺序是啥?

先看个简单的例子:

// main.js
import Vue from "vue";

new Vue({
  el: "#app",
  template: `<div>
    <div>{{computedCount}}</div>
  </div>`,
  data() {
    return {
      count: 1,
    }
  },
  watch: {
    count: {
      handler() {
        console.log('watch');
      },
      immediate: true,
    }
  },
  computed: {
    computedCount() {
      console.log('computed');
      return this.count + 1;
    }
  },
  created() {
    console.log('created');
  },
});

当前例子的执行顺序为:watch --> created --> computed

为什么?

new Vue的实例化过程中,会执行初始化方法this._init,其中有代码:

Vue.prototype._init = function (options) {
    // ...
    initState(vm);
    // ...
    callHook(vm, 'created');
    // ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el);
    }
}

function initState (vm) {
  vm._watchers = [];
  var 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 */);
  }
  if (opts.computed) { initComputed(vm, opts.computed); }
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch);
  }
}

猛一看代码,是不是发现先执行的initComputed(vm, opts.computed),然后执行initWatch(vm, opts.watch),再执行callHook(vm, 'created'),那为啥不是computed --> watch --> created呢?

不要着急,听我娓娓道来。

1、关于initComputed

const computedWatcherOptions = { lazy: 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 (!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') {
        // ...
    }
  }
}

在通过initComputed初始化计算属性的时候,通过遍历的方式去处理当前组件中的computed。首先,在进行计算属性实例化的时候,将{ lazy: true }作为参数传入,并且实例化的Watcher中的getter就是当前例子中的computedCount函数;其次,通过defineComputed(vm, key, userDef)的方式在当前组件实例vm上为key进行userDef的处理。具体为:

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  }
  // ...
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

从以上可以看出,这里通过Object.defineProperty(target, key, sharedPropertyDefinition)的方式,将函数computedGetter作为get函数,只有当对key进行访问的时候,才会触发其内部的逻辑。内部逻辑watcher.evaluate()为:

evaluate () {
    this.value = this.get()
    this.dirty = false
}

get中有主要逻辑:

value = this.getter.call(vm, vm)

这里的this.getter就是当前例子中的:

computedCount() {
  console.log('computed');
  return this.count + 1;
}

也就是说,只有当获取computedCount的时候才会触发computed的计算,也就是在进行vm.$mount(vm.$options.el)阶段才会执行到console.log('computed')

2、关于initWatch

function initWatch (vm: Component, watch: Object) {
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
function createWatcher (
  vm: Component,
  expOrFn: string | Function,
  handler: any,
  options?: Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}

在通过initWatch初始化侦听器的时候,如果watch为数组,则遍历执行createWatcher,否则直接执行createWatcher。如果handler是对象或者字符串时,将其进行处理,最终作为参数传入vm.$watch中去,具体为:

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

这里获取到的options中会有immediate: true的键值,同时通过options.user = true设置usertrue,再将其作为参数传入去进行Watcher的实例化。

当前例子中options.immediatetrue,所以会执行cb.call(vm, watcher.value),也就是以vm为主体,立刻执行cb。当前例子中cb就是handler

handler() {
    console.log('watch');
},

这里就解释了当前例子中console.log('watch')是最先执行的。

然后,执行完initComputedinitWatch以后,就会通过callHook(vm, 'created')执行到生命周期中的console.log('created')了。

最后通过vm.$mount(vm.$options.el)进行页面渲染的时候,会先去创建vNode,这时就需要获取到computedCount的值,进而触发其get函数的后续逻辑,最终执行到console.log('computed')

附:为什么vue中的watch在mounted之后执行

首先,在调用mounted的时候,会进入到defineReactive函数,然后调用函数里面的set方法,将mounted中赋的新的值给传递过去,并通过调用dep.notify( )把消息发送给订阅者,继而更新订阅者的列表

后面才开始渲染watch进入Watcher的class

总结

关于vuecreatedwatch的执行顺序相对比较简单,而其中computed是通过Object.defineProperty为当前vm进行定义,再到后续创建vNode阶段才去触发执行其get函数,最终执行到计算属性computed对应的逻辑。

到此这篇关于vue中created、watch和computed执行顺序详解的文章就介绍到这了,更多相关vue created、watch和computed执行顺序内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue计算属性computed的使用方法示例

    本文实例讲述了vue计算属性computed的使用方法.分享给大家供大家参考,具体如下: computed:{ b:function(){ //默认调用get return 值 } } computed:{ b:{ get: set: } } * computed里面可以放置一些业务逻辑代码,一定记得return <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&

  • vue中watch和computed的区别与使用方法

    computed 计算属性说明: computed 是基于响应性依赖来进行缓存的.只有依赖数据发生改变,才会重新进行计算(当触发重新渲染,若依赖数据没有改变,则 computed 不会重新计算).若没改变,计算属性会立即返回之前缓存的计算结果. 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化的值. computed 中的成员可以只定义一个函数作为只读属性, 也可以定义成 get/set 变成可读写属性 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对

  • Vue中的computed属性详解

    目录 插值表达式 methods computed 总结 今天来说说vue中的计算属性computed,为了更好的理解计算属性的好处,我们先通过一个案例来慢慢 了解计算属性,有如下案例:定义两个输入框以及一个span标签,span标签中的内容为两个输入框中的值,span标签中的内容随着输入框中的内容变化而变化 插值表达式 我们先用插值表达式的方法来实现这一效果 <body> <div id="app"> 姓: <input type="text&

  • vue中各选项及钩子函数执行顺序详解

    在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. vue的生命周期如下图: 在页面首次加载执行顺序有如下: beforeCreate //在实例初始化之后.创建之前执行 created //实例创建后执行 beforeMounted //在挂载开始之前调用 filters //挂载前加载过滤器 computed //计算属性 directives-bind //只调用一次,在指令第一次绑定到元素时调用 directives-inserted //被绑定元素插入父

  • vue中created、watch和computed的执行顺序详解

    目录 前言 为什么? 1.关于initComputed 2.关于initWatch 总结 前言 面试题:vue中created.watch(immediate: true)和computed的执行顺序是啥? 先看个简单的例子: // main.js import Vue from "vue"; new Vue({ el: "#app", template: `<div> <div>{{computedCount}}</div> &

  • python中多个装饰器的执行顺序详解

    装饰器是程序开发中经常会用到的一个功能,也是python语言开发的基础知识,如果能够在程序中合理的使用装饰器,不仅可以提高开发效率,而且可以让写的代码看上去显的高大上^_^ 使用场景 可以用到装饰器的地方有很多,简单的举例如以下场景 引入日志 函数执行时间统计 执行函数前预备处理 执行函数后清理功能 权限校验等场景 缓存 今天讲一下python中装饰器的执行顺序,以两个装饰器为例. 装饰器代码如下: def wrapper_out1(func): print('--out11--') def i

  • vue中keep-alive、activated的探讨和使用详解

    在修改公司的一个项目的时候发现了activated这个东西,一直觉得很疑惑,之前也没怎么用过啊!官网的生命周期那也没说过这东西啊!生命周期不就create mount update 和destory这几个东东么,怎么多了个activate出来. 百思不得其解,于是去问了下度娘和查了下文档!恍然大悟,原来这东东是结合keep-alive这东东使用的,下面顺便记录一下. keep-alive <keep-alive>包裹动态组件的时候,会缓存不活动的组件实例,而不是摧毁他们.其是一个抽象的组件,自

  • vue中的v-model原理,与组件自定义v-model详解

    VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" 和 @input="", 就像下面这样 // 标准写法 <input v-model="name"> // 等价于 <input :value="name" @input="name = $event.target.val

  • vue中引用swiper轮播插件的教程详解

    有时候我们需要在vue中使用轮播组件,如果是在vue组件中引入第三方组件的话,最好通过npm安装,从而进行统一安装包管理. 申明:本文所使用的是vue.2x版本. 通过npm安装插件:  npm install swiper --save-dev 在需要使用swiper的组件里引入swiper,swiper的初始化放在mounted里 Slider.vue源码: <template> <div class="swiper-container"> <div

  • 对for循环中表达式和循环体的执行顺序详解

    对于学c的朋友来说,for循环可能使我们经常用到的一种循环语句 for(表达式1:表达式2:表达式3){循环体} 知道其的语句执行顺序对我们来说可以避免很多失误 我们可以利用下面这个小程序轻易测出其内在的语句循环顺序: #include<stdio.h> void main() { int i; for (printf("#1\n"),i=1; printf("#2\n"),i<=5; printf("#3\n"),i++) {

  • Vue中的组件及路由使用实例代码详解

    1.组件是什么 组件系统是 Vue 的一个重要概念,因为它是一种抽象,允许我们使用小型.独立和通常可复用的组件构建大型应用.通常一个应用会以一棵嵌套的组件树的形式来组织: 1.1组件的声明及使用 全局组件 <body> <div id="app"> <!-- 用全局组件的名称作为HTML的标签 --> <myzujian></myzujian> </div> </body> <script>

  • 对vue中的事件穿透与禁止穿透实例详解

    在开发过程中经常遇到的一个场景,就是,页面弹窗,弹窗上有一个确定或者关闭按钮,这时,如果下方有一个按钮,那你点击弹窗的时候,也会触发弹窗下层的按钮事件,vue提供的解决方法就是直接在click.stop //阻止单击事件继续传播 <a v-on:click.stop="doThis"></a> js的解决办法是,直接在事件的方法中添加event.stopPropagation() //html <button>关闭</button> //

  • vue 中动态绑定class 和 style的方法代码详解

    先列举一些例子 class="['content',{'radioModel':checkType}]" class="['siteAppListDirNode',{open:appitem.open==true}]" class="['portalCenterMenu',{showNav:!showHideNav,hideNav:showHideNav}]" class="{shortcutMenuShow:!showHideNav,

随机推荐