决定跟着黄轶老师的vue2源码课程好好学习下vue2的源码,学习过程中,尽量输出自己的所得,提高学习效率,水平有限,不对的话请指正~
将vue的源码clone到本地,切换到分支2.6。
认识 flow
Flow 是 facebook 出品的 JavaScript 静态类型检查工具。
vue使用其进行类型检测。
怎么使用 flow
安装:
npm i flow-bin -g
创建配置文件:
flow init
创建一个 js 文件
/*@flow*/
function split(str) {
return str.split(" ");
}
split(11);
执行flow命令,就可以检测了,这边会发现报错
flow 的工作方式
通常类型检查分成 2 种方式:
-
类型推断:通过变量的使用上下文来推断出变量类型,然后根据这些推断来检查类型。(上面的例子就是推断)
-
类型注释:事先注释好我们期待的类型,Flow 会基于这些注释来判断。(推荐)
类型注释和 ts 非常像:
/*@flow*/
function add(x: number, y: number): number {
return x + y;
}
add("Hello", 11);
// 执行 flow,会报错
一般在需要flow检查的文件首行加上/*@flow*/
如果可以为undefined或者null的话:
var foo: string | void = null;
var foo: ?string = null;
flow 在 vue 源码中的使用
flow 可以自定义类型,vue 的主目录下.flowconfig 文件, 它是 Flow 的配置文件。
这其中的 [libs] 部分用来描述包含指定库定义的目录,默认是名为 flow 的目录。
遇到某个类型,想要看数据结构的话,就翻看这里。
vue 源码目录设计
主要看src的设计:
功能模块拆分的非常清楚,让阅读性和可维护性都很优雅。
vue 源码构建
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。
Rollup 主要是构建 js 文件,不处理其他的文件,相比 webpack 更轻量。
所谓构建,就是运行命令之后,生成最终的文件。
构建脚本
构建脚本是package.json,其中的的build命令,就是构建命令。
vue 的三种运行环境:
- web(普通的 web 端)
- ssr(服务端)
- weex(原生)
构建过程
src/build.js其实就是获取配置,然后根据环境参数过滤,最后在dist目录生成相应的js文件。
为了让配置中的entry/dist路径更具可读性,vue进行了一些技巧性的操作:
const builds = {
"web-runtime-cjs-dev": {
// 这里的 web会被替换成web的绝对路径,resolve就是做这个事的
entry: resolve("web/entry-runtime.js"),
// 同理 dist也是如此
dest: resolve("dist/vue.runtime.common.dev.js"),
format: "cjs",
env: "development",
banner,
},
// ...
};
再看下resolve:
const resolve = (p) => {
const base = p.split("/")[0];
// // aliases就是 路径的别名哈希表 {compile:"compiler文件夹的绝对地址",core:.....}
if (aliases[base]) {
return path.resolve(aliases[base], p.slice(base.length + 1));
} else {
return path.resolve(__dirname, "../", p);
}
};
format属性
format表示 最后生成的 js 文件符合什么规范:
- commonJS 规范,其实就是
require/module.exports - ESModule 规范,其实就是
import/export - umd 规范,算是兼容模式,
(function (window, factory) {
if (typeof exports === "object") {
module.exports = factory();
} else if (typeof define === "function" && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
小技巧:打印错误和文件大小
有两个函数,可以日常使用
function getSize (code) {
return (code.length / 1024).toFixed(2) + 'kb'
}
// catch(logError) 这样使用非常方便
function logError (e) {
console.log(e)
}
Runtime Only VS Runtime + Compiler
其实这两个是相对的。
使用vue的时候,如果template是字符串的话,如下
new Vue({
template: '<div>{{ hi }}</div>'
})
这种代码一般是在运行环境里,由运行环境将template编译成render函数,这里特别注意,编译是运行环境做的,这样vue就必须包含怎么编译的代码,专业名词就是Compiler
当然如果代码里没有这样的字符串,自然也就不需要Compiler。
显然加上Compiler既增加了vue的体积,又让运行环境干编译,会更费时。
那另外一种就好理解了,所谓的Runtime其实就是没有Compiler的vue代码,一般开发是用.vue文件表示模板,而.vue文件,是在你的编辑器里,也称为编译环境里,将其编译成render函数(依靠webpack的vue-loader插件),这样运行环境自然不需要编译,只需要运行就行,专业名词就是Runtime
显然Runtime既轻量,又省时。
说句人话,开发的时候如果模板没有字符串的话,直接Runtime就好,不需要Compiler
从入口开始
先找到定义vue的地方!
dist的vue.js是由entry-runtime-with-compiler.js生成的,一层层往上找线索~
// entry-runtime-with-compiler.js
import Vue from './runtime/index.js'
// runtime/index.js
import Vue from 'core/index'
// core/index.js
import Vue from './instance/index'
// instance/index 找到啦!
function Vue (options) {
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
this._init(options)
}
这里用函数的形式定义了类,好处是方便在Vue.prototype上面拓展,这样拓展的方法和属性可以分布在别的文件,方便维护。
小技巧:怎么判断有没有用new调用
其实就是this是不是属于当前实例
function C(){
if(!this instanceOf C){console.log('C is a constructor and should be called with the `new` keyword')}
}
小技巧:函数的形式定义类,方便拓展
class关键字的方式,拓展会不方便,反而函数的形式更加便捷,可以将方法和属性分门别类在其他文件
function C(){}
// getName.js
C.prototype.getName = function getName(){}