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) //
root,
refs - InitEvents (VM) // Handles events and callbacks passed by the parent component
- initRender(vm) //
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()