Also do not know which wave, study source code has become a deep understanding of the standard. I just want to say, you’re right

The preparatory work

  1. Download the vue source from GitHub (github.com/vuejs/vue)

  2. Learn about Flow, facebook’s JavaScript static type checker. Vue.js source using Flow to do static type checking

  3. Voe.js source directory design,vue.js source code are in SRC directory (vue-dev SRC) SRC ├── ─ core # Core Code ├── Platforms # Support ├── ├─ SFC #.vue File Parsing ├─ shared #

    ** Core directory: ** contains the core code of vue.js, including built-in components, global API encapsulation, Vue instantiation, observer, virtual DOM, utility functions, and more. The code here is the soul of vue.js

    **Vue.js is a cross-platform MVVM framework that can run on the Web or with WeeX on Natvie clients. Platform is an entry point to vue.js, and two directories represent the two main entry points, packaged into vue.js running on the Web and weex. For example, now the more popular MPvue framework is actually in this directory below a small program running platform related content.

  1. The lifecycle of VUe2.0 is divided into four main processes: 4.1 Create: Creating a NEW Vue(Vue) is created first. – DOM will be generated based on attributes such as el, template, and render methods, and added to the corresponding location. 4.3 Update: Updates the DOM when data changes. 4.4 DEstory: Destruction: Executed during destruction

What happens to new Vue()

The first in the vue lifecycle is new vue () to create an instance of vue, which corresponds to the source code in \vue-dev\ SRC \core\instance\index.js

import { initMixin } from './init' import { stateMixin } from './state' import { renderMixin } from './render' import { eventsMixin } from './events' import { lifecycleMixin } from './lifecycle' import { warn } from '.. /util/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)  } initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue) export default VueCopy the code

As you can see from the code in index.js, function vue is actually a function, which implements class in ES5. Function vue also adds if judgment, indicating that the vue must be instantiated by the new keyword. One question is why vUE is not defined in es6 style. The answer can be found by looking at the following method.

Function vue defines many mixins and passes the vue class as arguments to initMixin(vue), from import {initMixin} from ‘./init’

export function initMixin (Vue: Class<Component>) { Vue.prototype._init = function (options? : Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV ! == 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) }Copy the code

The initMixin method mounts a _init method on the vue prototype. Other mixins also mount methods on the vue prototype. The original vue class was created using es5 function for flexibility. The method can be written to each JS file, rather than one at a time, which is more convenient for the maintenance of the code later. This is also the reason for choosing ES5 creation.

When new Vue is called, the _init method on the Vue prototype is actually called.

Vue initialization basically does a few things, merging configurations, initializing life cycles, initializing event center, initializing render, initializing Data, props, computed, Watcher, and so on

Vue’s bidirectional binding principle

From the index.js entry analysis, the more to find that the citation between the various files is not disorderly cut disorderly, so from the original source code into the imitation of writing prototype, this way may understand more profound, and we encourage each other.

Two-way data in VUE is implemented through data hijacking (Object.defineProperty()) in conjunction with the publiser-subscriber pattern. Object.defineproperty () directly defines a new property on an Object or modifies an existing property, And return this object.

Use Object. DefineProperty ()

As a small example, define a svue.js file, define a book object, and assign a value to the output

Var Book = {name: 'vue authoritative guide '}; console.log(Book.name); // VUE authoritative guideCopy the code

The result is the “Vue Authority guide”. What if you want to append the title to the book while console.log(book.name) is executed? This can be done using Object.defineProperty(), the modified code

