Vue is currently one third of the country’s front-end Web end, but also as one of my main technology stack, in daily use, also curious about why, in addition to the recent emergence of a large number of vUE source code reading articles, I take this opportunity to draw some nutrition from everyone’s article and discussion, At the same time, some of the ideas of reading source code are summarized, produce some articles, as the output of their own thinking, my level is limited, welcome to leave a message to discuss ~
Target Vue version: 2.5.17-beta.0
Vue source note: github.com/SHERlocked9…
Note: the syntax of the source code in the article uses Flow, and the source code is truncated as required (in order not to be confused @_@), if you want to see the full version of the github address above, this is a series of articles, the article address is at the bottom of ~
If you are interested, you can join the wechat group at the end of the article and discuss with us
0. Prepare knowledge
- Flow
- ES6 grammar
- Common design patterns
- The idea of functional programming such as Coriolization
Static type-checking tool Flow, An introduction to ECMAScript 6, Currization in JS, JS Observer mode, Function caching using higher-order functions (Note mode)
1. File structure
The file structure is given in vue CONTRIBUTING. Md.
├ ─ ─ scripts -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains the scripts and configuration files related to build │ ├ ─ ─ alias. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - source module used to import the alias │ ├ ─ ─ config. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the project build configuration ├ ─ ─ build -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build relevant documents, Usually we don't need to move ├ ─ ─ dist -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build output directory file after ├ ─ ─ examples -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- To store some application cases using Vue development ├ ─ ─ flow -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- JS static type checking tools (flow) (https://flowtype.org/) type declaration ├ ─ ─ Package. The json ├ ─ ─test-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the test file ├ ─ ─ the SRC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- source directory │ ├ ─ ─ the compiler -------------------------- compiler code, Used to compile the template to render function │ │ ├ ─ ─ parser -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to store the template string into its elements of the abstract syntax tree code │ │ ├ ─ ─ codegen -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to deposit from the abstract syntax tree (AST) to generate the render function code │ │ ├ ─ ─ optimizer. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- static analysis tree, Optimization vdom rendering │ ├ ─ ─ the core -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- general storage, platform-independent runtime code │ │ ├ ─ ─ the observer -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the response type, Contains the data observed at the core of the code │ │ ├ ─ ─ vdom -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the creation of virtual DOM and patching code │ │ ├ ─ ─ the instance -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the Vue constructor code associated with the prototype │ │ ├ ─ ─ global API -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to the Vue constructor mount global method (static method) or attribute code │ │ ├ ─ ─ Components -------------------- contains abstracted generic components, Only keep alive - │ ├ ─ ─ server -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the service side rendering (server side rendering) of the relevant code │ ├ ─ ─ platforms -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- different platform-specific code │ │ ├ ─ ─ weex -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- weex platform support │ │ ├ ─ ─ the web -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the web platform support │ │ │ ├ ─ ─ entry - runtime. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - the runtime building entrance │ │ │ ├ ─ ─ Entry running-with-Compiler. js - │ │ ├─ Entry-Compiler. js --------------- vue-template-compiler package entry file │ │ ├── Template-compiler. js │ ├ ─ ─ entry - server - the renderer. Js -- -- -- -- -- -- -- -- the vue - server - the renderer package entry documents │ ├ ─ ─ SFC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - Contains a single file components (. Vue file) parsing logic, used in vue - the template - the compiler package │ ├ ─ ─ Shared -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the entire code base generic codeCopy the code
A few important directories:
- Compiler: Used to convert template to render function
- Core: Vue core code, including responsive implementation, virtual DOM, Vue instance method mount, global methods, abstract out of the common components, etc
- Platform: Entry files for different platforms, mainly the Web platform and WEEX platform. Different platforms have their own special build process. Of course, our focus is on the Web platform
- Server: Code related to server side rendering (SSR). SSR mainly renders components directly to HTML and is directly provided by the server side to the Client side
- SFC: mainly the logic of.vue file parsing
- Shared: Several common utility methods, some of which are set up to increase code readability
Dist /vue.runtime.esm.js; Dist /vue.runtime.esm.js; dist/vue.runtime.esm.js; CJS way output dist/vue.runtime.com mon. Js, UMD way o dist/vue. Runtime. Js. SRC /platforms/web/entry-runtime-with-compiler.js file as an entry to the runtime build, dist/vue. ESM, dist/vue. CJS output dist/vue.com common.js, UMD output dist/vue.js, including compiler
2. Import files
Any front-end project can be viewed from the package.json file, starting with its script.dev command line when we run NPM run dev:
"scripts": {
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev"
}
Copy the code
Rollup is a JS module wrapper similar to WebPack. In fact, vuE-V1.0.10 used WebPack before it was changed to rollup. If you want to know why it was changed to rollup, you can see the answer from You. In general, the package size is smaller and the initialization speed is faster.
You can see here that scripts/config.js is rolled up and given TARGET:web-full-dev. Let’s see what’s inside scripts/config.js
// scripts/config.js
const builds = {
'web-full-dev': {
entry: resolve('web/entry-runtime-with-compiler.js'), // Import file
dest: resolve('dist/vue.js'), // Output file
format: 'umd'.// See the following compilation instructions
env: 'development'./ / environment
alias: { he: './entity-decoder' }, / / alias
banner // Comments in front of each package - version/author/date.etc}},Copy the code
Format Compilation method description:
- Es: es Modules, using ES6 template syntax output
- CJS: CommonJs Module, file output following the CommonJs Module specification
- Amd: AMD Module, following the AMD Module specification file output
- Umd: Supports the output of files with external chain specifications, which can be directly used with script tags
Web-full-dev corresponds to the command we just passed in the command line, so rollup will start packing the following entry file, as well as many other commands and various output methods and formats to check the source code.
Therefore, the main focus of this article is on the SRC /platforms/web/entry-runtime-with-compiler.js file that contains the compiler compiler, In production and development environments, vue-loader is used to compile the template without the compiler package, but it is recommended to start with the compiler entry file for a better understanding of the principles and processes.
Let’s take a look at this file, where we import a Vue, and see where it came from
// src/platforms/web/entry-runtime-with-compiler.js
import Vue from './runtime/index'
Copy the code
Continue to look at
// src/platforms/web/runtime/index.js
import Vue from 'core/index'
Copy the code
keep moving
// src/core/index.js
import Vue from './instance/index'
Copy the code
keep moving*2
// src/core/instance/index.js
/* Here is the vue constructor, ES6 Class syntax is not used because mixin module partition is convenient */
function Vue(options) {
this._init(options) // Init method, located in initMixin
}
// The following mixins are mounted on vue. prototype
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export default Vue
Copy the code
When we new Vue(), we actually call this constructor, so we can start here.
3. Operation mechanism
Here I use Xmind to draw a rough diagram of the operation mechanism, basically the following analysis is in some parts of this diagram
The Vue examples in this article are represented in terms of VMS
This figure can be divided into many parts of the fine reading, the specific implementation of our detailed discussion in the following article, here first posted a part of the source taste fresh
3.1 Initializing _init()
After new Vue() in main.js, Vue calls the constructor’s _init() method, which is defined in the initMixin() method of core/instance/index.js
// src/core/instance/index.js
/* Here is the Vue constructor */
function Vue(options) {
this._init(options) // Init method, located in initMixin
}
// The following mixins are mounted to vue. prototype, which was already mounted when it was loaded
initMixin(Vue) Prototype add: _init...
stateMixin(Vue) Prototype: $data, $props, $set, $delete, $watch...
eventsMixin(Vue) Prototype: $on, $once, $off, $emit, $watch...
lifecycleMixin(Vue) Prototype add: _update, $forceUpdate, $destroy...
renderMixin(Vue) Prototype: $nextTick, _render,...
export default Vue
Copy the code
We can take a look at what the init() method does:
// src/core/instance/index.js
Vue.prototype._init = function(options? : Object) {
const vm: Component = this
initLifecycle(vm) / / SRC/core/instance initialization life cycle/lifecycle. Js
initEvents(vm) / / SRC/core/instance initialization events/events. Js
initRender(vm) / / initialization render SRC/core/instance/render. Js
callHook(vm, 'beforeCreate') // Call the beforeCreate hook
initInjections(vm) / / initialization injection value before the data/props SRC/core/instance/inject. Js
initState(vm) / / mount data/props/methods/watcher/computed
initProvide(vm) // Initialize Provide after data/props
callHook(vm, 'created') // Calls the created hook
if (vm.$options.el) { // $options can be considered as the options we passed to 'new Vue(options)'
vm.$mount(vm.$options.el) / / $mount method}}Copy the code
The _init() method performs a series of initialization Settings on the current VM instance. The most important thing is to initialize the State method, initState(VM), and to initialize the data/props method. Dependency Collection is a Dependency Collection on which data changes drive view changes by setting getters/setters for responsive objects via object.defineProperty ().
$options, if yes, mount the vm using the vm.$mount method to form a connection between the data layer and the view layer. This is why you need to manually mount the vm.$mount(‘#app’) yourself if the EL option is not provided.
We see that the Created hook is called before $mount is mounted, so we can’t manipulate the DOM until the Created hook fires because it hasn’t been rendered to the DOM yet.
3.2 Mounting $mount()
The mount method vm.$mount() is defined in several places, depending on the packaging method and platform, SRC/platform/web/entry – the runtime – with – compiler. Js, SRC/platform/web/runtime/index, js, SRC/platform/weex/runtime/index. The js, Our focus is on the first file, but the entry-runtime-with-compiler.js file will save the $mount method in Runtime /index.js first and run it with call at the end:
// src/platform/web/entry-runtime-with-compiler.js
const mount = Vue.prototype.$mount / / the original $mount preserved, located in the SRC/platform/web/runtime/index, js
Vue.prototype.$mount = function(el? : string | Element,//Mounted element hydrating? : boolean//Server render parameters) :Component {
el = el && query(el)
const options = this.$options
if(! options.render) {// If the render method is not defined
let template = options.template
// Convert the obtained template to the render function by compilation
if (template) {
const{ render, staticRenderFns } = compileToFunctions(template, {... },this)
options.render = render
}
}
return mount.call(this, el, hydrating) // execute the original $mount
}
Copy the code
In Vue 2.0, all Vue components will eventually require the Render method, whether we develop the component in a single.vue file or write an EL or template property, which will eventually be converted to render. CompileToFunctions are methods that compile template to render, as we’ll see later.
// src/platform/weex/runtime/index.js
Vue.prototype.$mount = function (el? : string | Element,//Mounted element hydrating? : boolean//Server render parameters) :Component {
el = el && inBrowser ? query(el) : undefined // Query is the document.querySelector method
return mountComponent(this, el, hydrating) / / is located in the core/instance/lifecycle. Js
}
Copy the code
The el that is not a DOM element is initially replaced by a DOM element by the query method and passed to the mountComponent method. Let’s look at the mountComponent definition:
// 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') // Call beforeMount hook
// Render watcher. When data changes, updateComponent acts as the getter for the Watcher object, relies on the collection, and renders the view
let updateComponent
updateComponent = (a)= > {
vm._update(vm._render(), hydrating)
}
// Render watcher, watcher does two things here, one is to execute the callback function when initialized
// The other is to execute the callback function when the monitored data in the VM instance changes
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted) {
callHook(vm, 'beforeUpdate') // Invoke the beforeUpdate hook}}},true /* isRenderWatcher */)
$vnode represents the parent virtual Node of the Vue instance, so if it is Null, it is the instance of the root Vue
if (vm.$vnode == null) {
vm._isMounted = true // Indicates that the instance is already mounted
callHook(vm, 'mounted') // Call mounted hook
}
return vm
}
Copy the code
Instantiate a render Watcher in the mountComponent method and pass in an updateComponent: () => {vm._update(vm._render(), hydrating)} first use the _render method to generate vNodes and then call the _update method to update the DOM. Take a look at the view update section
There are several hooks called, and their timing can be looked at.
3.3 Compile ()
If you need to render a scene, such as the template we wrote, the compiler will convert it into the render function, which consists of several steps:
The entry to the compileToFunctions method is just SRC /platform/web/ entry-Runtime-with-Compiler. js:
// src/platforms/web/compiler/index.js
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
Copy the code
Continue looking at the createCompiler method here:
// 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 the code
Here you can see the three important processes parse, optimize, generate, and then generate the render method code.
parse
: Parses data such as instructions, classes, and styles in the Template template using regular methods to form an abstract syntax tree ASToptimize
: Optimize AST, generate template AST tree, detect static subtrees that do not need DOM change, and reduce patch pressuregenerate
Render: generates the AST code for the render method
Observe ()
As an MVVM framework, we know that ViewModel, the bridge between Model layer and View layer, is the key to achieve data-driven Vue. The responsiveness of Vue is realized through Object.defineProperty. Set getters/setters for reactive objects. When the render function is rendered, the getter for the reactive object will be read for dependency collection. When the render function is modified, the setter will be set. Setter methods notify each watcher they previously collected to tell them that their value has been updated, triggering watcher’s update to patch the view.
Response to a type of entrance is located in the 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 the code
It regularly defines several methods to initialize props, methods, data, computed, and wathcer, and just look at the initData method
// 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 */) // Perform reactive processing on data
}
Copy the code
First check whether data is a function. If it is, take the return value and if it is not, take itself. Then there is an observe method to process 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 the code
This method uses data to instantiate an Observer object instance. Observer is a Class. The Observer constructor uses defineReactive to define the key reactivity of the object. It adds getters/setters recursively to the properties of the object for dependency collection and notify updates, which looks something like this
// src/core/observer/index.js
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.get: function reactiveGetter () {
/* Do dependency collection */
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
notify(); // Trigger the update}}); }Copy the code
3.5 Updating the patch in the View
When the object is reactive using defineReactive, when the render function is rendered, the getter of the reactive object is read to trigger the watcher dependent collection, and when the value of the reactive object is modified, This triggers the setter to notify the dependencies that were collected by notify, notifying itself that it has been modified and rerender the view as needed. The notified Watcher calls the update method to update the view, as described above in the updateComponent method passed to new Watcher(), which calls the update method to patch the view.
// src/core/instance/lifecycle.js
let updateComponent
updateComponent = (a)= > {
vm._update(vm._render(), hydrating)
}
// Render watcher, watcher does two things here, one is to execute the callback function when initialized
// The other is to execute the callback function when the monitored data in the VM instance changes
newWatcher(vm, updateComponent, noop, {... },true /* isRenderWatcher */)
Copy the code
The _render method generates the virtual Node, and the _update method passes the new VNode along with the old VNode to the patch
// src/core/instance/lifecycle.js
Vue.prototype._update = function(vnode: VNode, hydrating? : boolean) { // Call this method to update the view
const vm: Component = this
const prevVnode = vm._vnode
vm._vnode = vnode
if(! prevVnode) {/ / initialization
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
/ / update
vm.$el = vm.__patch__(prevVnode, vnode)
}
}
Copy the code
_update calls the __patch__ method, which mainly compares the new and old VNodes. Patchvnodes are obtained directly through the diff algorithm. Finally, the corresponding DOM of these differences is updated.
We have a general idea of how a Vue works from the instantiation of a constructor. We will discuss each part of it later
This article is a series of articles that will be updated later to make progress together
- Vue source code to read – file structure and operation mechanism
- Vue source code read – dependency collection principle
- Vue source code read – batch asynchronous update and nextTick principle
Online posts are mostly different in depth, even some inconsistent, the following article is a summary of the learning process, if you find mistakes, welcome to comment out ~
Reference:
- Vue2.1.7 source code learning
- Vue. Js technology revealed
- Analyze the internal operation mechanism of vue.js
- Vue. Js document
- [large dry goods] hand in hand with you over vUE part of the source code
- MDN – Object.defineProperty()
PS: Welcome to pay attention to my official account [front-end afternoon tea], come on together
In addition, you can join the “front-end afternoon tea Exchange Group” wechat group, long press to identify the following TWO-DIMENSIONAL code to add my friend, remarks add group, I pull you into the group ~