Data driven
- Data response
The data in the data response is the data model, which is just an ordinary javascript object. When we modify the data, the view will be updated, which avoids tedious DOM operation and improves development efficiency.
- Two-way binding
Data changes, view changes; Try to change, and the data will change
V-model can be used to create two-way data binding on form elements
- Data-driven is one of vUE’s most unique features
The development process is focused on the data itself, not how the data is rendered to the view. Okay
The core principle of data response
vue.2x–Object.defineProperty
When you pass a normal JavaScript object to a Vue instance as the data option, Vue will iterate through all the properties of that object, Use object.defineProperty to turn all of these properties into getters/setters. Object.defineproperty is a non-shim feature in ES5, which is why Vue does not support IE8 and earlier browsers.
<! DOCTYPEhtml>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DefineProperty Basic use - adapt a property to a getter and setter</title>
</head>
<body>
<div id="app">
hello
</div>
<script>
// Simulate the data option in Vue
let data = {
msg: 'hello'
}
// Simulate an instance of Vue
let vm = {}
// Data hijacking: Do some intervention when accessing or setting vm members
Object.defineProperty(vm, 'msg', {
// Enumerable (traversable)
enumerable: true.// Configurable (can be deleted with delete, can be redefined with defineProperty)
configurable: true.// Execute when a value is obtained
get () {
console.log('get: ', data.msg)
return data.msg
},
// When a value is set
set (newValue) {
console.log('set: ', newValue)
if (newValue === data.msg) {
return
}
data.msg = newValue
// Update the DOM value when the data changes
document.querySelector('#app').textContent = data.msg
}
})
/ / test
vm.msg = 'Hello World'
console.log(vm.msg)
</script>
</body>
</html>
Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DefineProperty Multiple members</title>
</head>
<body>
<div id="app">
hello
</div>
<script>
// Simulate the data option in Vue
let data = {
msg: 'hello'.count: 10
}
// Simulate an instance of Vue
let vm = {}
proxyData(data)
function proxyData(data) {
// Iterate over all properties of the data object
Object.keys(data).forEach(key= > {
// Convert attributes in data to setters/setters for vm
Object.defineProperty(vm, key, {
enumerable: true.configurable: true,
get () {
console.log('get: ', key, data[key])
return data[key]
},
set (newValue) {
console.log('set: ', key, newValue)
if (newValue === data[key]) {
return
}
data[key] = newValue
// Update the DOM value when the data changes
document.querySelector('#app').textContent = data[key]
}
})
})
}
/ / test
vm.msg = 'Hello World'
console.log(vm.msg)
</script>
</body>
</html>
Copy the code
vue3.x–Proxy
Proxy, used in ve3. X (new in ES6, not IE, performance optimized by browser), listens directly on objects, not properties.
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Proxy</title>
</head>
<body>
<div id="app">
hello
</div>
<script>
// Simulate the data option in Vue
let data = {
msg: 'hello'.count: 0
}
// Simulate the Vue instance
let vm = new Proxy(data, {
// The function that performs the agent behavior
// When accessing members of the VM will be executed
get (target, key) {
console.log('get, key: ', key, target[key])
return target[key]
},
// When setting vm members will be executed
set (target, key, newValue) {
console.log('set, key: ', key, newValue)
if (target[key] === newValue) {
return
}
target[key] = newValue
document.querySelector('#app').textContent = target[key]
}
})
/ / test
vm.msg = 'Hello World'
console.log(vm.msg)
</script>
</body>
</html>
Copy the code
Publish subscribe and observer patterns
Publish subscribe model
Assume that there is a “signal center”, a task is completed, to the signal center “publish” (subscribe) a signal, other tasks to the signal center “subscribe” (subscribe) this signal, so as to know when to start to execute, this is the publish subscribe mode.
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Vue User-defined events</title>
</head>
<body>
<script src="./js/vue.js"></script>
<script>
// Create a vue instance
let vm = new Vue()
// Register event (subscription message)
vm.$on('dataChange'.() = > {
console.log('dataChange')
})
vm.$on('dataChange'.() = > {
console.log('dataChange1')})// Trigger event (publish message)
vm.$emit('dataChange')
</script>
</body>
</html>
Copy the code
Sibling component communication process
// eventBus.js
// Event center
let eventHub = new Vue()
// ComponentA.vue
/ / publisher
addTodo: function() {
// Publish a message (event)
eventHub.$emit('add-todo', { text: this.newTodoText })
this.newTodoText = ' '
}
// ComponentB.vue
/ / subscriber
created: function() {
// Subscribe to message (event)
eventHub.$on('add-todo'.this.addTodo)
}
Copy the code
Simulate the implementation of vUE custom events
<! DOCTYPEhtml>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Publish subscribe model</title>
</head>
<body>
<script>
// Event triggers
class EventEmitter {
constructor () {
// object.create (null) This method sets the Object with no stereotype properties, which can improve performance
this.subs = Object.create(null)}// Register the event
$on (eventType, handler) {
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
// Triggers 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')
</script>
</body>
</html>
Copy the code
Observer mode
In contrast to the publish/subscribe pattern, the observer pattern has no event center, only the subscriber and the publisher, and the publisher needs to know that the subscriber exists.
The Watcher itself has an update method that is called for all subscribers when an event occurs. In vUE’s responsive mechanism, the observer’s update method is called when the data changes. The update method internally updates the view.
In observer mode, the subscriber’s update method is called by the publisher. The target (i.e., the publisher, Dep) keeps track of all subscribers within the publisher, and the publisher notifishes all subscribers when an event occurs. The publisher needs an internal attribute subs to keep track of all subscribers. This attribute is an array to which all observers that depend on the publisher need to be added, so you need to add an addSub() to the observer. This method adds observers to the array. The publisher also needs a notify() method to call update() on all observers when the event occurs.
<! DOCTYPEhtml>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<title>Observer mode</title>
</head>
<body>
<script>
// 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()
</script>
</body>
</html>
Copy the code
conclusion
The observer pattern is scheduled by a specific target, such as the current event, and Dep calls the observer method, so there is a dependency between the observer pattern subscriber and the publisher.
The publish/subscribe pattern is invoked by a unified scheduling center, so publishers and subscribers do not need to be aware of each other’s existence.
Simulation of vUE response principle
Vue
- function
- Responsible for receiving initialization parameters (options)
- Responsible for injecting properties from data into Vue instances, turning them into getters/setters
- Is responsible for calling an observer to listen for changes to all properties in the data
- Responsible for calling compiler parsing instructions/interpolation
- code
// vue.js
class Vue {
constructor (options) {
// 1. Save the data of the option through the attribute
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 instance
this._proxyData(this.$data)
// 3. Call the Observer object to listen for data changes
new Observer(this.$data)
// 4. Call the Compiler object to parse the instructions and difference expressions
new Compiler(this)}// Proxy properties
_proxyData (data) {
// Iterate over all attributes in data
Object.keys(data).forEach(key= > {
// Insert 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
}
})
})
}
}
Copy the code
Observer
- function
- Is responsible for converting the properties in the Data option to responsive data
- A property in data is also an object, and that property is converted to reactive data
- Data change notification is sent
- code
// observer.js
// Responsible for data hijacking
// Convert the member in $data to a getter/setter
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
// Create a DEP object that collects dependencies and sends notifications
let dep = new Dep()
// If val is an object, convert the properties inside val to reactive data
this.walk(val)
Object.defineProperty(obj, key, {
enumerable: true.configurable: true.// getter
get () {
// Collect dependencies
Dep.target && dep.addSub(Dep.target)
return val
},
// setter
set (newValue) {
if (newValue === val) {
return
}
val = newValue
that.walk(newValue)
// Send a notification when the data changes
dep.notify()
}
})
}
}
Copy the code
Compiler
- function
- Responsible for compiling templates, parsing instructions/interpolation
- Responsible for the first rendering of the page
- Rerender the view when the data changes
- code
// compiler.js
// Responsible for parsing instructions/interpolation
class Compiler {
constructor (vm) {
this.el = vm.$el
this.vm = vm
// Compile the template
this.compile(this.el)
}
// Compile the template 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)
}
// Check whether the node has children. If there are children, call compile recursively
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
/* compileElement handles the first render of the V-text and the first render of the V-Model */
// Compile element nodes to process instructions
compileElement (node) {
// console.log(node.attributes)
// Iterate over all attribute nodes
Array.from(node.attributes).forEach(attr= > {
// Get the name of the element attribute
let attrName = attr.name
// Check whether the current attribute name is a directive
if (this.isDirective(attrName)) {
// attrName 的形式 v-text v-model
// Capture the name of the property and get the text Model
attrName = attrName.substr(2)
let key = attr.value
// Handle different instructions
this.update(node, key, attrName)
}
})
}
// Responsible for updating the DOM
/ / create a Watcher
update (node, key, attrName) {
// node, attribute name of key data, second half of dir directive
let updateFn = this[attrName + 'Updater']
// Because we use this in textUpdater and so on
updateFn && updateFn.call(this, node, this.vm[key], key)
}
// Process the V-text instruction
textUpdater (node, value, key) {
node.textContent = value
// Create a watcher object for each instruction/interpolation to monitor data changes
new Watcher(this.vm, key, (newValue) = > {
node.textContent = newValue
})
}
// Update the data when the view changes
// Process the v-model instruction
modelUpdater (node, value, key) {
node.value = value
// Create a watcher for each instruction to watch the data change
new Watcher(this.vm, key, (newValue) = > {
node.value = newValue
})
// Bind both ways to listen for changes in the view
node.addEventListener('input'.() = > {
this.vm[key] = node.value
})
}
// Compile the text node to handle differential expressions
compileText (node) {
// {{ msg }}
let reg = / \ {\ {(. +?) \} \} /
// Get the contents of the text node
let value = node.textContent
if (reg.test(value)) {
// The value in the interpolation is the name of the property
let key = RegExp.$1.trim()
// Replace the interpolation with a concrete value
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
})
}
}
// Determine whether an element attribute is a directive
isDirective (attrName) {
return attrName.startsWith('v-')}// Check 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
Dep
- function
- Collect dependencies, add Watcher
- Inform all observers
- 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 a notification
notify () {
this.subs.forEach(sub= > {
sub.update()
})
}
}
Copy the code
Watcher
- function
- When data changes trigger dependencies, DEP informs all Watcher instances to update the view
- Adds itself to the DEP object when it instantiates itself
// watcher.js
class Watcher {
constructor (vm, key, cb) {
this.vm = vm
// Attribute name in data
this.key = key
// Call cb to update the view when the data changes
this.cb = cb
// Record the current watcher object on the static property of the Dep and add the watcher to the SUBs of the Dep when data is accessed
Dep.target = this
// Trigger a getter that tells DEp to record watcher for the current key
// Triggers the GET method, which calls addSub
this.oldValue = vm[key]
/ / clear the target
Dep.target = null
}
// Update the view when the data changes
update () {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) {
return
}
this.cb(newValue)
}
}
Copy the code
debugging
Use debugging to deepen your understanding of the code
- Debug the process of first rendering the page
- Debugging data changes the process of updating a view
<! DOCTYPEhtml>
<html lang="cn">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Mini Vue</title>
</head>
<body>
<div id="app">
<h1>Difference expression</h1>
<h3>{{ msg }}</h3>
<h3>{{ count }}</h3>
<h1>v-text</h1>
<div v-text="msg"></div>
<h1>v-model</h1>
<input type="text" v-model="msg">
<input type="text" v-model="count">
</div>
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
<script>
let vm = new Vue({
el: '#app'.data: {
msg: 'Hello Vue'.count: 100.person: { name: 'zs'}}})console.log(vm.msg)
// vm.msg = { test: 'Hello' }
vm.test = 'abc'
</script>
</body>
</html>
Copy the code
conclusion
- Review the overall process through the diagram below
- Vue
- Record the options passed in and set data/data/data/ EL
- Inject the data member into the Vue instance
- Responsible for calling the Observer to implement responsive data processing (data hijacking)
- Responsible for calling Compiler instructions/interpolation, etc
- Observer
- The data was hijacked
- Converts members of data into getters/setters
- Responsible for converting multi-level properties into getters/setters
- If you assign a property to a new object, set the member of the new object to be a getter/setter
- Add dependencies between Dep and Watcher
- Data change notification is sent
- The data was hijacked
- Compiler
- Responsible for compiling templates, parsing instructions/interpolation
- Responsible for the first rendering process of the page
- Re-render when data changes
- Dep
- Collect dependencies, add subscribers (Watcher)
- Notify all subscribers
- Watcher
- Adds itself to the DEP object when it instantiates itself
- Dep notifishes all Watcher instances to update the view when data changes