现代前端技术解析(3)详解架构师
手动触发指令绑定是比较直接的实现方式,主要思路是通过在数据对象上定义get()方法和set()方法,调用时手动触发get()或set()函数来获取、修改数据,改变数据后会主动触发get()和set()函数中View层的重新渲染功能。我们来看一个栗子
input q-value="value" type="text" id="input" span q-text="value" id="el" /span
let elems = [document.getElementById(el), document.getElementById(input)]; let data = { value: hello //定义Directive指令 let directive = { text: function(text){ this.innerHTML = text; value: function(value){ this.setAttribute(value, value); //数据绑定监听 if(document.addEventListener){ elems[1].addEventListener(keyup, function(e){ ViewModelSet(value, e.target.value); }, false); } else { elems[1].attachEvent(onkeyup, function(e){ ViewModelSet(value, e.target.value); }, false); //开始扫描节点 scan(); //模拟一次用户操作,设置页面2秒后自动改变数据更新视图 setTimeout(function(){ ViewModelSet(value, hello sysuzhyupeng); }, 1000) function scan(){ //扫描带指令的节点属性(for of是ES6中用来方便遍历数组的) for(let elem of elems){ elem.directive = {}; for(let attr of elem.attributes){ //判断是否存在q-的指令 if(attr.nodeName.indexOf(q-) = 0){ //调用属性指定 directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]); elem.directive.push(attr.nodeName.slice(2)); //设置数据改变之后扫描节点 function ViewModelSet(key, value){ data[key] = value; scan(); }
通过浏览器加载这个脚本之后,ViewModel的变化会自动改变输入框的内容,输入的内容的变化也会驱动ViewModel的变化。我们通过ViewModelSet() 方法改变ViewModel的数据后,需要主动调用scan()方法重新扫描HTML页面上的节点。
脏检测机制脏检测的基本原理是在ViewModel对象的某个属性值发生变化时,找到这个属性值相关的所有元素,然后再比较数据变化,如果变化则进行Directive调用,举个栗子
input qg-event="value" qg-bind="value" type="text" id="input" span qg-event="text" qg-bind="value" id="el" /span
let elems = [document.getElementById(el), document.getElementById(input)]; let data = { value: hello //定义Directive指令 let directive = { text: function(text){ this.innerHTML = text; value: function(value){ this.setAttribute(value, value); scan(elems); $digest(value); //数据绑定监听 if(document.addEventListener){ elems[1].addEventListener(keyup, function(e){ //改变model的值 data.value = e.target.value; //$digest value这个字段 $digest(e.target.getAttribute(qg-bind)); }, false); } else { elems[1].attachEvent(onkeyup, function(e){ data.value = e.target.value; $digest(e.target.getAttribute(qg-bind)); }, false); //模拟一次用户操作 setTimeout(function(){ data.value = hello sysuzhyupeng; //执行$digest方法启动脏检测 $digest(value); }, 1000) function scan(){ //扫描带指令的节点属性 for(let elem of elmes){ elem.directive = []; //可以理解为数据劫持监听 function $digest(value){ //找到所有绑定相同数据字段的元素 let list = document.querySelector([qg-bind= + value + ]); digest(list); //脏数据循环检测 function digest(elems){ //扫描带指令的节点属性 for(let elem of elmes){ for(let attr of elem.attributes){ if(attr.nodeName.indexOf(qg-event) = 0){ //调用属性指令 let dataKey = elem.getAttribute(qg-bind) || undefined; //进行脏数据检测,如果数据改变,则重新执行指令,否则跳过 if(elem.directive[attr.nodeValue] !== data[dataKey]){ directive[attr.nodeValue].call(elem, data[dataKey]); elem.directive[attr.nodeValue] = data[dataKey]; }
这里和手动绑定不同的是,脏检测只针对可能修改的元素进行扫描,这样就提高了ViewModel内容变化后扫描视图渲染的效率
前端数据对象劫持(Hijacking)数据劫持是目前使用比较广泛的方式,其基本思路是使用Object.defineProperty和Object.difineProperties对ViewModel数据对象进行get和set的监听,当有数据读取和赋值操作的时候则扫描元素节点,运行指定对应节点的Directive指令,这样ViewModel使用通用的等号赋值就可以了。举个栗子
input q-value="value" type="text" id="input" div q-text="value" id="el" /div
let elems = [document.getElementById(el), document.getElementById(input)]; let data = { value: hello //定义Directive指令 let directive = { text: function(text){ this.innerHTML = text; value: function(value){ this.setAttribute(value, value); let bValue; scan(); //可以理解为数据劫持监听 defineGetAndSet(data, value); //数据绑定监听 if(document.addEventListener){ elems[1].addEventListener(keyup, function(e){ //直接改变model data.value = e.target.value; }, false); } else { elems[1].attachEvent(onkeyup, function(e){ data.value = e.target.value; }, false); setTimeout(function(){ data.value = hello sysuzhyupeng; }, 2000); function scan(){ //扫描带指令的节点属性 for(let elem of elems){ elem.directive = {}; for(let attr of elem.attributes){ //判断是否存在q-的指令 if(attr.nodeName.indexOf(q-) = 0){ //调用属性指定 directive[attr.nodeName.slice(2)].call(elem, data[attr.nodeValue]); elem.directive.push(attr.nodeName.slice(2)); //定义对象属性设置劫持 function defineGetAndSet(obj, propName){ Object.defineProperty(obj, propName, { get: function(){ return bValue; set: function(newValue){ bValue = newValue; //拦截正常的set,变成更新视图 scan(); enumerable: true, configurable: true }
需要注意的是,defineProperty只支持IE8以上和Chrome浏览器,且IE8浏览器中需要使用es5-shim来提供支持。Firefox浏览器不支持该方法,需要使用_ difineGetter_和_ difineSetter_来代替。关于对象劫持可以参考我另一篇博客 Vue 双向数据绑定原理
Virtual DOMMVVM的前端交互模式大大提高了编程效率,自动双向数据绑定让我们可以页面逻辑实现的核心转移到数据层的修改操作上,而不是在页面中直接操作DOM。但是MVVM最终数据层反应到页面上View层的渲染和改变仍是通过对应的指令进行DOM操作来完成的,而且通常一次ViewModel的变化可能会触发页面上多个指令操作DOM的变化,带来大量的页面结构层DOM操作或渲染,先来看下面这个应用场景
ul id="root" li q-repeat="list" span q-text="value" /span span 固定文本 /span /li /ul
let viewModel = new VM({ $el: document.getElementById(root), data: { list: [{value: 1}, {value: 2}, {value: 3}] });
使用MVVM框架时就生成了一个数字列表,此时如果需要显示的内容变成了list: [{value: 0}{value: 1}, {value: 2}, {value: 3}] 在没有做优化的MVVM框架中一般会重新渲染整个列表。那么该怎样将这个增加的数据反映到View层上呢?我们其实可以把新的Model data和旧的Model data进行对比,然后记录ViewModel的改变方式和位置,就知道这次View层怎样去更新。
如果用js对象的属性层级结构来描述HTML DOM对象树的结构,在渲染前面对比前后两个js对象,就能找出视图改变的最小操作描述的对象。这里的HTML DOM对象树可以理解为Virtual DOM。
先总结一下使用Virtual DOM模式来控制页面DOM结构更新的过程:创建原始页面或组件的Virtual DOM结构,用户操作进行DOM更新时,生成用户操作后页面或组件的Virtual DOM结构并与之前的结构进行对比,找到最小变化的Virtual DOM的差异化描述对象,最后把差异化的Virtual DOM根据特定规则渲染到页面上。
创建Virtual DOM即把一段HTML字符串文本解析成一个能够描述它的js对象。我们很自然地想,通过浏览器提供的DOM API扫描这段DOM的节点,遍历它的属性,然后添加到js对象上即可。这似乎很合理,但其实这样是错的,这样创建Virtual DOM会直接失去Virtual DOM的优势,它是为了避免直接进行DOM操作而设计的,因为扫描过程本身使用到DOM的读取操作,这个过程很慢。所以一种更可选的方式是,自己实现上述这段HTML字符串文本的解析方式,根据标签之间的关系,读取生成Virtual DOM的结构。
let htmlString = ul id="root" span 1 span 固定文本 /li span 2 span 固定文本 /li span 3 span 固定文本 /li /ul let ulElement = createVDOM(htmlString);
那么createVDOM就可以如下实现:逐个分析字符串中的字符,根据词法分析内容,将标签名存为tagName,属性存入attributes,子标签内容存入chidren。根据HTML字符串解析创建Virtual DOM的过程相当于实现了一个HTML文本解析器。那么在对比Virtual DOM的算法实际上是对于多叉树结构的遍历算法,对多叉树遍历就有广度优先算法和深度优先算法。我们通过Virtual DOM的方式,有效减少了DOM操作。
6918.html
架构架构师架构设计相关文章
- 一年半经验前端社招7家大厂&独角兽全过经历 | 掘金技术征文
- 我用前端【最新】技术栈完成了一个生产标准的项目【Vue3 + TS + Vite + Pinia + Windicss】
- 一种将前端恶意代码关在“笼子”里的技术方案
- PECVD等离子增强化学沉积技术
- 【鹅厂网事】【更新版】服务器资源池化技术发展趋势简介
- 《三体》世界在射频前端产业的投影:技术大爆炸之后是一地鸡毛,还是光速飞船?(待续)
- 程序员真的很难碰到一个好的技术领导
- 【总结】1577- Web3.0前端工程师需要具备哪些技术?
- MetaDaily|北京经开区发布数字经济10+示范应用场景,华为参与打造首个港口数字孪生技术底座“津鸿”
- 盘点四大技术板块,洞察百项人工智能开源项目——InfoQ研究中心带你探秘中国人工智能开源领域
- 浅析微前端技术
- 浅析前端监控技术
- 前端技术提高思路详解程序员
- Oracle数据库技术从实践到知乎(oracle书籍知乎)
- Linux远程执行:探索技术的可能性(linuxrexec)
- Redis缓存技术:突破性性能优势(redis优势)
- 增长MySQL技术:求同比增长的新方法(mysql求同比)
- MySQL数据库中的交叉连接技术(mysql交叉连接)
- 探索Linux系统中的DMA技术(linuxdma)
- 技术MySQL实现高效的分页技术(mysql高效分页)
- 学会前端技术,尽享 SQL Server 功能(前端sqlserver)
- 端技术学习SQL Server七天前端技术提升之旅(sqlserver一周前)
- MySQL前端开发技术:点亮你的世界(mysql 前端)
- MSSQL数据库还原撤销技术实践(mssql 还原撤销)
- SSH与Redis技术搭建高效项目(ssh redis项目)
- Redis技术问题分析及解决方案(redis问题答案)
- 美国纽约警察局监控支出曝光 人脸识别等技术耗费数百万美元
- 推荐php模板技术[转]
- php+mysqli使用预处理技术进行数据库查询的方法