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 AST
  • optimize: Optimize AST, generate template AST tree, detect static subtrees that do not need DOM change, and reduce patch pressure
  • generateRender: 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

  1. Vue source code to read – file structure and operation mechanism
  2. Vue source code read – dependency collection principle
  3. 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:

  1. Vue2.1.7 source code learning
  2. Vue. Js technology revealed
  3. Analyze the internal operation mechanism of vue.js
  4. Vue. Js document
  5. [large dry goods] hand in hand with you over vUE part of the source code
  6. 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 ~