I.MVVM framework three elements: data responsiveness, template engine and its rendering
1. Data responsiveness: monitor data changes and update in an attempt
- object.defineProperty
- proxy
2. Template engine: Provides template syntax for describing views
- The interpolation: {{}}
- Instructions: V-bind, V-ON, V-model, V-for, V-if
3. Render: How do I convert templates to HTML
- Template => vdom => dom
vue add vuex
Copy the code
2. Principle of data response
- Simple implementation
const obj = {}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}:${val}`);
return val
},
set(newVal) {
if(newVal ! == val) { console.log(`set ${key}:${newVal}`);
val = newVal
}
}
})
}
defineReactive(obj, 'foo'.'foo')
obj.foo
obj.foo = 'foooooooooooo'
Copy the code
- Combined with the view
<! DOCTYPE html> <html lang="en">
<head></head>
<body>
<div id="app"></div>
<script>
const obj = {}
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}:${val}`);
return val
},
set(newVal) {
if(newVal ! == val) { val = newVal update() } } } ) } defineReactive(obj,'foo'.' ')
obj.foo = new Date().toLocaleTimeString()
function update() {
app.innerText = obj.foo
}
setInterval(() => {
obj.foo = new Date().toLocaleTimeString()
}, 1000);
</script>
</body>
</html>
Copy the code
- Iterate over objects that need to be reactive
// Object responsivity: iterates over each key and defines getters and settersfunctionObserve (obj) {// Check that the obj type must be an objectif(typeof obj ! = ='object' || obj == null) {
return
}
Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
}
Copy the code
- Resolve nested objects
function defineReactive(obj, key, val) {
observe(val)
Object.defineProperty(obj, key, {
//...
Copy the code
- Resolve the case where the assigned value is an object
obj.baz = {a:1}
obj.baz.a = 10 // no ok
set(newVal) {
if(newVal ! == val) { console.log('set', newVal); Observe (newVal) val = newVal}}Copy the code
- New attributes cannot be detected if added/removed
obj.dong = 'dong'Obj. dong // does not have a get message // write onesetMethod and the data to do reactive processingfunction set(obj, key, val) {defineReactive(obj, key, val)set(obj, 'dong'.'dong')
obj.dong
Copy the code
Responsivity of data applied to VUE
The principle of analysis
- NewVue () performs initialization first, performing reactive processing on data, which occurs in Observe
- At the same time, the template is compiled, the dynamically bound data is found, the view is obtained from the data and initialized, this process happens in compile
- Define both an update function and watcher, which watcher will call in the future when the data changes
- Because keys in date can be called multiple times in a view, each key requires a butler to pipe multiple Watcher
- Once the data changes in the future, the corresponding Dep will be found first and all Watcher will be informed to perform the update function
Involved Types
- Vue: frame constructor
- Observer: Perform data reactivity (here you need to distinguish between arrays and objects)
- Compile: Compile templates, initialize views, rely on collections (update function, created by Watcher)
- Watcher: Execute update function (update DOM)
- Dep: Manage multiple Watcher, batch update
The source code to achieve
Step 1: Frame constructor: Perform initialization
- Create a VUE instance
Class Vue {constructor(options) {// Save this option.$options = options
this.$dataOptions. data // Respond to observe(this).$data// proxy proxy(this) // new Compile('#app', this)
}
}
Copy the code
- Perform initialization to perform reactive processing on data
// Object responsive processingfunctionObserve (obj) {// Check that the obj type must be an objectif(typeof obj ! = ='object' || obj == null) {
return} // For each responsive object, New Observer(obj)} constructor(value) {this.value = value // This.walk (value)} walk(obj) {object.keys (obj).foreach (key => defineReactive(obj, key, obj[key]))}}functionDefineReactive (obj, key, val) {// Do the same}Copy the code
- Proxy for $data
/ / will be$dataKey in to the KVue instancefunction proxy(vm) {
Object.keys(vm.$data).forEach(key => {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key]
},
set(v) {
vm.$data[key] = v
}
})
})
}
Copy the code
Step 2: compile –compile
- Create an instance of compile
class Compile {
constructor(el, vm) {
this.$vm = vm
this.$el= document.querySelector(el) // Compile the templateif (this.$el) {
this.compile(this.$el}} // compile(el) {el.childnodes. ForEach (node => {// determine its typeif (this.isElement(node)) {
// console.log('Compile element', node.nodeName);
this.compileElement(node)
} else if (this.isInter(node)) {
// console.log('Compile interpolation', node.textContent);
this.compileText(node)
}
if(node.childNodes) {this.compile(node)}})} // Interpolate text to compileText(node) {// get matching expression // node.textContent = this.$vm[RegExp.The $1]
this.update(node, RegExp.The $1.'text')} // Get node attributes compileElement(node) {const nodeAttrs = node.attributes array. from(nodeAttrs).foreach (attr => {// v-xxx="aaa"Const attrName = attr.name // v-xxx const exp = attr.value // aaa // Determine the attribute typeif(this.isdirective (attrName)) {const dir = attrname.substring (2) // This [dir] && this[dir](node, exp)}else if(attrName.startWith(The '@')) {/ /}})} / / text text instructions (node, exp) {enclosing the update (node, exp.'text')
}
html(node, exp) {
this.update(node, exp, 'html'Update (node, exp, dir) {// textUpdater() // initialize const fn = this[dir +'Updater']
fn && fn(node, this.$vm// Update: new Watcher(this).$vm, exp, function(val) { fn && fn(node, val) }) } textUpdater(node, value) { node.textContent = value } htmlUpdater(node, Value) {node.innerhtml = value} // element isElement(node) {returnNode.nodetype === 1} {{xx}} isInter(node) {return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
isDirective(attrName) {
return attrName.indexOf('v-') === 0}}Copy the code
Step 3: Rely on collection
A view uses a key from data, which is called a dependency. The same key may appear multiple times, and each time they need to be collected and maintained with a Watcher, a process called dependency collection. Multiple Watchers require a Dep to manage and are notified uniformly when updates are needed.
- Create a Dep instance for each key when defineReactive
- Initialize the view to read a key, such as name1, and create a watcher1
- Since the getter method for Name1 is triggered, watcher1 is added to the Dep corresponding to Name1
- When name1 updates and the setter fires, it can be notified through the corresponding Dep to manage all Watcher updates
Code parsing
- Create a Watcher
// Watcher: A dependency in the interface corresponds to a Watcher class Watcher {constructor(VM, key, {this.vm = vm this.key = key this.updateFn = updateFn; Trigger get() dep.target = this this.vm[this.key] dep.target = null} // butler call in defineReactiveupdateCall (this.vm, this.vm[this.key])}} () {this.updatefn. call(this.vm, this.vm[this.key])}}Copy the code
- Write the update function and instantiate Watcher
// Call the update function to interpolate text to compileText(node) {// console.log(RegExp).The $1);
// node.textContent = this.$vm[RegExp.The $1];
this.update(node, RegExp.The $1.'text')
}
text(node, exp) {
this.update(node, exp, 'text')
}
html(node, exp) {
this.update(node, exp, 'html')
}
update(node, exp, dir) {
const fn = this[dir + 'Updater']
fn && fn(node, this.$vm[exp])
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val)
})
}
textUpdater(node, val) {
node.textContent = val;
}
htmlUpdater(node, val) {
node.innerHTML = val
}
Copy the code
- The statement Dep
class Dep {
constructor() {
this.deps = []
}
addDep(dep) {
this.deps.push(dep)
}
notify() { this.deps.forEach(dep => dep.update()); }}Copy the code
- Trigger getter when creating watcher
Class constructor {constructor(vm, key, updateFn) { Get () dep.target = this this.vm[this.key] dep.target = null}}Copy the code
- Rely on the collection to create Dep instances
defineReactive(obj, key, val){
this.observe(val);
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
Dep.target && dep.addDep(Dep.target);
return val
},
set(newVal) {
if (newVal === val) return
dep.notify()
}
})
}
Copy the code