Step by step implementation of bidirectional data binding
1. reduce() method of array
Application scenario: the initial value of the next operation, which depends on the return value of the last operation
(1) Cumulative calculation of array
Common implementation
const arr = [1,2,3,4,5,6] let total = 0; arr.forEach(item=>{ total+=item; }) console.log(total);
Implemented through reduce
Basic knowledge
- It is implemented using the reduce() method
- Usage: array. Reduce (function, initial value)
- Loop through the current array, focusing on "snowball"
- Array. Reduce ((result of last calculation, item item item of current cycle) = > {}, 0)
- Array. Reduce ((result of last calculation, item item item of current cycle) = > {return result of last calculation, item item item of current cycle}, 0)
- const cumulative result = array. Reduce ((last calculated result, item item item of current cycle) = > {return last calculated result
code
const total = arr.reduce((val,item) => { return val + item; },0) console.log(total);
(2) Gets the value of an object property in a chain
const obj = { name:'xx', info:{ address:{ location:'Beijing Shunyi', } } }
Common implementation
const location = obj.info.address.location console.log(location)//Beijing Shunyi
Implemented through reduce - an array is initially given
Basic ideas
//First reduce The initial value is obj This object, Current item Item is info for the first time reduce The result is obj.info Object corresponding to the property of //Second reduce The initial value is obj.info This object, Current item Item is address educe The result is obj.info.address Object corresponding to the property of //Third reduce The initial value is obj.info.address This object, Current item Item is location educe The result is obj.info.address.location The value corresponding to the property of
code
const attrs = ['info','address','location'] const location = attrs.reduce((newobj,key) => { return newobj[key] },obj) console.log(location)
The upgrade operation is implemented through reduce - a string is initially given and divided
code
const attrs = 'info.address.location' const location = attrs.split('.').reduce((newobj,key) => newobj[key] ,obj)
2. Publish subscribe mode
1, Code parsing
(1) Dep class
- Responsible for dependency collection
- First, there is an array to store all subscription information
- Second, provide a method to append subscription information to the array
- Third, provide a loop that triggers each subscription information in the array
code
//Collect dependencies / subscribers class Dep{ //The constructor() method is a special method for creating and initializing objects created in classes. // When the class is initialized, the constructor() method is called automatically, and it must use the exact name "constructor" constructor() { //This sub array is used to store the information of all subs cribers this.subs = [] } //Add subs criber information to the sub array and accept an instance addSub(watcher){ this.subs.push(watcher) } //Method of issuing notification notify(){ this.subs.forEach((watcher) => watcher.update()) } }
(2) Watcher class
- Responsible for subscribing to some events
code
//Subscriber's class class Watcher{ constructor(cb) { this.cb =cb//Mount cb on yourself } //Method of triggering callback update(){ this.cb() } }
//Create class const w1 = new Watcher(() => { console.log('I'm the first subscriber') }) // w1.update(); call this function to print that I am the first subscriber const w2 = new Watcher(() => { console.log('I'm the second subscriber') }) const dep = new Dep() dep.addSub(w1) dep.addSub(w2) dep.notify()
2, Operation
(1) Describes how vue publish subscribe mode works
- As long as we re assign the value to the data in vue, the assignment action will be monitored by vue, and then vue will notify each subscriber of the data change. Next, the subscriber (DOM element) will update its own class content according to the latest data
- At the moment when the data is updated, update will be triggered, and the code written in it is the operation based on the new data
3. Use Object.defineProperty() to hijack data
(1) . hijack the value operation through get() - it is called getter, and the function name is get
(2) . hijack the assignment operation through set() - it is called setter, and the function name is set
const obj = { name:'zs', age:20, } Object.defineProperty(obj,'name',{ enumerable:true,//Current attribute, allowed to be looped for in configurable:true,//The current attribute can be configured, such as delete, and the attribute in the object can be deleted get(){ console.log('Someone got it obj.name Value of') return 'I am not zs' }, set(newval){ console.log('Don't value',newval) dep.notify() } }) //Value operation console.log(obj.name) //Assignment operation obj.name = 'ls'
Pre implementation of two-way binding paves the way for code learning
- html
- Implement getter and setter
- Attribute agent
- Data hijacking
- Recursively add set and get for each
- Solve the problem that there is no set or get after assignment
<body> <div id="app"> <h1>full name:{{name}}</h1> </div> <script src="05-vue.js"></script> <script> const vm = new Vue({ el:'#app', data:{ name:'zs', age:20, info:{ a:'a1', c:'c1' } } }) console.log(vm.name);//Write vm.name as undefined here //To access here, you need to access vm.$data.name //Through the property proxy, you can access the properties using vm.name
- js
- this refers to the instance that is finally new
//Implement getter s in vue class Vue{ constructor(options) { this.$data = options.data //Call the method of data hijacking observe(this.$data) //Attribute agent Object.keys(this.$data).forEach((key) => { Object.defineProperty(this,key,{ enumerable:true, configurable:'true', get() { return this.$data[key] }, set(newvalue) { this.$data[key] = newvalue } }) }) } } //Define a method of data hijacking function observe(obj) { //This is the termination condition of recursion if(!obj || typeof obj !== 'object') return//Is empty, not an object //Get each attribute of the current obj through object.keys(obj) Object.keys(obj).forEach((key) => { //The attribute value corresponding to the currently cycled key let value = obj[key] //The child node, value, is recursive observe(value) //You need to add getter s and setter s for the properties corresponding to the current key Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get() { console.log(`Someone got it ${key}Value of`); return value }, set(newvalue) { value = newvalue; //Here, if the values a and C in the object are re assigned, the new //a and c will not have new get and set, so they need to call the new one again. What is modified here is info or an object vm.$data.info = {a:4,c:9}, observe(value) } }) }) }
- Document fragmentation – to improve performance, the content changes, the page needs to be redrawn, the positioning position changes, and the page needs to be rearranged. In order to improve performance, add document fragmentation, that is, save the node in memory and modify it, so it will not be redrawn. After editing, re render the results to the page
- Take out the data and put it into the text fragment. Operate it and return it to the vm
//Call the function compiled by the template. The template structure is required for compilation. Get the data, transfer it to this, and call it when it is created Compile(options.el,this) //Method for template compilation of HTML structure function Compile(el,vm) { //Get the Dom element corresponding to el vm.$el = document.querySelector(el) //Get the element with id="demo" in the document: document.querySelector("#demo"); //Create document fragments to improve the performance of Dom operations const fragment = document.createDocumentFragment() //take out while((childNode = vm.$el.firstChild)){ fragment.appendChild(childNode) } //Perform template compilation replace(fragment) //Put it in vm.$el.appendChild(fragment) function replace(node) { //Define regular matching interpolation expressions const regMustache = /\{\{\s*(\S+)\s*\}\}/ //\S matches any non white space characters //\s matches any white space characters, including spaces, tabs, page breaks, and so on. //() non blank characters are extracted and grouped with a small bracket //It is proved that the current node node is a text child node and needs regular replacement if(node.nodeType == 3){ //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it // console.log(node.textContent); //Conditions for terminating recursion return } //The proof is not a text node, but may be a Dom element, which needs recursive processing node.childNodes.forEach((child) => replace(child)) }
- Text sub nodes are regularly matched and replaced. Here, the data is rendered to the page
if(node.nodeType == 3){ //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it // console.log(node.textContent); const text = node.textContent //Regular string matching and extraction const execRusult = regMustache.exec(text)//Is an array, the index 0 is {{name}}, the index 1 is name, and the exec () method is used to retrieve the matching of regular expressions in the string. if(execRusult){ const value = execRusult[1].split('.').reduce((newobj,k) => newobj[k],vm) // console.log(value); node.textContent = text.replace(regMustache,value) }
- Publish subscribe – when the data is updated, the page is rendered
- Create two classes and create an instance of the watcher class
//At this time, create an instance of the watcher class, save this method to the watcher, and call update to execute new Watcher(vm,execRusult[1],(newValue) => { node.textContent = text.replace(regMustache,newValue) }) //Dependent collected classes class Dep{ constructor() { //All watcher s should be stored in this array this.subs = [] } //Like in the array, add the watcher method addSub(watcher){ this.subs.push(watcher) } //The method responsible for notifying each watcher notify(){ this.subs.forEach((watcher) => watcher.update()) } } //Subscriber's class class Watcher{ //The cb callback function records the current watcher and updates its own text content //At the same time, you need to get the latest data. Therefore, during the new watcher, you need to pass it in to the vm //You should know that among the numerous data in the vm, the data is the data you currently need. During the new watcher period, specify the name of the data corresponding to the watcher constructor(vm,key,cb) { this.vm = vm this.key = key this.cb = cb } //The watcher instance needs the update function so that the publisher can notify us to update update(){ this.cb() } }
- Store the watcher instance in the dep.subs array
get() { //As long as the following line is executed, the watcher instance of new just now is added to the dep.sub array Dep.target&&dep.addSub(Dep.target) } //The following three lines of code are responsible for saving the created watcher instance into the subs array of the dep instance Dep.target = this//Custom properties, watcher instance key.split('.').reduce((newobj,k) => newobj[k],vm)//The main purpose here is to execute this line of code, jump to get and return name and age, Dep.target = null
- Unidirectional binding of data to view - displayed on the page after modification
set(newvalue) { dep.notify() } update(){ const value = this.key.split('.').reduce((newobj,k) => newobj[k],this.vm) this.cb(value) }
- Realize one-way data binding of text box
//Judge whether the current node is an input field if(node.nodeType === 1&& node.tagName.toUpperCase() === 'INPUT'){ //Get all attribute nodes of the current element const attrs = Array.from(node.attributes) const findResult = attrs.find((x) => x.name === 'v-model') if(findResult){ //Get the value of the current v-model attribute v-model = 'name' v-model='info.a ' const expStr = findResult.value const value = expStr.split('.').reduce((newobj,k) => newobj[k],vm) node.value = value //Create an instance of Watcher new Watcher(vm,expStr,(newValue) => { node.value = newValue }) } }
- Realize bidirectional data binding of text box
//Listen to the input event of the text box, get the latest value of the text box, and update the latest value to the vm node.addEventListener('input',(e) => { const keyArr = expStr.split('.') const obj = keyArr.slice(0,keyArr.length-1).reduce((newobj,k) => newobj[k],vm) obj[keyArr[keyArr.length-1]] = e.target.value })
Complete code
- HTML
<div id="app"> <h3 >full name:{{name}}</h3> <h3>Age is:{{age}}</h3> <h3>info.a The values are:{{info.a}}</h3> <div>name Value of:<input type="text" v-model="name"> </div> <div>info.a Value of:<input type="text" v-model="info.a"> </div> </div>5 <script src="05-vue.js"></script> <script> const vm = new Vue({ el:'#app', data:{ name:'zs', age:20, info:{ a:'a1', c:'c1' } } }) console.log(vm.name);//Write vm.name as undefined here //To access here, you need to access vm.$data.name //Through the property proxy, you can access the properties using vm.name </script> </body>
- js
//Implement getter s in vue class Vue{ constructor(options) { this.$data = options.data //Call the method of data hijacking observe(this.$data) //Attribute agent Object.keys(this.$data).forEach((key) => { Object.defineProperty(this,key,{ enumerable:true, configurable:'true', get() { return this.$data[key] }, set(newvalue) { this.$data[key] = newvalue } }) }) //Call the function compiled by the template. The template structure is required for compilation. Get the data, transfer it to this, and call it when it is created Compile(options.el,this) } } //Define a method of data hijacking function observe(obj) { //This is the termination condition of recursion if(!obj || typeof obj !== 'object') return//Is empty, not an object const dep = new Dep() //Get each attribute of the current obj through object.keys(obj) Object.keys(obj).forEach((key) => { //The attribute value corresponding to the currently cycled key let value = obj[key] //The child node, value, is recursive observe(value) //You need to add getter s and setter s for the properties corresponding to the current key Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get() { //As long as the following line is executed, the watcher instance of new just now is added to the dep.sub array Dep.target&&dep.addSub(Dep.target) return value }, set(newvalue) { value = newvalue; dep.notify() //Here, if the values a and C in the object are re assigned, the new //a and c will not have new get and set, so they need to call the new one again. What is modified here is info or an object vm.$data.info = {a:4,c:9}, observe(value) } }) }) } //Method for template compilation of HTML structure function Compile(el,vm) { //Get the Dom element corresponding to el vm.$el = document.querySelector(el) //Get the element with id="demo" in the document: document.querySelector("#demo"); //Create document fragments to improve the performance of Dom operations const fragment = document.createDocumentFragment() //take out while((childNode = vm.$el.firstChild)){ fragment.appendChild(childNode) } //Perform template compilation replace(fragment) //Put it in vm.$el.appendChild(fragment) function replace(node) { //Define regular matching interpolation expressions const regMustache = /\{\{\s*(\S+)\s*\}\}/ //\S matches any non white space characters //\s matches any white space characters, including spaces, tabs, page breaks, and so on. //() non blank characters are extracted and grouped with a small bracket //It is proved that the current node node is a text child node and needs regular replacement if(node.nodeType == 3){ //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it // console.log(node.textContent); const text = node.textContent //Regular string matching and extraction const execRusult = regMustache.exec(text)//Is an array, the index 0 is {{name}}, the index 1 is name, and the exec () method is used to retrieve the matching of regular expressions in the string. if(execRusult){ const value = execRusult[1].split('.').reduce((newobj,k) => newobj[k],vm) // console.log(value); node.textContent = text.replace(regMustache,value) //At this time, create an instance of the watcher class, save this method to the watcher, and call update to execute new Watcher(vm,execRusult[1],(newValue) => { node.textContent = text.replace(regMustache,newValue) }) } //Conditions for terminating recursion return } //Judge whether the current node is an input field if(node.nodeType === 1&& node.tagName.toUpperCase() === 'INPUT'){ //Get all attribute nodes of the current element const attrs = Array.from(node.attributes) const findResult = attrs.find((x) => x.name === 'v-model') if(findResult){ //Get the value of the current v-model attribute v-model = 'name' v-model='info.a ' const expStr = findResult.value const value = expStr.split('.').reduce((newobj,k) => newobj[k],vm) node.value = value //Create an instance of Watcher new Watcher(vm,expStr,(newValue) => { node.value = newValue }) //Listen to the input event of the text box, get the latest value of the text box, and update the latest value to the vm node.addEventListener('input',(e) => { const keyArr = expStr.split('.') const obj = keyArr.slice(0,keyArr.length-1).reduce((newobj,k) => newobj[k],vm) obj[keyArr[keyArr.length-1]] = e.target.value }) } } //The proof is not a text node, but may be a Dom element, which needs recursive processing node.childNodes.forEach((child) => replace(child)) } } //Dependent collected classes class Dep{ constructor() { //All watcher s should be stored in this array this.subs = [] } //Like in the array, add the watcher method addSub(watcher){ this.subs.push(watcher) } //The method responsible for notifying each watcher notify(){ this.subs.forEach((watcher) => watcher.update()) } } //Subscriber's class class Watcher{ //The cb callback function records the current watcher and updates its own text content //At the same time, you need to get the latest data. Therefore, during the new watcher, you need to pass it in to the vm //You should know that among the numerous data in the vm, the data is the data you currently need. During the new watcher period, specify the name of the data corresponding to the watcher constructor(vm,key,cb) { this.vm = vm this.key = key this.cb = cb //The following three lines of code are responsible for saving the created watcher instance into the subs array of the dep instance Dep.target = this//Custom properties key.split('.').reduce((newobj,k) => newobj[k],vm) Dep.target = null } //The watcher instance needs the update function so that the publisher can notify us to update update(){ const value = this.key.split('.').reduce((newobj,k) => newobj[k],this.vm) this.cb(value) } }
ye