Data driven
- Data response
- The data model is just a normal JavaScript object, and as we modify the data, the view is updated, eliminating tedious DOM manipulation and improving development efficiency
- Two-way binding
- Data changes, view changes; As the view changes, so does the data
- We can use
v-model
Create two-way data binding on form elements
- Data-driven is one of the most unique features of Vue
- You need to focus on the data itself, not how the data is rendered to the view. Okay
Data response principle Vue2
Cn.vuejs.org/v2/guide/re…
Data response principle Vue3
- Developer.mozilla.org/zh-CN/docs/…
- From zero to achieve Vue3.0 reactive source www.bilibili.com/video/BV1AQ…
Observer pattern and subscriber publisher pattern
Publish subscribe pattern – publish-subscribe pattern
Subscriber, publisher, signal center
// Simple implementation
// Event trigger
class EventEmitter {
constructor () {
// { 'click': [fn1, fn2], 'change': [fn] }
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
let em = new EventEmitter()
em.$on('click'.() = > {
console.log('click1')
})
em.$on('click'.() = > {
console.log('click2')
})
em.$emit('click')
Copy the code
Observer model
- Observer (Subscriber) – Watcher
- Update (): Exactly what to do when an event occurs
- Target (publisher) -dep
- Subs array: Stores all observers
- AddSub (): Adds an observer
- Notify (): When an event occurs, the update() method of all observers is called
- No event center
// Publisher - target
class Dep {
constructor () {
// Record all subscribers
this.subs = []
}
// Add subscribers
addSub (sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// Issue a notification
notify () {
this.subs.forEach(sub= > {
sub.update()
})
}
}
// Subscriber - observer
class Watcher {
update () {
console.log('update')}}/ / test
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
Copy the code
parsing
vue.js
* Function * is responsible for receiving initialized parameters (options) * Is responsible for injecting properties from 'data' into 'Vue' instances, Convert to 'getter/setter' * is responsible for calling 'observer' to listen for changes to all properties in 'data' * is responsible for calling 'Compiler' to parse index/difference expressions * structure * properties * $options * $EL * $data * _proxyData()Copy the code
observer.js
* Function * is responsible for converting properties in the 'data' option into responsive data. * An attribute in the 'data' option is also an object. Convert this property to reactive data * data change send notification * Structure * Property * None * Method * walk(data) * defineReactive(data, key, value)Copy the 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
compiler.js
- 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
- structure
- attribute
- el
- vm
- methods
- compile(el)
- compileElement(node)
- compileText(node)
- isDirective(attrName)
- isTextNode(node)
- isElementNode(node)
- attribute
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)
}
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// Compile element node, process instruction
compileElement(node) {
// 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])
}
// Process the V-text instruction
textUpdater(node, value) {
node.textContent = value
}
/ / processing v - model
modelUpdater(node, value) {
node.value = value
}
// Compile the text node to handle the difference expression
compileText(node) {
let reg = / \ {\ {(. +?) \} \} /
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
// 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
}
// Determine if the node is an element node
isElementNode(node) {
return node.nodeType == 1}}Copy the code
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()
})
}
}
// observer.js
defineReactive(obj, key, val) {
let that = this
// Collect dependencies and send notifications
let dep = new Dep()
// ...
Object.defineProperty(obj, key, {
get() {
// ...
// Collect dependencies
Dep.target && Dep.addSub(Dep.target)
return val
},
set(newValue) {
// ...
// Send notifications
dep.notify()
}
})
}
Copy the code
watcher.js
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
// Key is the 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 static property target
Dep.target = this
// Trigger the get method, in which addSub is called
this.oldValue = vm[key]
Dep.target = null
}
// Update the view when data changes
update () {
let newValue = this.vm[this.key]
if (this.oldValue == newValue) {
return
}
this.cb(newValue)
}
}
Copy the code
Watcher’s collection needs to be in compiler
compileText
// Compile the text node to handle the difference expression
compileText(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
new Watcher(this.vm, key, (newVal) = > {
node.textContent = newVal
})
}
}
Copy the code
textUpdater
和modelUpdater
// Process the V-text instruction
textUpdater(node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) = > {
node.textContent = newValue
})
}
/ / processing v - model
modelUpdater(node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) = > {
node.value = newValue
})
}
Copy the code
Two-way binding
/ / processing v - model
modelUpdater(node, value, key) {
node.value = value
// ...
// Bidirectional binding
node.addEventListener('input'.() = > {
this.vm[key] = node.value
})
}
Copy the code