前言
文章中的
Vue均指的是Vue2版本。(笔者使用的Vue版本是2.7.0-alpha.4) 在Vue中, 我们经常用下面的写法定义组件的methods以及data, 从而定义一个Vue组件。
const app = new Vue({
el: '#app',
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++;
}
}
});
这对于我们来说是在熟悉不过了。不过,为什么我们能在Vue内部定义的函数中通过this直接访问该Vue实例上的data和methods中定义的属性。
要回答这个问题,我们可以深入Vue的源码, 再次声明,这里使用的版本是v2.7.0-alpha.4来看看。
(tips: 不同的版本可能会有所差异,v2.7.0-alpha.4目前应该已经使用了ts的写法)。
搭建调试环境
克隆代码
git clone https://github.com/vuejs/vue.git
下载到源码后,我们来首先看看Readme.md,接着我们看看.github/CONTRIBUTING.md, 阅读文档,来开始我们的开发环境。
安装依赖
Vue项目使用了pnpm, 进行开发,可以了解一下pnpm。
pnpm i
安装后,我们根据脚本npm run dev, 实质上是运行下方的代码
rollup -w -c scripts/config.js --environment TARGET:full-dev
这个时候,每当你改变了src下的源代码,rollup会进行检测,并重新打包,以保证最新产物。
运行产物
上方我们已经得到了产物,那我们怎么使用呢?
在这里,我们使用了http-server去手动启动一个服务器,然后在对应的代码文件中引用即可。
新建测试文件
我们在examples下新建文件index.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">
The count is {{ count }}
<button @click="count++">add</button>
</div>
</body>
<script src="../dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data() {
return {
count: 1
}
},
methods: {
log() {
console.log('create');
}
},
mounted() {
console.log('mounted');
this.log();
console.log(this.count);
}
})
</script>
</html>
http-server运行
接者,我们来到项目目录, 使用http-server
http-server -p 8082 . -c-1
访问http://127.0.0.1:8082/examples/, 得到如下页面。
同时,我们去src/core/index.ts中修改源码。
import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'
+ console.log(Vue);
initGlobalAPI(Vue)
看到下图,则说明我们可以边修改边调试源码了。
但上面的准备,足够了吗?
生成sourcemap
实际上,上面,我们调试的仅仅时产物,其实不是特别方便,这里都是js的打包产物,对其进行debug,其实并没能回溯到我们的源代码。
所以,我们需要开启打包时生成sourcemap,追述到我们的源代码, 修改package.json中dev命令。
{
// ...
“script”: {
"dev": "rollup -w -c scripts/config.js --environment TARGET:full-dev --sourcemap",
// ....
}
// ...
}
重新跑npm run dev, 得到下方页面。
并且,我们打上断点,如果进入调试模式,那么我们的调试环境基本完成了。
那么,接下来到了读源码的时候了。
分析源码
由于笔者对于vue有过一段了解,对于data和methods的话,我们猜测一下,应该是在init的阶段。
我们来看vue中的__init, 这里__init的函数时在initMixin时候定义的。
跟着debug, 我们来到了initState(vm)函数中,这里是初始化了data和methods。
这里,其实是初始化了props,data, methods等。这里我们主要看initMethods和initData,看看这两个过程做了啥?
initMethods
下面就是initMethods,即Vue中初始过程中对methods属性的操作。
这里我们看主要逻辑(__DEV__中的逻辑,主要是避免methods中的key和props,保留字冲突,报警告)。
function initMethods(vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
// ... code __DEV__ 环境下 处理逻辑
// noop 为空函数 bind做了一层`polyfill`, 做兼容
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
这里,我们就能在Vue实例中的函数通过this访问options中定义的methods中的属性了。
接着,我们看看initData.
initData
下方便是initData的实现。
首先,由于options中的data可以为函数/对象。在这里,我们要拿到实际的data对象,所以就有了
let data: any = vm.$options.data
data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
接着,我们看看核心代码
function initData(vm: Component) {
// get data
// ...code
// proxy data on instance
// 得到`data`所有键
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
// 遍历代码,如果`data`中的`key`和`methods`/`props`中的键是否冲突,只有不冲突,并不为保留关键字的才会,使用执行proxy(vm, `_data`, key)
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
// ... code
}
整个流程如下:
get Data, 得到传入的data, 获取data对象,并赋值到vm._data上。- 对
data对象进行遍历,判断key是否和methods和props冲突。 - 如果不冲突,再执行
proxy data。
接着,我们看看proxy函数。
实质上,是使用了defineProperty, 拦截了get, set, 从而实现代理。
这样子,当我们访问对应代理的key时候,会访问vm._data[key], 这样子我们就能再组件实例中的函数访问直接访问对应的变量了。
总结
阅读源码能让我们学习一些不错的设计思想,以及部分编程规范。这里我们通过阅读vue的源码,了解了以下的知识点:
Vue2的源码调试。Vue中init阶段中的initMethods以及initData的过程。
学而知不足,上方的知识也是Vue的一小部分,也能让我们学习到部分知识点。通过阅读源码,学习别人编码思想,也是一种益处。