var Book = {} var name = ''; Object.defineProperty(Book, 'name', { set: function (value) { name = value; Console. log(' You took a title called '+ value'); }, get: function () {return ' '+ name +' '}}) book.name = 'vue '; // You took a title called vue Authoritative Guide console.log(book.name); // VUE authoritative GuideCopy the code

Object.defineproperty () overrides get and set for the name property of the Book Object, triggering the GET execution when the name property is accessed.

Hands-on simulation of write data bidirectional binding

MVVM implementation consists of two main aspects, data changes update the view, view changes update data. Do it in 3 steps:

(1) Implement a listener Observer that hijks and listens for all attributes, notifying the subscriber that the Observer is a data listener. The core implementation method is object.defineProperty (). If you want to listen on all attributes, you can recursively traverse all attribute values and Object.defineProperty() on them.

Corresponding to the source directory: / vue – dev/SRC/core/observer/index. Js

function defineReactive(data, key, val) { observe(val); DefineProperty (data, key, {enumerable: true, 64x: true, get: function() {return val; }, set: function(newVal) { val = newVal; Console. log(' property '+ key +' has been listened on, now value: '+ newval.tostring () +' '); }}); } function observe(data) { if (! data || typeof data ! == 'object') { return; } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; var library = { book1: { name: '' }, book2: '' }; observe(library); Library.book1. name = 'Vue Authoritative Guide '; Library. Book2 = 'there is no such book '; // The book2 property has been listened on, and now values: "No such book"Copy the code

Since there are many subscribers, we need to have a message subscriber Dep to collect these subscribers and manage them centrally between the listener Observer and the subscriber Watcher, so we need to modify the code

function defineReactive(data, key, val) { observe(val); Var dep = new dep (); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: Function () {if (dep.target) {dep.addSub(dep.target); function() {if (dep.target) {dep.addSub(dep.target); // Add a subscriber here} return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; Console. log(' property '+ key +' has been listened on, now value: '+ newval.tostring () +' '); dep.notify(); // Notify all subscribers if data changes}}); } function observe(data) { if (! data || typeof data ! == 'object') { return; } Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function Dep () { this.subs = []; Prototype = {addSub: function(sub) {this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); }};Copy the code

In setter functions, if the data changes, all subscribers are notified, and the subscribers execute the corresponding updated function

(2) Implement a subscriber Watcher, which can receive notification of attribute changes and execute corresponding functions to update the view

function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; this.value = this.get(); Prototype = {update: function() {this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value ! == oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { Dep.target = this; Var value = this.vm.data[this.exp]; // return value; }};Copy the code

The simple version of Watcher has been designed. By associating the Observer with Watcher, you can implement a simple two-way binding of data, defining index.js

function SelfVue (data, el, exp) { this.data = data; // Pass in {} object data observe(data); // Listen for el.innerhtml = this.data[exp]; // This. Data [name] // subscriber function update will call new watcher (this, exp, function (value) { el.innerHTML = value; }); return this; }Copy the code

Define index. HTML to introduce the above 3 JS files for testing

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <meta HTTP-equiv =" x-UA-compatible "content=" IE =edge"> <title>Document</title> </head> <body> <h1 id="name">{{name}}</h1> </body> <script src="observer.js"></script> <script src="watcher.js"></script> <script src="index.js"></script> <script type="text/javascript"> var ele = document.querySelector('#name'); var selfVue = new SelfVue({ name: 'hello world' }, ele, 'name'); Window.settimeout (function () {console.log('name value changed '); selfVue.data.name = '66666666'; }, 2000); </script> </html>Copy the code

(3) to implement a parser Compile, you can scan and analyze related instructions of each node, and according to the initial template data and initializes the corresponding subscriber Although it has achieved an example of a two-way data binding, but the whole process didn’t go to parse the dom node, but fixed a node to replace the data directly, So the next step is to implement a parser Compile to do the parsing and binding

function Compile(el, vm) { this.vm = vm; this.el = document.querySelector(el); this.fragment = null; this.init(); } Compile.prototype = { init: function () { if (this.el) { this.fragment = this.nodeToFragment(this.el); this.compileElement(this.fragment); this.el.appendChild(this.fragment); } else {console.log('Dom element does not exist '); } }, nodeToFragment: function (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; While (child) {// move the Dom element into the fragment. Fragmentchild (child); child = el.firstChild } return fragment; }, compileElement: function (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; Self.com pileText(node, reg.exec(text)[1]) if (self.istextNode (node) &&reg.test (text)) {self.compileText(node, reg.exec(text)[1]); self.compileText(node, reg.exec(text)[1]); self.compileText(node, reg.exec(text)[1]) } if (node.childNodes && node.childNodes.length) { self.compileElement(node); // continue recursively through the child node}}); }, compileText: function(node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); New Watcher(this.vm, exp, function (value) {// Generate the subscriber and bind the update function self.updatetext (node, value); }); }, updateText: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, isTextNode: function(node) { return node.nodeType == 3; }}Copy the code

Modified index. Js

function SelfVue (options) { var self = this; this.vm = this; this.data = options.data; Object.keys(this.data).forEach(function(key) { self.proxyKeys(key); }); observe(this.data); new Compile(options.el, this.vm); return this; } SelfVue.prototype = { proxyKeys: function (key) { var self = this; Object.defineProperty(this, key, { enumerable: false, configurable: true, get: function proxyGetter() { return self.data[key]; }, set: function proxySetter(newVal) { self.data[key] = newVal; }}); }}Copy the code

Modified index. HTML

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <meta HTTP-equiv =" x-UA-compatible "content=" IE =edge"> <title>Document</title> </head> <body> <div id="app"> <h2>{{title}}</h2> <h1>{{name}}</h1> </div> </body> <script src="observer.js"></script> <script src="watcher.js"></script> <script src="compile.js"></script> <script src="index.js"></script> <script type="text/javascript"> var selfVue = new SelfVue({ el: '#app', data: { title: 'hello world', name: '' } }); Selfvue.title = 'hello '; window.setTimeout(function () {selfvue.title =' hello '; }, 2000); window.setTimeout(function () { selfVue.name = 'canfoo'; }, 2500); </script> </html>Copy the code

Now that you can parse the contents of {{}}, continue to refine compile. Js if you want to support more instructions

function Compile(el, vm) { this.vm = vm; this.el = document.querySelector(el); this.fragment = null; this.init(); } Compile.prototype = { init: function () { if (this.el) { this.fragment = this.nodeToFragment(this.el); this.compileElement(this.fragment); this.el.appendChild(this.fragment); } else {console.log('Dom element does not exist '); } }, nodeToFragment: function (el) { var fragment = document.createDocumentFragment(); var child = el.firstChild; While (child) {// move the Dom element into the fragment. Fragmentchild (child); child = el.firstChild } return fragment; }, compileElement: function (el) { var childNodes = el.childNodes; var self = this; [].slice.call(childNodes).forEach(function(node) { var reg = /\{\{(.*)\}\}/; var text = node.textContent; if (self.isElementNode(node)) { self.compile(node); } else if (self.isTextNode(node) && reg.test(text)) { self.compileText(node, reg.exec(text)[1]); } if (node.childNodes && node.childNodes.length) { self.compileElement(node); }}); }, compile: function(node) { var nodeAttrs = node.attributes; var self = this; Array.prototype.forEach.call(nodeAttrs, function(attr) { var attrName = attr.name; if (self.isDirective(attrName)) { var exp = attr.value; var dir = attrName.substring(2); Self.com pileEvent(node, self.vm, exp, dir) if (self.iseventDirective (dir)) {self.iseventDirective self.compileEvent(node, self.vm, exp, dir); } else {self.compileModel(node, self.vm, exp, dir); } node.removeAttribute(attrName); }}); }, compileText: function(node, exp) { var self = this; var initText = this.vm[exp]; this.updateText(node, initText); new Watcher(this.vm, exp, function (value) { self.updateText(node, value); }); }, compileEvent: function (node, vm, exp, dir) { var eventType = dir.split(':')[1]; var cb = vm.methods && vm.methods[exp]; if (eventType && cb) { node.addEventListener(eventType, cb.bind(vm), false); } }, compileModel: function (node, vm, exp, dir) { var self = this; var val = this.vm[exp]; this.modelUpdater(node, val); new Watcher(this.vm, exp, function (value) { self.modelUpdater(node, value); }); node.addEventListener('input', function(e) { var newValue = e.target.value; if (val === newValue) { return; } self.vm[exp] = newValue; val = newValue; }); }, updateText: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; }, modelUpdater: function(node, value, oldValue) { node.value = typeof value == 'undefined' ? '' : value; }, isDirective: function(attr) { return attr.indexOf('v-') == 0; }, isEventDirective: function(dir) { return dir.indexOf('on:') === 0; }, isElementNode: function (node) { return node.nodeType == 1; }, isTextNode: function(node) { return node.nodeType == 3; }}Copy the code

Modified index. HTML

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, <meta HTTP-equiv =" x-UA-compatible "content=" IE =edge"> <title>Document</title> </head> <body> <div id="app"> <h2>{{title}}</h2> <input v-model="name"> <h1>{{name}}</h1> </div> </body> <script src="observer.js"></script>  <script src="watcher.js"></script> <script src="compile.js"></script> <script src="index.js"></script> <script type="text/javascript"> var selfVue = new SelfVue({ el: '#app', data: { title: 'hello world', name: '' } }); </script> </html>Copy the code

You can see what the V-Model looks like

To be continued