💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ***** ## 1 意义 >[info] 前端的视图层和数据层之间的互动 >[info] 可以从视图层发生改变后数据层发生改变 >[info] 也可以从数据层发生改变后视图层发生改变 >[info] 因此需要双向绑定实现视图和数据之间的互动 ## 2 手动绑定 >[info] (1)从数据到视图层 在数据对象上定义get和set方法 调用时手动调用get或set数据,改变数据后发出UI层的渲染操作 >[info] (2)从视图到数据层的变化驱动 主要是input,select,textarea等表单元素的UI层发生变化 通过监听dom的change,keypress,keyup等事件激活数据层数据的更新 ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>data-binding-method-set</title> </head> <body> ;UI视图层 <input q-value="value" type="text" id="input"> <div q-text="value" id="el"></div> ;双向绑定 <script> ;UI层 var elems = [document.getElementById('el'), document.getElementById('input')]; ;数据层 var data = { value: 'hello!' }; ;UI层修改 var command = { text: function(str){ this.innerHTML = str; }, value: function(str){ this.setAttribute('value', str); } }; ;节点扫描数据绑定处理 var scan = function(){ for(var i = 0, len = elems.length; i < len; i++){ ;当前元素节点 var elem = elems[i]; elem.command = []; ;元素节点属性处理 for(var j = 0, len1 = elem.attributes.length; j < len1; j++){ ;当前处理属性 var attr = elem.attributes[j]; ;属性值修改 if(attr.nodeName.indexOf('q-') >= 0){ command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]); elem.command.push(attr.nodeName.slice(2)); } } } } ;从数据层到UI层 function mvSet(key, value){ data[key] = value; scan(); } ;UI事件监听 elems[1].addEventListener('keyup', function(e){ mvSet('value', e.target.value); }, false); ;开始扫描 scan(); ;定时修改数据层 setTimeout(function(){ mvSet('value', 'fuck'); },1000) </script> </body> </html> ~~~ ## 3 脏检查 >[info] 检查脏数据进行UI层的操作更新,脏检查机制在数据发生变化时进行的。 >[info] 对UI层的dom事件,xht事件做了封装,在里面触发进入digest流程 >[info] digest流程里面,从rootscope开始遍历,检查所有的watcher。 >[info] 通过设置的数据来查找与数据相关的元素,比较数据变化,如果变化则进行指令操作 ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>data-binding-drity-check</title> </head> <body> ;UI层 <input q-event="value" ng-bind="value" type="text" id="input"> <div q-event="text" ng-bind="value" id="el"></div> ;数据绑定处理 <script> ;UI层 var elems = [document.getElementById('el'), document.getElementById('input')]; ;数据层 var data = { value: 'hello!' }; ;UI层修改 var command = { text: function(str) { this.innerHTML = str; }, value: function(str) { this.setAttribute('value', str); } }; ;UI层节点扫描处理 var scan = function(elems) { for (var i = 0, len = elems.length; i < len; i++) { ;当前节点 var elem = elems[i]; ;节点指令 elem.command = {}; ;节点属性遍历 for (var j = 0, len1 = elem.attributes.length; j < len1; j++) { var attr = elem.attributes[j]; ;事件属性获取 if (attr.nodeName.indexOf('q-event') >= 0) { ;获取ng-bind属性 var dataKey = elem.getAttribute('ng-bind') || undefined; ;注册到节点指令 command[attr.nodeValue].call(elem, data[dataKey]); ;获取节点指令对应的值 elem.command[attr.nodeValue] = data[dataKey]; } } } } ;脏检查机制 var digest = function(elems) { ;节点遍历 for (var i = 0, len = elems.length; i < len; i++) { ;当前节点 var elem = elems[i]; ;节点属性遍历 for (var j = 0, len1 = elem.attributes.length; j < len1; j++) { var attr = elem.attributes[j]; ;事件属性获取 if (attr.nodeName.indexOf('q-event') >= 0) { ;获取ng-bind属性的值 var dataKey = elem.getAttribute('ng-bind') || undefined; ;节点指令注册 if(elem.command[attr.nodeValue] !== data[dataKey]){ command[attr.nodeValue].call(elem, data[dataKey]); elem.command[attr.nodeValue] = data[dataKey]; } } } } } ;遍历节点初始化指令 scan(elems); ;数据劫持监听 function $digest(value){ var list = document.querySelectorAll('[ng-bind='+ value + ']'); digest(list); } ;UI层事件注册 if(document.addEventListener){ elems[1].addEventListener('keyup', function(e) { data.value = e.target.value; $digest(e.target.getAttribute('ng-bind')); }, false); }else{ elems[1].attachEvent('onkeyup', function(e) { data.value = e.target.value; $digest(e.target.getAttribute('ng-bind')); }, false); } ;数据层定时更新 setTimeout(function() { data.value = 'fuck'; $digest('value'); }, 2000) </script> </body> </html> ~~~ ## 4 前端数据劫持 >[info] 使用Object.defineProperty对数据对象做属性get和set的监听 >[info] 所有数据的读取和赋值操作时则调用节点的指令。 >[info] 对应Object.defineProperty需要做浏览器兼容处理 ~~~ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>data-binding-hijacking</title> </head> <body> ;UI层 <input q-value="value" type="text" id="input"> <div q-text="value" id="el"></div> ;数据绑定 <script> ;UI层 var elems = [document.getElementById('el'), document.getElementById('input')]; ;数据层 var data = { value: 'hello!' }; ;UI层修改指令 var command = { text: function(str) { this.innerHTML = str; }, value: function(str) { this.setAttribute('value', str); } }; ;节点遍历生成指令 var scan = function() { ;节点遍历 for (var i = 0, len = elems.length; i < len; i++) { ;当前节点 var elem = elems[i]; ;节点的指令 elem.command = []; ;节点属性遍历 for (var j = 0, len1 = elem.attributes.length; j < len1; j++) { ;当前属性 var attr = elem.attributes[j]; ;指令属性 if (attr.nodeName.indexOf('q-') >= 0) { command[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]); elem.command.push(attr.nodeName.slice(2)); } } } } ;默认属性 var bValue; ;属性劫持 var defineGetAndSet = function(obj, propName) { try { Object.defineProperty(obj, propName, { get: function() { return bValue; }, set: function(newValue) { bValue = newValue; scan(); }, enumerable: true, configurable: true }); } catch (error) { console.log("browser not supported."); } } ;指令初始化 scan(); ;注册数据监听指令 defineGetAndSet(data, 'value'); ;UI层事件注册 if(document.addEventListener){ elems[1].addEventListener('keyup', function(e) { data.value = e.target.value; }, false); }else{ elems[1].attachEvent('onkeyup', function(e) { data.value = e.target.value; }, false); } ;数据层自动更新 setTimeout(function() { data.value = 'fuck'; }, 2000) </script> </body> </html> ~~~ ## 5 小结 >[info] 数据绑定实现UI层与数据层的同步更新 >[info] UI层发生变化后数据层进行自动同步 >[info] 数据层发送变化UI层自动同步更新 >[info] 核心思路是UI层的事件监听与数据层的set/get劫持 ## 6 参考连接 [数据绑定](https://ouvens.github.io/frontend-javascript/2015/11/29/js-data-two-ways-binding.html)