最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue源码解读(methods,data)

    正文概述 掘金(Husky-Yellow)   2021-03-01   716

    系列文章:

    • Vue源码解读(设计篇)
    • Vue源码解读(Rollup篇)
    • Vue源码解读(入口到构造函数整体流程)
    • Vue源码解读(响应式原理介绍和Prop)

    methods处理

    在分析完props相关逻辑后,接下来分析与methods相关的逻辑,这部分相比于props要简单得多。

    export function initState (vm: Component) {
      // 省略代码
      const opts = vm.$options
      if (opts.methods) initMethods(vm, opts.methods)
    }
    

    initState()方法中,调用了initMethods()并传入了当前实例vm和撰写的methods。接下来,看一下initMethods方法具体的实现:

    function initMethods (vm: Component, methods: Object) {
      const props = vm.$options.props
      for (const key in methods) {
        if (process.env.NODE_ENV !== 'production') {
          if (typeof methods[key] !== 'function') {
            warn(
              `Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
              `Did you reference the function correctly?`,
              vm
            )
          }
          if (props && hasOwn(props, key)) {
            warn(
              `Method "${key}" has already been defined as a prop.`,
              vm
            )
          }
          if ((key in vm) && isReserved(key)) {
            warn(
              `Method "${key}" conflicts with an existing Vue instance method. ` +
              `Avoid defining component methods that start with _ or $.`
            )
          }
        }
        vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
      }
    }
    

    在以上代码中可以看到,initMethods()方法实现中最重要的一段代码就是:

    // 空函数
    function noop () {}
    
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    

    它首先判断了定义的methods是不是function类型,如果不是则赋值为一个noop空函数,如果是则把这个方法进行bind绑定,其中传入的vm为当前实例。这样做的目的是为了把methods方法中的this指向当前实例,这样就能在methods方法中通过this.xxx的形式很方便的访问到propsdata以及computed等与实例相关的属性或方法。

    在开发环境下,它还做了如下几种判断:

    • 必须为function类型。
    // 抛出错误:Method sayHello has type null in the component definition. 
    //          Did you reference the function correctly?
    export default {
      methods: {
        sayHello: null
      }
    }
    
    • 命名不能和props冲突。
    // 抛出错误:Method name has already been defined as a prop.
    export default {
      props: ['name']
      methods: {
        name () {
          console.log('name')
        }
      }
    }
    
    • 命名不能和已有的实例方法冲突。
    // 抛出错误:Method $set conflicts with an existing Vue instance method. 
    //          Avoid defining component methods that start with _ or $.
    export default {
      methods: {
        $set () {
          console.log('$set')
        }
      }
    }
    

    在分析完以上initMethods流程后,能得到如下流程图:

    Vue源码解读(methods,data)

    data处理

    Vue中关于data的处理,根实例和子组件有一点点区别,接下来着重分析子组件中关于data的处理过程。

    export function initState (vm: Component) {
      const opts = vm.$options
      if (opts.data) {
        initData(vm)
      } else {
        observe(vm._data = {}, true /* asRootData */)
      }
    }
    

    在以上代码中,首先判断了opts.data,如果值为true则代表是子组件(子组件如果没有显示定义data,则使用默认值),否则代表是根实例。对于根实例而言不需要执行initData的过程,只要对vm._data进行observe即可。

    接下来,详细分析initData的过程,它是定义在src/core/instance/state.js文件中的一个方法:

    function initData (vm: Component) {
      let data = vm.$options.data
      data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}
      if (!isPlainObject(data)) {
        data = {}
        process.env.NODE_ENV !== 'production' && warn(
          'data functions should return an object:\n' +
          'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
          vm
        )
      }
      // proxy data on instance
      const keys = Object.keys(data)
      const props = vm.$options.props
      const methods = vm.$options.methods
      let i = keys.length
      while (i--) {
        const key = keys[i]
        if (process.env.NODE_ENV !== 'production') {
          if (methods && hasOwn(methods, key)) {
            warn(
              `Method "${key}" has already been defined as a data property.`,
              vm
            )
          }
        }
        if (props && hasOwn(props, key)) {
          process.env.NODE_ENV !== 'production' && warn(
            `The data property "${key}" is already declared as a prop. ` +
            `Use prop default value instead.`,
            vm
          )
        } else if (!isReserved(key)) {
          proxy(vm, `_data`, key)
        }
      }
      // observe data
      observe(data, true /* asRootData */)
    }
    

    虽然initData()方法的代码有点长,但详细观察后可以发现,其主要做的就是四件事情:类型判断取值命名冲突判断proxy代理以及observe(data)

    然后,分别对以上几块进行详细解释:

    • 类型判断取值:对于子组件而言,由于组件可以多次复用,因此函数必须通过工厂函数模式返回一个对象,这样在组件多次复用时就能避免引用类型的问题。
    // Child Component
    // 抛出错误:data functions should return an object
    export default {
      data: {
        msg: 'Hello, Data'
      }
    }
    

    对于data是一个函数的情况,调用getData方法来取值,getData方法定义如下:

    export function getData (data: Function, vm: Component): any {
      pushTarget()
      try {
        return data.call(vm, vm)
      } catch (e) {
        handleError(e, vm, `data()`)
        return {}
      } finally {
        popTarget()
      }
    }
    

    代码分析:pushTarget是一个与响应式依赖收集有关的,会在后续进行详细说明。getData的取值过程包裹在try/catch中,通过data.call(vm, vm)进行调用返回,如果函数调用出错,则使用handleError进行错误统一处理。

    • 命名冲突判断:由于propsmethods有更高的优先级,因此data属性的命名不能和propsmethods中的命名冲突,因为无论是propsmethods还是data最后都会反映在实例上。另外一种命名冲突,是不能以$或者_开头,因为这样很容易和实例私有方法、属性或对外暴露以$开头的方法、属性冲突。
    // 1.与methods命名冲突
    // 抛出错误:Method name has already been defined as a data property.
    export default {
      data () {
        return {
          name: 'data name'
        }
      },
      methods: {
        name () {
          console.log('methods name')
        }
      }
    }
    
    // 2.与props命名冲突
    // 抛出错误:The data property name is already declared as a prop.
    //          Use prop default value instead.
    export default {
      props: ['name'],
      data () {
        return {
          name: 'data name'
        }
      }
    }
    
    // 3.不能以$和_开头
    export default {
      data () {
        return {
          $data: '$data'
          _isVue: true
        }
      }
    }
    
    • proxy代理:在之前已经介绍过proxy代理的作用,也讲过proxy代理_props的例子,这里代理_data跟代理_props是同样的道理。
    export default {
      data () {
        return {
          msg: 'Hello, Msg'
        }
      }
    }
    // 代理前
    console.log(this._data.msg)
    proxy(vm, '_data', 'msg')
    // 代理后
    console.log(this.msg)
    
    • observe(data)observe的作用是把传入值所有的属性(包括嵌套属性)递归的进行响应式defineReactive,会在之后的章节中详细介绍observe的实现原理,在initData中只要知道observe(data)会把data函数返回对象的所有属性全部变成响应式的即可。

    在分析完initData的实现后,可以得到initData的整体流程图。

    Vue源码解读(methods,data)


    起源地下载网 » Vue源码解读(methods,data)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元