Vue源码模块分析
2022-04-06 16:44:36 1 举报
AI智能生成
vue源码分析
作者其他创作
大纲/内容
文件结构
dist:所有输出的版本
cjs
webpack1、browserfiry等老版本打包器(vue.common.xx.js)
esm
webpack2等新版本打包器(vue.esm.xx.js)
umd
兼容cjs和amd(vue.js、vue.min.js)
runtime
仅包含运行时,没有编译器(预编译)
examples:测试代码
flow:类型声明
packages:独立模块
scripts:打包脚本
src:核心代码
compiler:编译器,
包括把模板解析成AST语法树,
AST语法树优化,代码生成等功能
包括把模板解析成AST语法树,
AST语法树优化,代码生成等功能
codegen:代码生成
directives:指令v-bind v-model v-on
parser:ast语法树生成部分
core:核心代码。
包括内置组件、全局API封装、
Vue实例化、观察者、虚拟DOM、工具函数等
包括内置组件、全局API封装、
Vue实例化、观察者、虚拟DOM、工具函数等
components:内置组件 KeepAlive
global-api:extend, assets, mixin, use),observer,util
instance:render相关 以及vue构造函数定义 生命周期钩子挂载 原型链上实例方法的挂载
observer:响应式的实现
util:工具包 主要是 debug,lang,next-tick,options(合并策略),props(props处理),env运行环境嗅探
vdom:虚拟dom实现
platforms:平台特殊代码
server:服务端渲染的逻辑,这部分代码是跑在服务器的node.js
sfc:单文件解析器,把 .vue 文件内容解析成一个 javascript 对象
shared:公共帮助代码,一些定义的工具方法,这些方法会被浏览器端的Vue.js 和服务端的Vue.js 共同使用
types:源码使用了Flow做静态类型检查,需要给typescript专门写一套
文件入口
寻找入口文件
通过dev脚本,提取出关键信息:scripts/config.js、web-full-dev
package.json
在 scripts/config.js 中寻找web-full-dev,拿到entry
scripts/config.js
寻找entry中的resolve函数定义
scripts/config.js
寻找resolve函数中调用的aliases,然后向上合并,得到entry完整地址
scripts/alias.js
成功得到入口文件,代码从这里开始执行
src/platforms/web/entry-runtime-with-compiler.js
寻找Vue构造函数
从入口文件开始寻找
src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
寻找入口文件中Vue的来源
src/platforms/web/runtime/index.js
import Vue from 'core/index'
寻找第2步中Vue来源
src/core/index.js
import Vue from './instance/index'
寻找第3步中Vue来源,
终于找到Vue构造函数
终于找到Vue构造函数
src/core/instance/index.js
initMixin(Vue)
实现了Vue.prototype._init(),初始化的入口,各种初始化工作,包括下方4个Mixin()涉及的数据初始化
stateMixin(Vue)
和组件状态相关:【Vue.prototype.】$data, $props, $set, $delete, $watch
eventsMixin(Vue)
事件的核心方法:【Vue.prototype.】$on, $once, $off, $emit
lifecycleMixin(Vue)
生命周期的核心方法:【Vue.prototype.】_update, $forceUpdate, $destroy
renderMixin(Vue)
渲染的核心方法:【Vue.prototype.】$nextTick, _render(_update, _render都是私有方法,不打算给用户使用的)
数据渲染
Vue构造函数内部
调用了_init()完成了初始化
调用了_init()完成了初始化
src/core/instance/init.js
initLifecycle(vm)
初始化$parent、$root、$children、$refs
initEvents(vm)
处理父组件传递的监听器
initRender(vm)
$slots, $scopedSlots, _c, $createElement()
beforeCreate钩子函数
显然beforeCreate的钩子函数中无法获取到props、data中定义的值,也不能调用methods中定义的函数
initInjections(vm)
获取注入的数据,resolve injections before data/props
initState(vm)
初始化组件中的props,methods,data,computed,watch...
initProvide(vm)
提供数据,resolve provide after data/props
created钩子函数
beforeCreate,created钩子执行的时候,还没有渲染DOM,所以我们也无法访问DOM
vm.$mount(vm.$options.el)
挂载组件。选项里有el时,自动执行$mount(所以new Vue({})选项里不需要单独设置$mount)
在原型的基础上
扩展的$mount()
扩展的$mount()
src/platforms/web/entry-runtime-with-compiler.js
Vue 不能挂载在 body、html 这样的根节点上
如果render不存在
把 el 或者 template 字符串转换成 render 方法(在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法)
若有template,判断template类型(#id、模板字符串、dom元素),转化成html字符串
若没有template,则判断el,用 getOuterHTML(el) 提取挂载DOM元素的HTML用作template
调用编译器,将template字符串转换为render函数(关于编译器,下一分支会详细介绍)
调用原型上的$mount挂载
原型上的$mount()
src/platforms/web/runtime/index.js
对el做处理,转变成对象
调用mountComponent()
mountComponent()
具体实现
具体实现
src/core/instance/lifecycle.js
执行beforeMount钩子函数之前检查选项里有无render函数,没有的话建立一个空的vnode
定义函数 updateComponent(),内部会执行 vm._render()和vm._update(),生成虚拟dom并更新dom
实例化一个渲染 watcher 进行数据绑定,在watcher内部把 updateComponent() 赋值给getter
挂载实例,调用mounted钩子(vm._isMounted设置为true表示这个实例已经挂载)
vm._render()
生成虚拟DOM
生成虚拟DOM
src/core/instance/render.js
核心:vnode = render.call(vm._renderProxy, vm.$createElement)
寻找vm.$createElement的定义,发现内部调用了createElement()
src/core/instance/render.js
寻找createElement(),内部调用_createElement()生成虚拟DOM
src/core/vdom/create-element.js
寻找 _createElement()
src/core/vdom/create-element.js
children 的规范化
目的:把children 变成一个类型为 VNode 的 Array
normalizeChildren()
src/core/vdom/helpers/normalzie-children.js
simpleNormalizeChildren()
src/core/vdom/helpers/normalzie-children.js
创建VNode
如果tag为string 类型
如果是内置节点,则直接创建一个普通 VNode
如果是为已注册的组件名,则通过createComponent创建一个组件类型的VNode
否则创建一个未知的标签的 VNode
如果tag为Component 类型
调用 createComponent 创建一个组件类型的 VNode 节点
vm._update()
更新DOM
更新DOM
src/core/instance/lifecycle.js
核心:调用 vm.__patch__ 方法
寻找vm.__patch__方法
src/platforms/web/runtime/index.js
在服务端渲染中没有真实的浏览器DOM环境,所以不需要把VNode最终转换成DOM,因此是一个空函数noop,而在浏览器端渲染中,它指向了 patch 方法
寻找patch()方法
src/platforms/web/runtime/patch.js
nodeOps:封装了一系列DOM操作的方法,更新dom时使用;
modules:定义了一些模块的钩子函数的实现
寻找createPatchFunction()方法
src/core/vdom/patch.js
内部定义了一系列的辅助方法,最终返回了一个patch方法,这个方法赋值给了vm._update函数里调用的vm.__patch__
核心:createElm()
作用:通过虚拟节点创建真实的 DOM 并插入到它的父节点中
实现
尝试创建子组件
如果vnode包含tag,则对tag的合法性在非生产环境下做校验,看是否是一个合法标签
调用平台 DOM 的操作去创建一个占位符元素
调用 createChildren 方法创建子元素
src/core/vdom/patch.js
原理:遍历子虚拟节点,深度优先递归调用 createElm
执行所有的 create 的钩子并把 vnode push 到 insertedVnodeQueue 中
最后调用 insert 方法把 DOM 以先子后父的顺序插入到父节点中
src/core/vdom/patch.js
整个过程就是递归创建一个完整的 DOM 树并插入到 Body 上
组件化
createComponent()
src/core/vdom/create-component.js
构造子类构造函数
安装组件钩子函数
实例化 VNode
编译器
前置知识
在使用 vue-cli 脚手架构建项目时,会遇到一个构建选项 Vue build,有两个选项,Runtime + Compiler和Runtime-only,接下来我们分析一下这两者的区别
定义
Runtime + Compiler
运行时+编译器
内部包含编译代码,可以把编译过程放在运行时做
Runtime-only
运行时版本
运行的时候是不带编译器的,编译是在离线的时候做的
在构建时通过 webpack 的 vue-loader 工具将 .vue模板预编译成 Javascript,进行了预编译,在这个过程中会将组件中的template模板编译为render函数
区别
生成的脚手架
文件大小
runtime-only 比 runtime-compiler 轻 6kb,只包含运行时的Vue.js代码,因此代码体积也会更轻量,runtime-only 运行更快
运行过程
Runtime + Compiler
template -> ast -> render -> virtual dom -> 真实dom
Runtime-only
render -> virtual dom -> 真实dom
前一种比后一种多了将template转为render的过程,这部分就是靠编译器实现的
入口文件
Runtime + Compiler
src/platforms/web/entry-runtime-with-compiler.js
在原型的基础上扩展的$mount(),可以解析template,编译成render
Runtime-only
src/platforms/web/entry-runtime.js
src/platforms/web/runtime/index.js
原型上的$mount(),直接解析render
编译
在《数据渲染》模块的第二步已经分析过在扩展$mount()中是如何获取到template的,这里不做重复分析,直接看template是如何转化成render函数的
编译入口
src/platforms/web/entry-runtime-with-compiler.js
调用编译器compileToFunctions()生成动态节点渲染方法render和静态节点渲染方法集合staticRenderFns
寻找compileToFunctions(),内部调用createCompiler()
src/platforms/web/compiler/index.js
createCompiler()的返回值赋值给变量
compile
compileToFunctions
寻找createCompiler(),内部调用createCompilerCreator()
src/compiler/index.js
传参是个函数,真正的编译过程都在这个 baseCompile 函数里执行
ast = parse(template.trim(), options):用正则等方式解析template模板中的指令、class、style等数据,形成AST树
optimize(ast, options):将AST树进行优化,检测不需要进行DOM改变的静态节点
寻找createCompilerCreator(),
return一个createCompiler函数
return一个createCompiler函数
src/compiler/create-compiler.js
createCompiler()返回一个对象
compile 方法属性,createCompiler()是内部定义的一个函数
compileToFunctions 属性,这个 compileToFunctions 对应的就是 $mount 函数调用的 compileToFunctions 方法,它是调用 createCompileToFunctionFn 方法的返回值
寻找createCompileToFunctionFn(),
return一个compileToFunctions函数
return一个compileToFunctions函数
src/compiler/to-function/js
核心:const compiled = compile(template, options)
compile 函数在执行 createCompileToFunctionFn 的时候作为参数传入,它是 createCompiler 函数中定义的 compile 函数
寻找compile()
src/compiler/create-compiler.js
核心:const compiled = baseCompile(template.trim(), finalOptions)
编译过程
parse:生成AST树
src/compiler/parse/index.js
内部调用了parseHTML
src/compiler/parser/html-parser
循环解析 template ,用正则做各种匹配,对于不同情况分别进行不同的处理,直到整个 template 被解析完毕。
在匹配的过程中会利用 advance 函数不断前进整个模板字符串,直到字符串末尾。
optimize:优化AST树
src/compiler/optimizer.js
过程
深度遍历AST树,先标记静态节点,再标记静态根
目标
目标:通过标记静态节点的方式,优化更新时对静态节点的处理逻辑
generate:AST转化成可执行代码
0 条评论
下一页
为你推荐
查看更多