background

Vue data bidirectional binding is implemented through data hijacking combined with the publisher-subscriber pattern. First, data hijacking listens, so we need to set up a listener Observer to listen for all properties. If the property changes, Watcher needs to be told to update it. Since there are many subscribers, we need to have a message subscriber Dep that collects these subscribers and manages them uniformly between the listener Observer and the subscriber Watcher. Then, we also need an instruction parser Compile to scan and parse each node element, initialize the corresponding instruction into a subscriber Watcher, and replace template data or bind the corresponding function. At this time, when the subscriber Watcher receives the corresponding attribute change, it will execute the corresponding update function. Update the view, and then implement the process

kvue.js

Function defineReactive(obj, key, val) {// Define (val); Const Dep = new Dep() object.defineProperty (obj, key, {get() {console.log("get", key); Dep.target && dep.adddep (dep.target) return val; }, set(newVal) { if (newVal ! == val) { observe(newVal); console.log("set", key); val = newVal; // update() dep.notify() } }, }); Function observe(obj) {if (typeof obj! == "object" || obj === null) { return; } // Create an Observer instance new Observer(obj); Class Observer {constructor(obj) {if (constructor(obj) {if (obj (obj)) {if (obj (obj)) {if (obj (obj))) {if (obj (obj))) (array.isarray (obj)) {// todo} else {// This. Walk (obj); } } walk(obj) { Object.keys(obj).forEach((key) => { defineReactive(obj, key, obj[key]); }); } } function proxy(vm) { Object.keys(vm.$data).forEach((key) => { Object.defineProperty(vm, key, { get() { return vm.$data[key]; }, set(v) { vm.$data[key] = v; }}); }); } class KVue { // new KVue({el, data}) constructor(options) { this.$options = options; this.$data = options.data; Observe (this.$data); // 1.5 proxy proxy(this); // 2. Compile new Compile(options.el, this); } } class Compile { constructor(el, vm) { this.$vm = vm; this.$el = document.querySelector(el); If (this.$el) {this.compile(this.$el); this.compile(this.$el); ForEach ((node) => {if (this.iselm (node)) {console.log(" compile element ", node.nodeName); this.compileElement(node); } else if (this.isinter (node)) {// console.log(' Compile interpolating text ', Node.textContent); this.compileText(node); } // recursion if (node.childnodes.length > 0) {this.compile(node); }}); } isElm(node) {return node.nodeType === 1; } / / interpolation judgment isInter (node) {return node. The nodeType = = = 3 && / \ {\ {(. *) \} \} /. The test (node. TextContent); } // {{ooxx}} => ooxx => this.$vm.ooxx compileText(node) { this.update(node, RegExp.$1, 'text')} // Element compileElement(node) {const attrs = node.attributes; Array.from(attrs).forEach((attr) => { // k-text="counter" const attrName = attr.name; // k-text const exp = attr.value; // directive -directive if (this.isdir (attrName)) {const dir = attrname.substring (2); this[dir] && this[dir](node, exp); }}); } update(node, exp, dir) { // 1. Const fn = this[dir+'Updater'] fn && fn(node, this.$vm[exp]) // 2 New Watcher(this.$vm, exp, function (val) {fn &&fn (node, val) }) } isDir(attrName) { return attrName.startsWith("k-"); } // k-text text(node, exp) { this.update(node, exp, 'text') } textUpdater(node, val) { node.textContent = val; } //k-model model(node, exp) { this.update(node, exp, Node.addeventlistener ('input',e=>{this.$vm[exp] = e.target.value})} modelUpdater(node, e=>{this.$vm[exp] = e.target.value})} val) { node.value = val; } // k-html html(node, exp) { this.update(node, exp, 'html') } htmlUpdater(node, val) { node.innerHTML = val; }} // update executor Watcher class constructor {constructor(vm, key, constructor) Updater) {this.vm = vm this.key = key this.updater = updater // Save Watcher reference dep.target = this this.vm[this.key] Dep.target = null } update() { this.updater.call(this.vm, this.vm[this.key]) } } class Dep { constructor() { this.deps = [] } addDep(dep) { this.deps.push(dep) } notify() { this.deps.forEach(w => w.update()) } }Copy the code

khtml

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <div id="app"> <p @click="add">{{counter}}</p> <p k-text="counter"></p> <p k-html="desc"></p> <input type="text" k-model="content"> <h2 k-html="content"></h2> </div> <script src="./kvue.js"></script> <script> const app = New KVue({el: '#app', data: {counter: 1, desc: 'wait ·<span style="color: red"> { add() { this.counter++ } }, }) setInterval(() => { app.counter++ // app.$data.counter++ }, 1000); </script>Copy the code

How does vue mutate array methods

The source code

import { def } from ".. /util/index"; const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); const methodsToPatch = [ "push", "pop", "shift", "unshift", "splice", "sort", "reverse", ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method]; Def (arrayMethods, method, function mutator(... Args) {// args passed arr.push (33) args is [33] const result = original. Apply (this, args); const ob = this.__ob__; console.log(ob,args); let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); break; } if (inserted) ob.observeArray(inserted); // notify change ob.dep.notify(); return result; }); }); // In other files, Global search can see / / Observer. The prototype. ObserveArray = function observeArray (items) {/ / for (var I = 0, l = items. Length; i < l; i++) { // observe(items[i]); / / / /}};Copy the code

To put it simply,Vue overwrites the array’s seven methods through prototype interception. First, it gets the array’s OB, which is its Observer object. If there is a new value, it calls observeArray to listen for the new value, and then manually calls Notify to render The watcher, perform the update

Vue initialization process

Platforms /web/entry-runtime-with-compiler.js Extensions default $mount methods: handle template or EL options

platforms/web/runtime/index.js

  • Install Web platform-specific directives and components
  • Define __Patch__ : the patch function that performs the patching algorithm for updates
  • $mount: Mount the vue instance to the specified host element (get the DOM and replace the host element)

Core /index.js initializes the global API as follows:

  • Vue.set = set
  • Vue.delete = del
  • Vue.nextTick = nextTick
  • InitUse (Vue) // Implements the vue. use function
  • InitMixin (Vue) // Implements the vue. mixin function
  • InitExtend (Vue) // Implements the vue.extend function
  • InitAssetRegisters (Vue) / / registered Vue.com ponent/directive/filter

The core/instance/index.js Vue constructor defines the Vue instance API

Function Vue (options) {// constructor only _init this._init(options)} initMixin(Vue) // implement init function stateMixin(Vue) // state related API $data,$props,$set,$DELETE,$watch eventsMixin(Vue)// Eventrelated API $ON,$once,$off,$emit lifecycleMixin(Vue) // Lifecycle API $destroy renderMixin(Vue)// Render API _render,$nextTickCopy the code

Core /instance/init.js creates component instances, initializes their data, properties, events, and so on

  • initLifecycle(vm) //
    p a r e n t , parent,
    root,
    c h i l d r e n , children,
    refs
  • InitEvents (VM) // Handles events and callbacks passed by the parent component
  • initRender(vm) //
    s l o t s , slots,
    scopedSlots,_c,$createElement
  • callHook(vm, ‘beforeCreate’)
  • InitInjections (VM) // Get injection data
  • InitState (VM) // Initialize props, methods, data, computed, watch
  • InitProvide (VM) // Provides data injection
  • callHook(vm, ‘created’)

$mount

  • mountComponent

Perform the mount, retrieve the VDOM, and convert it to dom

  • new Watcher()

Create component render watcher

  • updateComponent()

Perform initialization or update

  • update()

Initialization or update, which converts the passed VDOM to a DOM and performs a DOM creation operation at initialization

  • render() src\core\instance\render.js

Render the component and get the VDOM

New Vue() => _init() => $mount() => mountComponent() => new Watcher() => updateComponent() => Render () => _update()