File structure and operating mechanism of Vue source code

不言
Release: 2018-07-09 11:22:40
Original
1373 people have browsed it

This article mainly introduces the file structure and operating mechanism of Vue source code. It has certain reference value. Now I share it with you. Friends in need can refer to it.

vue is currently the front-end web in China. End is one third of the world, and it is also one of my main technology stacks. I know it in daily use and am curious about why. In addition, a large number of Vue source code reading articles have appeared in the community recently. I would like to take this opportunity to learn from I have learned some nutrition from everyone’s articles and discussions, and at the same time summarized some thoughts when reading the source code, and produced some articles as a summary of my own thinking. My level is limited, so welcome to leave a message for discussion~

Target Vue version:2.5.17-beta.0
vue source code comments: https://github.com/SHERlocked...
Statement: The syntax of the source code in the article uses Flow, and the source code is as needed All are abridged (in order not to be confused@_@). If you want to see the full version, please enter the github address above. This article is a series of articles. The article address can be found at the bottom~

0. Preliminary knowledge

  • Flow

  • ES6 syntax

  • Commonly used design patterns

  • Functional programming ideas such as currying

Here are some preliminary articles: JS static type checking tool Flow, introduction to ECMAScript 6 - Ruan Yifeng, Curry in JS ization, JS observer mode, JS uses high-order functions to implement function caching (memo mode)

1. File structure

The file structure is introduced in CONTRIBUTING.md of vue, here Directly translated:

