Creation time: 2020-09-11
Mainly understand and implement the following methods:
- Observe : a listener that listens for property changes and notifies subscribers
- Watch : Subscribers receive property changes and update the view
- Compile : parser parsing directives, initializing templates, binding subscribers, binding events
- Dep : store all corresponding watcher instances
Main execution process
The process of loading the watcher into the subscription list of the corresponding dep instance
Relevant html code for parsed binding data
The code here is the template to be parsed in compile, parsing the commands such as v-model {{}} v-on
<div id="app"> <p> Name:<input type="text" v-model="name"></p> <p>student ID:<input type="text" v-model="number"></p> <p><span>student ID:</span> <span>{{ number }}</span></p> <p><span>computed accomplish:</span> <span>{{ getStudent }}</span></p> <p> <button v-on:click="setData">event binding</button> </p> </div>
observer code
Add get, set for data data to add watcher, and create Dep instance to notify update view
const defineProp = function(obj, key, value) { observe(value) /* * Create a unique dep for each different property in advance */ const dep = new Dep() Object.defineProperty(obj, key, { get: function() { /* * Created according to different properties and only called when the Watcher is created */ if(Dep.target) { dep.targetAddDep() } return value }, set: function(newVal) { if(newVal !== value) { /* * The assignment operation here is to facilitate the return of the value in the get method, because the get method will be called immediately after the assignment */ value = newVal /* * Notify all subscribers of the listening property */ dep.notify() } } }) } const observe = function(obj) { if(!obj || typeof obj !== 'object') return Object.keys(obj).forEach(function(key) { defineProp(obj, key, obj[key]) }) }
Dep code
Mainly put the watcher into the corresponding dep subscription list
let UUID = 0 function Dep() { this.id = UUID++ // Store all listening watcher s of the current property this.subs = [] } Dep.prototype.addSub = function(watcher) { this.subs.push(watcher) } // The purpose is to pass the current dep instance to the watcher Dep.prototype.targetAddDep = function() { // Here this is the instantiated dep Dep.target.addDep(this) } Dep.prototype.notify = function() { // Fire all watcher s for the current property this.subs.forEach(_watcher => { _watcher.update() }) } Dep.target = null
Watcher code
After the data is updated, update the view
function Watcher(vm, prop, callback) { this.vm = vm this.prop = prop this.callback = callback this.depId = {} this.value = this.pushWatcher() } Watcher.prototype = { update: function() { /* update value change */ const value = this.vm[this.prop] const oldValue = this.value if (value !== oldValue) { this.value = value this.callback(value) } }, // The purpose is to receive the dep instance, which is used to put the current watcher instance into subs addDep: function(dep) { if(!this.depId.hasOwnProperty(dep.id)) { dep.addSub(this) this.depId[dep.id] = dep.id } else { console.log('already exist'); } }, pushWatcher: function() { // Storage Subscriber Dep.target = this // Trigger the get monitoring of the object, and add this assigned to target above to subs var value = this.vm[this.prop] // delete after adding Dep.target = null return value } }
Compile code
Parse html templates, create code snippets, bind data events
function Compile(vm) { this._vm = vm this._el = vm._el this.methods = vm._methods this.fragment = null this.init() } Compile.prototype = { init: function() { this.fragment = this.createFragment() this.compileNode(this.fragment) // When the content of the code snippet is compiled, insert it into the DOM this._el.appendChild(this.fragment) }, // Create document fragments based on real DOM nodes createFragment: function() { const fragment = document.createDocumentFragment() let child = this._el.firstChild while(child) { // After adding a node to the document fragment, the node will be deleted from its original position, which is equivalent to moving the node position fragment.appendChild(child) child = this._el.firstChild } return fragment }, compileNode: function(fragment) { let childNodes = fragment.childNodes; [...childNodes].forEach(node =>{ if(this.isElementNode(node)) { this.compileElementNode(node) } let reg = /\{\{(.*)\}\}/ // Get all text content under the node let text = node.textContent // Determine whether it is a plain text node and whether the text content has {{}} if(this.isTextNode(node) && reg.test(text)) { let prop = reg.exec(text)[1].trim() this.compileText(node, prop) } if(node.childNodes && node.childNodes.length) { // Compile child nodes recursively this.compileNode(node) } }) }, compileElementNode: function(element) { // Get attributes, only element nodes have the following methods let nodeAttrs = element.attributes; [...nodeAttrs].forEach(attr => { let name = attr.name if(this.isDirective(name)) { /* * v-model on a tag that accepts input events */ let prop = attr.value if (name === 'v-model') { /* * The obtained value is the data that needs to be bound */ this.compileModel(element, prop) } else if(this.isEvent(name)) { /* * bind event */ this.bindEvent(element, name, prop) } } }) }, compileModel: function(element, prop) { let val = this._vm[prop] this.updateElementValue(element, val) new Watcher(this._vm, prop, value => { this.updateElementValue(element, value) }) element.addEventListener('input', event => { let newValue = event.target.value if (val === newValue) return this._vm[prop] = newValue }) }, compileText: function(textNode, prop) { let text = '' if(/\./.test(prop)) { var props = prop.split('.') text = this._vm[props[0]][props[1]] } else { text = this._vm[prop] } this.updateText(textNode, text) console.log(text); new Watcher(this._vm, prop, (value) => { this.updateText(textNode, value) }) }, bindEvent: function(element, name, prop) { var eventType = name.split(':')[1] var fn = this._vm._methods[prop] element.addEventListener(eventType, fn.bind(this._vm)) }, /* * Determine if an attribute is an instruction */ isDirective: function (text) { return /v-/.test(text) }, isEvent: function(text) { return /v-on/.test(text) }, isElementNode: function(node) { // element node returns 1 text node (text in element or attribute) 3 attribute node 2 (deprecated) return node.nodeType === 1 }, isTextNode: function(node) { return node.nodeType === 3 }, updateElementValue: function(element, value) { element.value = value || '' }, updateText: function(textNode, value) { textNode.textContent = value || '' } }
vue brief constructor
Mainly realizes two-way binding of data, custom events, computed
function FakeVue(options) { this._data = options.data this._methods = options.methods this._computed= options.computed this._el = document.querySelector(options.el) // Proxy the attributes in _data to the outer vm, here only the first layer attributes of _data are proxied Object.keys(this._data).forEach(key => { this._proxyData(key) }) this._initComputed() this._init() } FakeVue.prototype._init = function() { // Begin to recursively listen to all properties of the object until the property value is a value type observe(this._data) new Compile(this) } FakeVue.prototype._proxyData = function(key) { Object.defineProperty(this, key, { get: function() { return this._data[key] }, set: function (value) { this._data[key] = value } }) } FakeVue.prototype._initComputed = function() { // Simple implementation: just mount the value to keep up const computed = this._computed Object.keys(computed).forEach((v) => { Object.defineProperty(this, v, { get: computed[v], set: function (value) {} }) }) }
Create a vue instance
try{ let vm = new FakeVue({ el: '#app', data: { name: 'warren', number: '10011', score: { math: 90 } }, computed: { getStudent: function() { return `${this.name}: student number is ${this.number}` } }, methods:{ // By binding events to elements in compile setData: function() { alert('name: '+this.name); } } }); } catch(error) { console.error(error) }
Epilogue
This is a simple example of vue implementation principle from the author's understanding point of view, I hope it will help you who are exploring
In this example, the main complication is the parsing of the html template, the two-way binding of the data.
It is recommended to follow the execution order of the code to understand the whole process. The code of key points has necessary comments. If you find any problems, please correct them.
Finally attach vue Source address , mainly concerned with the core and compiler files;
Welcome to exchange Github