准备工作 - 学习vue源码系列1
决定跟着黄轶老师的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(){}