亲宝软件园·资讯

展开

Vue2 this直接获取data和methods原理解析

codeniu 人气:0

学习目标

本篇文章将通过阅读 vue 的源码,来回答 [为什么 Vue2 this 能够直接获取到 data 和 methods?]

仓库地址:Github

如何学习调试 vue2 源码

通过去改源码的方式来学习代码,就是看到一段代码,你可能不是太懂它具体的作用是什么,那就尝试去改其中几行代码,猜测他们可能会造成那些影响,然后执行代码去验证你的猜想。

使用 Github Workspace 克隆一份代码,定位到源码位置,如下图:

安装完依赖后执行命令:

pnpm run dev

编译器会实时的将代码打包到 dist 目录下,如图:

我们引入打包后的代码,就可以实时的调试源码了,在example文件夹下新建一个html文件,并放入如下内容:

<!DOCTYPE html>
<html lang="en">
 <head>
   <meta charset="UTF-8" />
   <meta http-equiv="X-UA-Compatible" content="IE=edge" />
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
   <title>Document</title>
 </head>
 <body>
   <div id="app">
     <h2 @click="changeMsg">hello {{msg}}</h2>
   </div>
   <script src="../dist/vue.js"></script>
   <script>
     const vm = new Vue({
       el: '#app',
       data: {
         msg: 'world'
       },
       methods: {
         changeMsg() {
           this.msg = 'codeniu'
         }
       }
     })
   </script>
 </body>
</html>

使用 vscode 拓展 Live Server,打开文件:

Github Workspace 会生成一个在线预览的地址,所有的操作都是在浏览器中完成的,非常便捷。

使用浏览器的调试工具在 new Vue() 这行打上断点,开始调试:

分析源码

调试

我们在断点调试的时候要带着一下两个问题,看看vue实例化的步骤是什么:

也就是关注data 与 methods 两个关键词,果不其然,在 mergeOptions 方法中发现了我们想要寻找的关键字。

找到源码中 mergeOptions 的位置:

export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
  ...
  
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    
  ...
  }
}

initState

这一步操作是将所有选项合并,为下一步 initState 做准备,在 initState 处打上断点, F8 跳到这个断点处,F10 进入到这个函数内部。

export function initState(vm: Component) {
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)

  // Composition API
  initSetup(vm)

  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
    initData(vm)
  } else {
    const ob = observe((vm._data = {}))
    ob && ob.vmCount++
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

从代码上看这个函数的功能是进行一些初始化操作

在 initMethods 与 initData 处分别打断点进入。

initMethods

function initMethods(vm: Component, methods: Object) {
  const props = vm.$options.props
  for (const key in methods) {
    if (__DEV__) {
      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)
  }
}

这个函数看起来像是用来初始化组件实例的方法的。它接收两个参数:vm 和 methods,其中 vm 是组件实例,methods 是包含组件方法的对象。

首先,这个函数检查组件是否定义了 props 属性。如果定义了,它会警告用户,如果方法名和已有的 prop 名称相同,给出警告。

然后检查函数名是否包含 $ 与 _ ,如果方法名包含这两个符号,给出警告。

最后使用bind函数将this指向为vm,因此我们才得以使用this访问到vm实例中的所有选项。

initData

function initData(vm: Component) {
  let data: any = vm.$options.data
  data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
  if (!isPlainObject(data)) {
    data = {}
    __DEV__ &&
      warn(
        'data functions should return an object:\n' +
          'https://v2.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 (__DEV__) {
      if (methods && hasOwn(methods, key)) {
        warn(`Method "${key}" has already been defined as a data property.`, vm)
      }
    }
    if (props && hasOwn(props, key)) {
      __DEV__ &&
        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
  const ob = observe(data)
  ob && ob.vmCount++
}

InitData 函数初始化 Vue.js 组件的数据:

总结

通过本次课程的学习,加深了在浏览器中调试代码的方法,并且通过阅读源码对vue2的响应式原理有了进一步的了解。

加载全部内容

相关教程
猜你喜欢
用户评论