我正在参与掘金会员专属活动-源码共读第一期,点击参与
前言
从易到难开始学习源码。学习源码不是为了面试,只是想揭开别人造的轮子背后的神秘面纱。同时也希望在此过程中提升自己的能力和开拓视野。
准备环境
首页还是克隆一份 vue2源码 到我们本地
- 全局安装一个依赖 http-server , npm install -g http-server
- 运行命令 http-server -p 8083
看到上图,服务启动成功
开始调试
开始调试之前,我们先创建一个文件 examples/test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue2 this 能够直接获取到 data 和 methods</title>
</head>
<body>
<div id="app">
{{ msg }}
</div>
<script src="../dist/vue.js"></script>
<script>
debugger
new Vue({
el: '#app',
data() {
return {
msg: 'hello vue'
}
},
methods: {
printMsg(){
console.log(this.msg);
}
},
})
</script>
</body>
</html>
然后使用浏览器打开我们的服务 http://127.0.0.1:8003/examples/test.html
进入到下图的文件,然后点击右键,选择第一个 Reveal in sidebar 选项,就可以看到源码对应的目录
然后在下一步往下走,进入到 _init 函数
export function initMixin(Vue: typeof Component) {
Vue.prototype._init = function (options?: Record<string, any>) {
const vm: Component = this
/*** 删除了部分代码 ***/
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate', undefined, false /* setContext */)
initInjections(vm) // resolve injections before data/props
// props、methods、data、computed、watch
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
/*** 删除了部分代码 ***/
}
}
这里我删除了部分代码,我们把重点放在 initState 函数上,在这个函数之上我写了注释:props、methods、data、computed、watch。是的没错这个函数就是处理我们 vue2 中的这些属性,让我们再下一步进入到 initState 函数内部,在执行这个函数的时候,传入了一个参数 vm: vm 是 vue 实例化对象。
export function initState(vm: Component) {
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
/*** 删除了部分代码 ***/
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)
}
}
// 处理 data
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++
}
// 处理 methods
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)
}
}
initData 函数处理data
- 判断data类型是不是函数,如果是函数,则执行后返回,否则直接返回 data || {}
- 使用工具函数 isPlainObject 判断data是不是对象类型,如果不是则将 data 设置成空对象,并打印警告
- 循环data中所有的属性不能和 props、methods 上的属性相同
- 将 data 上的所有属性代理到 vm
- 将 data 设置成响应式数据
initMethods 函数处理 methods 对象
循环 methods 对象,判断 methods 上的属性不能不是函数类型,不能跟 props 的属性同名,最后使用 bind 改变每个属性(function 类型)的 this 指向,然后代理到 vm。
走到这里本节的目标就实现了,data 和 methods 的属性都代理到 vm,然后 methods 中的所有属性(function 类型)的指针 this 都是指向 vm。所以 this 等于 vm
总结
本文内容是 vue2 源码的入门级调试,也是刚走了个开头。通过实例化 Vue 进入到 _init 函数,一步一步 vue 初始化的流程,这里只读了其中几个函数的初始化过程,就找到了问题的答案。
本文学到了在 vue2 中 this 是如何 data 和 methods 下的属性。
以上是我对本节学习和理解,如果有什么不对的地方,还请指正!