Before we implement Vue mini, let’s take a look at some concepts
1. Data-driven
- Data driven
- The core principles of responsiveness
- Publish subscribe mode and observer mode
Data driven
-
Data responsive, bidirectional binding, data driven
-
Data response
-
The data model is just JavaScript for ordinary objects, and when we modify the data, the view is updated, avoiding tedious DOM manipulation and improving development efficiency
-
Two-way binding
-
The data changes, the view changes, and so does the data
-
We can use v-Models to create two-way data binding on form elements
Data-driven is one of the most unique features of Vue
- How do you render to a view when you don’t need relational data
2, the core principle of data response Vue2
let data = {
msg: 'Cabbage'
}
// Simulate the Vue instance
let vm = {}
// Data hijacking, when accessing or setting a vm member, does some interference
Object.defineProperty(vm, 'msg', {
/ / can be enumerated
enumerable: true.// configurable (you can use delete fir tree or redefine via defineProperty)
configurable: true,
get () {
return data.msg
},
set (newValue) {
if (newValue === data.msg) {
return
}
data.msg = newValue
// Data changes, update the DOM value
document.querySelector("#app").textContent = data.msg
}
})
/ / test
vm.msg = 'Hello word'
console.log(vm.msg)
// Multiple attributes
let vm = {}
proxyData(data)
function proxyData(data) {
Object.keys(data).forEach(key= > {
Object.defineProperty(vm, key, {
enumerable: true.configurable: true,
get () {
console.log('set:', key, data[key])
return data[key]
},
set(newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
document.querySelector("#app").textContent = data[key]
}
})
})
}
// vm. MSG = 'Chinese'
// set: MSG
Copy the code
3, Vue response type principle Vue3
- MDN Proxy
- Listen directly on objects, not properties
- New in ES6, not supported by IE, performance is optimized by browser
let data = {
msg: 'hello'
}
let vm = new Proxy(data, {
get(target, key) {
return target[key]
},
// Setting the vm's membership will be performed
set(target, key, newValue) {
console.log('set', key, newValue)
if (target[key] === newValue) {
return
}
target[key] = newValue
document.querySelector('#app').textContent = target[key]
}
})
vm.msg = 'Cabbage'
console.log(vm.msg)
Copy the code
4. Publish and subscribe
- Publish/subscribe
- The subscriber
- The publisher
- The signal center
We assume that there is a “signal center” that publishes a signal when a task completes, and that other tasks subscribe to the signal center to know when they can start executing. This is called a “publish/schedule pattern” (publish-subscribe pattern)
let vm = new Vue()
// Register events (subscribe messages)
vm.$on('dataChange'.() = > {
console.log('dataChange')
})
vm.$on('dataChange'.() = > {
console.log('dataChange1')})// Trigger events (publish messages)
// Custom events
class EventEmitter {
constructor () {
this.subs = Object.create(null)}// Register events
$on (eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// Trigger the event
$emit (eventType) {
if (this.subs[eventType]) {
this.subs[eventType].forEach(handler= > {
handler()
})
}
}
}
// Test it
let em = new EventEmitter()
em.$on('click'.() = > {
console.log('click1')
})
em.$on('click'.function() {
console.log('click2')
})
em.$emit('click')
Copy the code
5. Observer mode
- Observer (subscriber) — Watcher
- Upload (): Specific things to do when an event occurs
- Target (publisher) –Dep
- Subs array: Stores all observers
- AddSub (): Adds an observer
- Notify (): When an event occurs, call the upload() method for all observers
- No event center
class Dep {
constructor () {
// Record all subscribers
this.subs = []
}
// Add an observer
addSub() {
if (sub && sub.update) {
this.subs.push(sub)
}
}
notify () {
this.subs.forEach(sub= > {
sub.update()
})
}
}
class Watcher {
update () {
console.log('update')}}// Test it
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
Copy the code
- The observer mode is scheduled by a specific target, and the Dep goes back and calls the observer’s methods, so there is a dependency between the observer mode’s subscribers and publishers
- The publish/subscribe pattern is invoked by the unified dispatch center, so publishers and subscribers do not need to be aware of each other’s existence
- As shown in figure
6. Simulation of Vue response type principle – analysis
- Vue basic structure
- Print Vue instance observation
- As shown in figure
Vue
- Inject the members in data into the Vue instance and convert the members in data into getters/setters
Observer
- The ability to listen for all attributes of the data object, get the latest values and notify Dep if there are changes
Ok, so much, finally to how to implement the mini version of vUE, now we will step by step to implement the mini version of vUE
7, Vue
- Responsible for receiving initialization parameters (options)
- Is responsible for injecting properties from Data into Vue instances and converting them into getters/setters
- Responsible for calling the Observer to listen for changes to all properties in data
- Responsible for calling compiler parse instructions/differential expressions
structure
+ $options
+ $el
+ $data
+ _proxyData
First add a vue.js file to the file
class Vue {
constructor (options) {
// 1. Save the data of the option through properties
// 2. Convert data members into getters and setters and inject them into Vue instances
// 3. Call observer to listen for data defense
// 4. Invoke the Compiler object to parse instructions and differential expressions
}
// Proxy data
_proxyData (data) {
}
}
Copy the code
The complete code
// vue.js
class Vue {
constructor (options) {
// 1. Save the data of the option through properties
this.$options = options || {}
this.$data = options.data || {}
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
// 2. Convert data members into getters and setters and inject them into Vue instances
this._proxyData(this.$data)
// 3. Call observer to listen for data defense
new Observer(this.$data)
// 4. Invoke the Compiler object to parse instructions and differential expressions
}
_proxyData (data) {
// Iterate over all attributes in data
Object.keys(data).forEach(key= > {
// Inject the data attribute into the Vue instance
Object.defineProperty(this, key, {
enumerable: true.configurable: true,
get () {
return data[key]
},
set(newValue) {
if (newValue === data[key]) {
return
}
data[key] = newValue
}
})
})
}
}
// Test it
// Console type vm to see if there are instances
Copy the code
At this point we need to use vue.js in index. HTML
<script src="vue.js"></script>
let vm = new Vue({
el: '#app'.data: {
msg: 'Hello Vue'.count: 100}})console.log(vm.msg)
Copy the code
Enter vm test on the console and you can see the member we just added
8 the Observer.
- Is responsible for converting properties in the Data option into responsive data
- A property in data is also an object, and that property is converted into responsive data
- Send notifications of data changes
structure
+ walk(data)
+ defineReactive(data, key, value)
Create a new observer.js and import it in index.html
// +walk(data)
// + defineReactive(data, key, value)
/ / structure
class Observer {
walk (data) {
}
defineReactive (obj, key, val) {
}
}
Copy the code
Complete code
class Observer {
constructor (data) {
this.walk(data)
}
walk (data) {
// 1. Check whether data is an object
if(! data ||typeofdata ! = ='object') {
return
}
// 2. Iterate over all properties of the data object
Object.keys(data).forEach(key= > {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
let that = this
// If val is an object, convert the attributes inside val to responsive data
this.walk(val)
Object.defineProperty(obj, key, {
enumerable: true.configurable: true,
get () {
return val
},
set (newValue) {
if (newValue === val) {
return
}
val = newValue
that.walk(newValue)
// Send notifications}}}})Copy the code
Test the
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Vue</title>
</head>
<body>
<div id="app">
<h1>Chinese cabbage</h1>
<h1>{{ msg }}</h1>
<h1>{{ count }}</h1>
<div>{{ name }}</div>
<h1>v-text</h1>
<div v-html="htmlStr"></div>
<div v-text="msg"></div>
<input type="text" v-model="msg">
<input type="text" v-model="count">
</div>
<script src="observer.js"></script>
<script src="vue.js"></script>
<script>
let vm = new Vue({
el: '#app'.data: {
msg: 'Hello Vue'.count: 100.person: {
name: 'zzzz'}}})console.log(vm.msg)
vm.msg = { test: 'Chinese cabbage' }
</script>
</body>
</html>
Copy the code
Remember to call Observer in step 3 of vue.js
new Observer(this.$data)
Copy the code
Enter VM on the console and see MSG as a responsive object
9, the Compiler
The Compiler function
- Responsible for compiling templates and parsing instruction/differential expressions
- Responsible for the first rendering of the page
- Re-render the view when the data changes
The Compiler structure
+ el
+ vm
+ compile(el)
+ compileElement(node)
+ compileText(node)
+ isDirective(arrrNode)
+ isTextNode(node)
+ isElementNode(node)
The new compiler. Js
// The compiler. Js method is implemented step by step
class Compiler {
constructor (vm) {
this.el = vm.$el
this.vm = vm
}
// Compile templates to handle text nodes and element nodes
compile (el) {
}
// Compile the element node to generate the instruction
compileElement (node) {
}
// Compile the text node to interpolate the expression
compileText (node) {
}
// Determine if element attributes are directives
isDirective(attrName) {
return attrName.startsWith('v-')}// Determine whether the node is a text node
isTextNode (node) {
return node.nodeType === 3
}
// Check whether the node is an element node
isElementNode (node) {
return node.nodeType === 1}}Copy the code
Compiler compile
class Compiler {
constructor (vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// Compile templates to handle text nodes and element nodes
compile (el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node= > {
// Process text nodes
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// Process element nodes
this.compileElement(node)
}
// Determine whether node has children. If there are children, recursively call compile
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
}
Copy the code
Compiler compileText
// Compile the text node to get the difference
compileText (node) {
// console.dir(node)
let reg = / \ {\ {(. +?) \} \} /
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
Copy the code
Compiler compileElement
// Compile the element node to generate the instruction
compileElement (node) {
console.log(node.attributes)
// Iterate over all attribute nodes
Array.from(node.attributes).forEach(attr= > {
// Check if it is a command
let attrName = attr.name
if (this.isDirective(attrName)) {
// v-text --> text
attrName = attrName.substr(2)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update (node, key, attrName) {
let updateFn = this[attrName + 'Updater']
updateFn && updateFn(node, this.vm[key])
}
Copy the code
10, Dep
function
- Collect dependencies and add observers
- Notify all observers
Dep structure
+ subs
+ addSubs(sub)
+ notify
New Dep. Js
class Dep {
constructor () {
// Store all observers
this.subs = []
}
// Add an observer
addSub (sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// Send notifications
notify () {
this.subs.forEach(sub= > {
sub.update()
})
}
}
Copy the code
After completing dep.js, we need to create the DEP object in defineReactive in observer.js
defineReactive (obj, key, val) {
let that = this
// Collect dependencies and send notifications
let dep = new Dep()
// If it is val, convert the attributes inside val to a responsive object
that.walk(val)
Object.defineProperty(obj, key, {
enumerable: true.configurable: true,
get () {
// Collect dependencies
Dep.target && dep.addSub(Dep.target)
return val
},
set (newValue) {
if (newValue === val) {
return
}
val = newValue
that.walk(newValue)
// Send notifications
dep.notify()
}
})
}
Copy the code
11, Watcher
function
- When data changes start dependent, DEP notifies all Watcher instances of update attempts
- Add yourself to the DEP object when instantiating itself
Watcher
+ vm
+ key
+ cb
+ oldValue
+ update
Create a new watcher.js file
class Watcher {
constructor (vm, key, cb) {
this.vm = vm;
// Attribute name in data
this.key = key;
// The callback is responsible for updating the view
this.cb = cb;
// Record the Watcher object to the Dep class's static property target
Dep.target = this;
// Trigger the get method and call addSub in the get method
this.oldValue = vm[key];
Dep.target = null
}
// Update the view when data changes
update () {
let newValue = this.vm[this.key];
// Determine whether the new value is equal to the old value
if (this.oldValue === newValue) {
return
}
this.cb(newValue)
}
}
Copy the code
We need compileText, textUpdater, modelUpdater in compile.js to create watcher objects
// Compile the text node to get the difference
compileText (node) {
// console.dir(node)
let reg = / \ {\ {(. +?) \} \} /
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// Create a Watcher object to update the view when data changes
new Watcher(this.vm, key, (newValue) = > {
node.textContent = newValue
})
}
}
// Process the V-text instruction
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) = > {
node.textContent = newValue
})
}
// v-model
modelUpdater (node, value, key) {
node.value = value;
new Watcher(this.vm, key, (newValue) = > {
node.value = newValue
})
}
/ / to the key
update (node, key, attrName) {
let updateFn = this[attrName + 'Updater']
updateFn && updateFn.call(this, node, this.vm[key], key)
}
Copy the code
At this point, the compile object is called in step 4 of vue.js, parsing the instructions and interpolation expressions
// 4. Invoke the Compiler object to parse instructions and differential expressions
new Compiler(this)
Copy the code
Open the console and see the following. At this point, the Mini version of vUE is almost complete
12. Bidirectional binding
Register events for input to implement bidirectional binding
// v-model
modelUpdater (node, value, key) {
node.value = value;
new Watcher(this.vm, key, (newValue) = > {
node.value = newValue
})
// Bidirectional binding
node.addEventListener('input'.() = > {
this.vm[key] = node.value
})
}
Copy the code
13, summary
Create a Vue object, record the options passed from options in the Vue constructor, and then call a Proxy data, that is, inject data into the Vue instance, create Observer and Compiler. The function of the Observer is data hijacking. In the Observer, the data property is converted into getters and setters. When the data changes, that is, when the set is triggered, the change needs to be notified and the notify method of the Dep is called. And the notify method of Dep calls the Update method in Watcher to notify Watcher that the data has changed and you’re going to update the view, and then the Update method in Watcher is going to update the view only when the data has changed, When the Watcher object is created, the current Watcher object is added to the SUBs array of the Dep to collect the dependencies, so that the Dep records the Watcher and then creates the Compiler object, which parses instructions, differential expressions, and when the page is first loaded. The relevant methods in Compiler are called to update the view, while the Compiler also subscribing to data changes and binding update functions. When creating a Watcher object, a callback function is passed to update the view. Note that the view is updated in the Compiler for the first time. Watcher updates the view when data is sent for changes.
The completed code has been submitted to Github
Please click here
Thank you for your
Thank you for taking the time to read this article. If you find it helpful, please give it a thumbs up. (Thanks for your encouragement and support 🌹🌹🌹)