├── scripts ------------------------------- 包含与构建相关的脚本和配置文件 │ ├── alias.js -------------------------- 源码中使用到的模块导入别名 │ ├── config.js ------------------------- 项目的构建配置 ├── build --------------------------------- 构建相关的文件,一般情况下我们不需要动 ├── dist ---------------------------------- 构建后文件的输出目录 ├── examples ------------------------------ 存放一些使用Vue开发的应用案例 ├── flow ---------------------------------- JS静态类型检查工具[Flow](https://flowtype.org/)的类型声明 ├── package.json ├── test ---------------------------------- 测试文件 ├── src ----------------------------------- 源码目录 │ ├── compiler -------------------------- 编译器代码,用来将 template 编译为 render 函数 │ │ ├── parser ------------------------ 存放将模板字符串转换成元素抽象语法树的代码 │ │ ├── codegen ----------------------- 存放从抽象语法树(AST)生成render函数的代码 │ │ ├── optimizer.js ------------------ 分析静态树,优化vdom渲染 │ ├── core ------------------------------ 存放通用的,平台无关的运行时代码 │ │ ├── observer ---------------------- 响应式实现,包含数据观测的核心代码 │ │ ├── vdom -------------------------- 虚拟DOM的 creation 和 patching 的代码 │ │ ├── instance ---------------------- Vue构造函数与原型相关代码 │ │ ├── global-api -------------------- 给Vue构造函数挂载全局方法(静态方法)或属性的代码 │ │ ├── components -------------------- 包含抽象出来的通用组件,目前只有keep-alive │ ├── server ---------------------------- 服务端渲染(server-side rendering)的相关代码 │ ├── platforms ------------------------- 不同平台特有的相关代码 │ │ ├── weex -------------------------- weex平台支持 │ │ ├── web --------------------------- web平台支持 │ │ │ ├── entry-runtime.js ---------------- 运行时构建的入口 │ │ │ ├── entry-runtime-with-compiler.js -- 独立构建版本的入口 │ │ │ ├── entry-compiler.js --------------- vue-template-compiler 包的入口文件 │ │ │ ├── entry-server-renderer.js -------- vue-server-renderer 包的入口文件 │ ├── sfc ------------------------------- 包含单文件组件.vue文件的解析逻辑,用于vue-template-compiler包 │ ├── shared ---------------------------- 整个代码库通用的代码
Copy after login

Several important directories:

  • compiler:Compilation, used to convert template into render function

  • core:Vue’s core code, including responsive implementation, virtual DOM, Vue instance method mounting, global methods, abstracted common components, etc.

  • platform:The entry files for different platforms are mainly for the web platform and the weex platform. Different platforms have their own special construction processes. Of course, our focus is on the web platform

  • server:Related code for server-side rendering (SSR). SSR mainly renders components directly into HTML and provides them directly to the Client from the Server side

  • sfc:Mainly the logic of .vue file parsing

  • shared:Some common tool methods, some It is set up to increase the readability of the code

Thesrc/platforms/web/entry-runtime.jsfile under platform serves as the entry point for the runtime build , ESM mode outputs dist/vue.runtime.esm.js, CJS mode outputs dist/vue.runtime.common.js, UMD mode outputs dist/vue.runtime.js, compiler that does not include template template to render function
src/platforms/web/entry-runtime-with-compiler.jsThe file serves as the entry point for the runtime build. ESM mode outputs dist/vue.esm.js, and CJS mode outputs dist/vue.common. js, UMD mode output dist/vue.js, including compiler

2. Entry file

Any front-end project can start from thepackage.jsonfile, come first Take a look at itsscript.dev, which is its command line when we runnpm run dev:

"scripts": { "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev" }
Copy after login
The rollup here is a JS module similar to webpack Packager, in fact, the Vue - v1.0.10version used webpack before, and then changed it to rollup. If you want to know why it was changed to rollup, you can take a look at You Yuxi’s own answer. In general The purpose is to make the package size smaller and the initialization speed faster.

You can see here that rollup runs thescripts/config.jsfile and gives a parameterTARGET:web-full-dev, let’s take a lookscripts/config.jsWhat’s inside

// scripts/config.js const builds = { 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), // 入口文件 dest: resolve('dist/vue.js'), // 输出文件 format: 'umd', // 参看下面的编译方式说明 env: 'development', // 环境 alias: { he: './entity-decoder' }, // 别名 banner // 每个包前面的注释-版本/作者/日期.etc }, }
Copy after login

format Compilation method description:
es:ES Modules, using ES6 template syntax output
cjs:CommonJs Module, file output that follows the CommonJs Module specification
amd:AMD Module, file output that follows the AMD Module specification
umd:Supports external Chain specification file output, this file can directly use the script tag

hereweb-full-devcorresponds to the command we just passed in the command line, then rollup will press The entry file below starts to be packaged. There are many other commands and various other output methods and formats. You can check the source code yourself.

So the main focus of this article is thesrc/platforms/web/entry-runtime-with-compiler.jsfile that contains the compiler. In production and development environments we use vue -loader is used to compile the template, so there is no need for a package with a compiler. However, in order to better understand the principles and processes, it is recommended to start with the entry file with a compiler.

Let’s take a look at this file first. Vue is imported here to see where it comes from.

// src/platforms/web/entry-runtime-with-compiler.js import Vue from './runtime/index'
Copy after login

Continue reading

// src/platforms/web/runtime/index.js import Vue from 'core/index'
Copy after login

keep moving

// src/core/index.js import Vue from './instance/index'
Copy after login

keep moving*2

// src/core/instance/index.js /* 这里就是vue的构造函数了,不用ES6的Class语法是因为mixin模块划分的方便 */ function Vue(options) { this._init(options) // 初始化方法,位于 initMixin 中 } // 下面的mixin往Vue.prototype上各种挂载 initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default Vue
Copy after login

When wenew Vue( ), this constructor is actually called. You can start to read it from here.

3. 运行机制

这里我用xmind粗略的画了一张运行机制图,基本上后面的分析都在这张图上面的某些部分了

本文 Vue 实例都是用vm来表示

File structure and operating mechanism of Vue source code

上面这个图可以分为多个部分细加阅读,具体的实现我们在后面的文章中详细讨论,这里先贴一部分源码尝尝鲜

3.1 初始化 _init( )

File structure and operating mechanism of Vue source code

当我们在 main.js 里new Vue( )后,Vue 会调用构造函数的_init( )方法,这个方法是位于 core/instance/index.js 的initMixin( )方法中定义的

// src/core/instance/index.js /* 这里就是Vue的构造函数 */ function Vue(options) { this._init(options) // 初始化方法,位于 initMixin 中 } // 下面的mixin往Vue.prototype上各种挂载,这是在加载的时候已经挂载好的 initMixin(Vue) // 给Vue.prototype添加:_init函数,... stateMixin(Vue) // 给Vue.prototype添加:$data属性, $props属性, $set函数, $delete函数, $watch函数,... eventsMixin(Vue) // 给Vue.prototype添加:$on函数, $once函数, $off函数, $emit函数, $watch方法,... lifecycleMixin(Vue) // 给Vue.prototype添加: _update方法, $forceUpdate函数, $destroy函数,... renderMixin(Vue) // 给Vue.prototype添加: $nextTick函数, _render函数,... export default Vue
Copy after login

我们可以看看init( )这个方法到底进行了哪些初始化:

// src/core/instance/index.js Vue.prototype._init = function(options?: Object) { const vm: Component = this initLifecycle(vm) // 初始化生命周期 src/core/instance/lifecycle.js initEvents(vm) // 初始化事件 src/core/instance/events.js initRender(vm) // 初始化render src/core/instance/render.js callHook(vm, 'beforeCreate') // 调用beforeCreate钩子 initInjections(vm) // 初始化注入值 before data/props src/core/instance/inject.js initState(vm) // 挂载 data/props/methods/watcher/computed initProvide(vm) // 初始化Provide after data/props callHook(vm, 'created') // 调用created钩子 if (vm.$options.el) { // $options可以认为是我们传给 `new Vue(options)` 的options vm.$mount(vm.$options.el) // $mount方法 } }
Copy after login

这里_init()方法中会对当前vm实例进行一系列初始化设置,比较重要的是初始化 State 的方法initState(vm)的时候进行data/props的响应式化,这就是传说中的通过Object.defineProperty()方法对需要响应式化的对象设置getter/setter,以此为基础进行依赖搜集(Dependency Collection),达到数据变化驱动视图变化的目的。

最后检测vm.$options上面有没有el属性,如果有的话使用vm.$mount方法挂载vm,形成数据层和视图层的联系。这也是如果没有提供el选项就需要自己手动vm.$mount('#app')的原因。

我们看到created钩子是在挂载$mount之前调用的,所以我们在created钩子触发之前是无法操作 DOM 的,这是因为还没有渲染到 DOM 上。

3.2 挂载 $mount( )

File structure and operating mechanism of Vue source code

挂载方法vm.$mount( )在多个地方有定义,是根据不同打包方式和平台有关的,src/platform/web/entry-runtime-with-compiler.jssrc/platform/web/runtime/index.jssrc/platform/weex/runtime/index.js,我们的关注点在第一个文件,但在entry-runtime-with-compiler.js文件中会首先把runtime/index.js中的$mount方法保存下来,并在最后用 call 运行:

// src/platform/web/entry-runtime-with-compiler.js const mount = Vue.prototype.$mount // 把原来的$mount保存下来,位于 src/platform/web/runtime/index.js Vue.prototype.$mount = function( el?: string | Element, // 挂载的元素 hydrating?: boolean // 服务端渲染相关参数 ): Component { el = el && query(el) const options = this.$options if (!options.render) { // 如果没有定义render方法 let template = options.template // 把获取到的template通过编译的手段转化为render函数 if (template) { const { render, staticRenderFns } = compileToFunctions(template, {...}, this) options.render = render } } return mount.call(this, el, hydrating) // 执行原来的$mount }
Copy after login

在 Vue 2.0 版本中,所有 Vue 的组件的渲染最终都需要 render 方法,无论我们是用单文件 .vue 方式开发组件,还是写了 el 或者 template 属性,最终都会转换成 render 方法。这里的compileToFunctions就是把 template 编译为 render 的方法,后面会介绍。

// src/platform/weex/runtime/index.js Vue.prototype.$mount = function ( el?: string | Element, // 挂载的元素 hydrating?: boolean // 服务端渲染相关参数 ): Component { el = el && inBrowser ? query(el) : undefined // query就是document.querySelector方法 return mountComponent(this, el, hydrating) // 位于core/instance/lifecycle.js }
Copy after login

这里的el一开始如果不是DOM元素的话会被 query 方法换成DOM元素再被传给mountComponent方法,我们继续看mountComponent的定义:

// src/core/instance/lifecycle.js export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode } callHook(vm, 'beforeMount') // 调用beforeMount钩子 // 渲染watcher,当数据更改,updateComponent作为Watcher对象的getter函数,用来依赖收集,并渲染视图 let updateComponent updateComponent = () => { vm._update(vm._render(), hydrating) } // 渲染watcher, Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数 // ,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数 new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted) { callHook(vm, 'beforeUpdate') // 调用beforeUpdate钩子 } } }, true /* isRenderWatcher */) // 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例 if (vm.$vnode == null) { vm._isMounted = true // 表示这个实例已经挂载 callHook(vm, 'mounted') // 调用mounted钩子 } return vm }
Copy after login

mountComponent方法里实例化了一个渲染Watcher,并且传入了一个updateComponent,这个方法:() => { vm._update(vm._render(), hydrating) }首先使用_render方法生成VNode,再调用_update方法更新DOM。可以看看视图更新部分的介绍

这里调用了几个钩子,他们的时机可以关注一下。

3.3 编译 compile( )

如果在需要转换 render 的场景下,比如我们写的 template ,将会被 compiler 转换为 render 函数,这其中会有几个步骤组成:

File structure and operating mechanism of Vue source code

入口位于刚刚 src/platform/web/entry-runtime-with-compiler.js 的compileToFunctions方法:

// src/platforms/web/compiler/index.js const { compile, compileToFunctions } = createCompiler(baseOptions) export { compile, compileToFunctions }
Copy after login

继续看这里的createCompiler方法:

// src/compiler/index.js export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ): CompiledResult { const ast = parse(template.trim(), options) if (options.optimize !== false) { optimize(ast, options) } const code = generate(ast, options) return { ast, render: code.render, staticRenderFns: code.staticRenderFns } })
Copy after login

这里可以看到有三个重要的过程parseoptimizegenerate,之后生成了 render 方法代码。

  • parse:会用正则等方式解析 template 模板中的指令、class、style等数据,形成抽象语法树 AST

  • optimize:优化AST,生成模板AST树,检测不需要进行DOM改变的静态子树,减少 patch 的压力

  • generate:把 AST 生成 render 方法的代码

3.4 响应式化 observe( )

Vue作为一个MVVM框架,我们知道它的 Model 层和 View 层之间的桥梁 ViewModel 是做到数据驱动的关键,Vue的响应式是通过 Object.defineProperty 来实现,给被响应式化的对象设置getter/setter,当 render 函数被渲染的时候会触发读取响应式化对象的getter进行依赖收集,而在修改响应式化对象的时候会触发设置settersetter方法会notify它之前收集到的每一个watcher来告诉他们自己的值更新了,从而触发watcherupdatepatch更新视图。

File structure and operating mechanism of Vue source code

响应式化的入口位于 src/core/instance/init.js 的initState中:

// src/core/instance/state.js export function initState(vm: Component) { vm._watchers = [] const opts = vm.$options if (opts.props) initProps(vm, opts.props) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) } else { observe(vm._data = {}, true /* asRootData */) } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }
Copy after login

它非常规律的定义了几个方法来初始化propsmethodsdatacomputedwathcer,这里只看initData方法,来窥一豹

// src/core/instance/state.js function initData(vm: Component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {} observe(data, true /* asRootData */) // 给data做响应式处理 }
Copy after login

首先判断了下 data 是不是函数,是则取返回值不是则取自身,之后有一个observe方法对data进行处理,看看这个方法

// src/core/observer/index.js export function observe (value: any, asRootData: ?boolean): Observer | void { let ob: Observer | void ob = new Observer(value) return ob }
Copy after login

这个方法主要用data去实例化一个 Observer 对象实例,Observer 是一个 Class,Observer 的构造函数使用defineReactive方法给对象的键响应式化,它给对象的属性递归添加getter/setter,用于依赖收集和 notify 更新,这个方法大概是这样的

// src/core/observer/index.js function defineReactive (obj, key, val) { Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { /* 进行依赖收集 */ return val; }, set: function reactiveSetter (newVal) { if (newVal === val) return; notify(); // 触发更新 } }); }
Copy after login

3.5 视图更新 patch( )

File structure and operating mechanism of Vue source code

当使用defineReactive方法将对象响应式化后,当 render 函数被渲染的时候,会读取响应化对象的getter从而触发getter进行watcher依赖的收集,而在修改响应化对象的值的时候,会触发setter通知notify之前收集的依赖,通知自己已被修改,请按需重新渲染视图。被通知的watcher调用update方法去更新视图,位于上面介绍过的传递给new Watcher( )updateComponent方法中,这个方法会调用update方法去patch更新视图。

// src/core/instance/lifecycle.js let updateComponent updateComponent = () => { vm._update(vm._render(), hydrating) } // 渲染watcher, Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数 // ,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数 new Watcher(vm, updateComponent, noop, {...}, true /* isRenderWatcher */)
Copy after login

这个_render方法生成虚拟 Node,_update方法中的会将新的 VNode 与旧的 VNode 一起传入patch

// src/core/instance/lifecycle.js Vue.prototype._update = function(vnode: VNode, hydrating?: boolean) { // 调用此方法去更新视图 const vm: Component = this const prevVnode = vm._vnode vm._vnode = vnode if (!prevVnode) { // 初始化 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { //更新 vm.$el = vm.__patch__(prevVnode, vnode) } }
Copy after login

_update调用__patch__方法,它主要是对新老 VNode 进行比较patchVnode,经过 diff 算法得出它们的差异,最后这些差异的对应 DOM 进行更新。

以上就是本文的全部内容,希望对大家的学习有所帮助,更多相关内容请关注PHP中文网!

相关推荐:

Vue源码之依赖收集原理

关于react项目静态类型检查方案

The above is the detailed content of File structure and operating mechanism of Vue source code. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!