Vue2 this直接获取data和methods原理解析
codeniu 人气:0学习目标
本篇文章将通过阅读 vue 的源码,来回答 [为什么 Vue2 this 能够直接获取到 data 和 methods?]
仓库地址:Github
- 如何学习调试 vue2 源码
- data 中的数据为什么可以用 this 直接获取到
- methods 中的方法为什么可以用 this 直接获取到
- 学习源码中优秀代码和思想,投入到自己的项目中
如何学习调试 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 中的数据为什么可以用 this 直接获取到
- methods 中的方法为什么可以用 this 直接获取到
也就是关注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 初始化 methods
- initData 初始化 data
- initComputed 初始化 computed
在 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 组件的数据:
- 如果 data 属性是一个函数,则使用 Vue 实例作为参数调用它以获取数据。
- 检查数据是否为普通对象。如果不是,则使用空对象作为数据,并给出警告。
- 循环访问数据对象,使用 Object.defineProperty 设置对象的get与set函数,为下一步响应式做铺垫。
- 使用观察函数观察数据。在数据发生改变时响应到页面,或者在页面发生变化时,响应到数据。
总结
通过本次课程的学习,加深了在浏览器中调试代码的方法,并且通过阅读源码对vue2的响应式原理有了进一步的了解。
加载全部内容