当前位置: 首页 > Web前端 > HTML

前端技术演进(5):现代前端交互框架

时间:2023-04-02 12:54:47 HTML

这个来自之前的培训,删掉了一些业务相关的,参考了很多资料(参考列表),谢谢学长好吗?随着前端技术的发展,前端框架也在不断变化。DOM时代的DOM(DocumentObjectModel,文档对象模型)将HTML文档表示为树状结构,定义了访问和操作HTML文档的标准方法。前端开发基本上涉及到HTML页面,所以与DOM打交道是免不了的。最早的web前端是一个静态的黄页,网页上的内容无法更新。慢慢地,用户可以在网页上进行一些简单的操作,比如提交表单、上传文件等。但是,整个页面的局部或整体更新是通过刷新页面来实现的。随着AJAX技术的出现,越来越多的用户对前端页面的操作变得越来越复杂,从而进入了直接操作DOM元素的时代。要对DOM元素进行操作,您需要使用DOMAPI。常见的DOMAPI包括:类型方法节点查询getElementById、getElementsByName、getElementsByClassName、getElementsByTagName、querySelector、querySelectorAll节点创建createElement、createDocumentFragment、createTextNode、cloneNode节点修改appendChild、replaceChild、removeChild、insertBefore、innerHTML节点关系parentNode、previousSibling、childNodes节点属性innerHTML,attributes,getAttribute,setAttribute,getComputedStyle内容加载XMLHttpRequest,ActiveX使用DOMAPI完成前端页面的任何操作,但是随着网站应用的复杂性,使用nativeAPI效率很低。于是jQuery,一个用于操作DOM的交互式框架诞生了。为什么jQuery可以成为这个时代最流行的框架?主要是他帮助前端开发者解决了太多的问题:封装了DOMAPI,提供了统一便捷的调用方式。简化了元素的选择,可以快速选择所需的元素。提供AJAX接口,统一封装XMLHttpRequest和ActiveX。统一的事件处理。提供异步处理机制。与大多数主流浏览器兼容。除了解决以上问题,jQuery还拥有良好的生态,大量的插件随时可用,让前端开发比以前顺畅很多。尤其是IE6、IE7时代,没有jQuery,意味着没完没了的兼容性处理。//DOMAPI:document.querySelectorAll('#containerli');//jQuery$('#container').find('li');随着HTML5技术的发展,jQuery提供的很多方法已经在原生的标准中实现了,慢慢的,jQuery的必要性也在逐渐降低。http://youmightnotneedjquery.com/渐渐地,SPA(SinglePageApplication,单页应用)开始被广泛认可。整个应用的内容在一个页面中,不同的内容完全通过异步交互加载。这时候使用jQuery直接操作DOM的方式就不好管理了,页面上的事件绑定也会变得混乱。在这种情况下,就迫切需要一个能够自动管理DOM与页面上数据交互的框架。MV*模式MVC、MVP和MVVM是常见的软件架构设计模式(ArchitecturalPattern),它们通过分离关注点来改进代码的组织。单纯从概念上,很难区分和感受这三种模式在前端框架中的区别。我们通过一个例子来体验一下:有一个可以加减值的组件:值显示在上面,两个按钮可以加减值,操作后的值会更新显示。Model层用于封装与应用程序业务逻辑相关的数据和数据的处理方法。这里我们封装了Model中需要用到的数值型变量,定义了三个操作数值的方法:add、sub、getVal。varmyapp={};//创建这个应用程序对象myapp.Model=function(){varval=0;//要操作的数据/*操作数据的方法*/this.add=function(v){if(val<100)val+=v;};this.sub=function(v){if(val>0)val-=v;};this.getVal=function(){返回值;};};Viewas视图层主要负责数据的展示。myapp.View=function(){/*视图元素*/var$num=$('#num'),$incBtn=$('#increase'),$decBtn=$('#decrease');/*渲染数据*/this.render=function(model){$num.text(model.getVal()+'rmb');};};在这里,数据从模型层传递到视图层的逻辑是通过Model&View完成的。但是对于一个应用程序来说,这还远远不够。我们还需要响应用户操作,同步更新View和Model。前端MVC模式MVC(ModelViewController)是一种非常经典的设计模式。用户对View的操作都交给了Controller。在Controller中,响应View的事件,调用Model的接口对数据进行操作。一旦Model发生变化,就会通知相关的view进行更新。Model层用于存储业务数据。一旦数据发生变化,模型就会通知相关的视图。//Modelmyapp.Model=function(){varval=0;this.add=function(v){if(val<100)val+=v;};this.sub=function(v){if(val>0)val-=v;};this.getVal=function(){返回值;};/*观察者模式*/varself=this,views=[];this.register=function(view){视图。推(查看);};this.notify=function(){for(vari=0;i0)val-=v;};this.getVal=function(){返回值;};};Model层还是主要是业务相关的数据和相应的数据处理方法,非常简单。//Viewmyapp.View=function(){var$num=$('#num'),$incBtn=$('#increase'),$decBtn=$('#decrease');this.render=function(model){$num.text(model.getVal()+'rmb');};this.init=function(){varpresenter=newmyapp.Presenter(this);$incBtn.click(presenter.increase);$decBtn.click(presenter.decrease);};};MVP定义了Presenter和View之间的接口,用户对View的操作都转移到了Presenter上。比如这里的View暴露了setter接口(render方法)供Presenter调用。Presenter通知Model更新后,Presenter调用View提供的接口更新视图。//Presentermyapp.Presenter=function(view){var_model=newmyapp.Model();var_view=视图;_view.render(_model);this.increase=function(){_model.add(1);_看法。渲染(模型);};this.decrease=function(){_model.sub(1);_view.render(_model);};};Presenter充当View和Model之间的“中间人”,除了基本的业务逻辑外,还有很多代码需要“手动同步”数据从View到Model,从Model到View,所以Presenter很重并且难以维护。如果Presenter对视图渲染的需求增加,它不得不过多地关注特定的视图。一旦视图需求改变,Presenter也需要改变。前端MVVM模式MVVM(Model-View-ViewModel)最早是由微软提出的。ViewModel指的是“ModelofView”——视图的模型。MVVM自动化了View和Model的同步逻辑。以前由Presenter负责的View和Model的同步不再是手动操作,而是交给了框架提供的数据绑定功能。你只需要告诉它View显示的数据对应于Model的哪一部分即可。我们使用Vue来完成这个栗子。在MVVM中,我们可以把Model称为数据层,因为它只关心数据本身,不关心任何行为(格式化数据是View的职责),这里可以理解为类似于数据对象JSON。//模型变量数据={val:0};与MVC/MVP不同,MVVM中的视图通过使用模板语法以声明方式将数据呈现到DOM中。当ViewModel更新Model时,它绑定要更新的数据Set到View。

{{val}}rmb
-+
ViewModel大致就是MVC的Controller和MVP的Presenter。也是整个模型的重点,业务逻辑主要集中在这里,核心之一就是数据绑定。与MVP不同的是没有View为Presente提供接口。之前由Presenter负责的View和Model之间的数据同步,交给了ViewModel中的databinding来处理。当Model发生变化时,ViewModel会自动更新。;ViewModel发生变化,Model也会随之更新。newVue({el:'#myapp',data:data,methods:{add(v){if(this.val<100){this.val+=v;}},sub(v){if(this.val>0){this.val-=v;}}}});整体上比MVC/MVP要精简很多。既简化了业务和接口的依赖,又解决了频繁的数据更新(以前用jQuery操作DOM很麻烦)。因为在MVVM中,View并不知道Model的存在,ViewModel和Model也不知道View。这种低耦合模式可以使开发过程更加简单,提高应用程序的可重用性。Vue中的数据绑定采用双向绑定技术(Two-Way-Data-Binding),即View的变化可以实时引起Model的变化,Model的变化也可以实时更新到View。其实双向数据绑定可以简单理解为模板引擎,但是会根据数据变化实时渲染。有人厚颜无耻的申请了专利:数据变化检测在不同的MVVM框架中,实现双向数据绑定的技术是不一样的。目前一些主流的数据绑定实现方式大致有以下几种:手动触发绑定手动触发指令绑定是一种比较直接的实现方式,主要思路是在数据对象上定义get()方法和set()方法,调用时,手动触发get()或set()函数获取和修改数据,改变数据后,会在get()和set()函数中主动触发View层的重新渲染功能.脏检测机制Angularjs是一个典型的使用脏检测机制的框架。它通过检查脏数据来更新视图层。脏检测的基本原理是当属性值发生变化时,找出与ViewModel对象的某个属性值相关的所有元素,然后比较数据变化。如果有变化,则调用Directive命令重新扫描并渲染元素。前端数据对象劫持数据劫持是目前应用广泛的一种手段。基本思路是利用Object.defineProperty和Object.defineProperies来监听ViewModel数据对象的属性get()和set(),当有数据读取和赋值操作时,扫描元素节点,运行Directive指令指定对应的节点。这样,ViewModel就可以使用一般的等号赋值了。Vue是典型的采用数据劫持和发布-订阅模式的框架。观察者数据监听器:负责监听数据对象的所有属性(数据劫持),监听到数据变化后通知订阅者。编译器指令解析器:扫描模板,解析指令,绑定指定事件。Watcher订阅者:与Observer和Compile相关联,可以订阅和接收属性变化的通知,执行与指令绑定的相应操作,更新视图。在ES6Proxy之前,我们讲过Proxy是如何实现数据劫持的:总结一下,前端框架从直接操作DOM到MVC设计模式,再到MVP,再到MVVM框架。向易维护、易扩展的基本方向发展。虽然现在的前端框架已经成熟,开始迭代到更高的版本,但是还没有结束。我们现在的编程对象还没有脱离DOM编程的基本套路。框架的改进大大提高了开发效率,但DOM元素运行效率没有改变。为了解决这个问题,一些框架提出了VirtualDOM的概念。VirtualDOMMVVM的前端交互方式大大提高了编程效率。自动双向数据绑定可以让我们将页面逻辑实现的核心转移到数据层的修改操作上,而不是直接在页面中操作DOM。MVVM虽然改变了前端开发的逻辑方式,但是页面上数据层到View层的最终渲染和变化仍然是通过DOM操作通过相应的指令完成的,通常一个ViewModel的变化可能会在页面上触发多个.指令对DOM的变化进行操作,导致产生大量的页面结构层DOM操作或渲染。比如一段伪代码:
    {{list.value}}
letviewModel=newVM({data:{list:[{value:1},{value:2},{value:3}]}})使用MVVM框架生成数字列表,此时如果要显示的内容变为[{value:1},{value:2},{value:3},{value:4}],在MVVM框架中,一般会重新渲染整个列表,包括列表中不需要改变的部分。但实际上,如果直接操作和改变DOM,只需要在
    子元素的末尾插入一个新的
  • 元素即可。但是在一般的MVVM框架中,我们通常不会那样做。毫无疑问,在这种情况下,MVVM的View层更新方式消耗了更多不必要的性能。那么如何改进ViewModel让浏览器知道它其实只是添加了一个元素呢?通过比较[{value:1},{value:2},{value:3}]和[{value:1},{value:2},{value:3},{value:4}]实际上只是increasing添加了一个{value:4},那么如何将这个增加的数据反映到View层呢?可以对比一下新的Model数据和旧的Model数据,然后记录下ViewModel的变化方式和位置,就知道这次View层应该怎么更新了,比直接重新渲染效率高很多整个列表。其实这里可以这样理解,ViewModel中的数据是另一种描述页面View内容的数据结构标识,只是需要用特定的MVVM描述语法编译生成完整的DOM结构。上述HTMLDOM对象树的结构可以用JavaScript对象的属性层次结构来描述。当数据发生变化时,会生成一个新的变化的Elements,并与原来的Elemnets结构进行比较。比较完成后,决定更改哪个DOM元素。刚才例子中的ulElement对象可以理解为VirtualDOM。一般认为,VirtualDOM是一个JavaScript对象,可以直接描述一段HTMLDOM结构,浏览器可以根据其结构,按照一定的规则创建出独一无二的HTMLDOM结构。整体上,VirtualDOM的交互方式减少了MVVM或其他框架对DOM的扫描或操作次数,数据变化后只在适当的地方基于JavaScript对象对页面进行最少的DOM操作,避免了大量的DOM操作重新加载渲染。diff算法Virtual-DOM的执行过程:用JS对象模拟DOM树->比较两个虚拟DOM树的差异->将差异应用到真实的DOM树上。在VirtualDOM中,最重要的部分是find找到两个VirtualDOM之间的差异,得到一个差异树对象。VirtualDOM的比较算法实际上是多叉树结构的遍历算法。但是,要找到任意两棵树之间的最小修改步长,一般依次递归比较节点,算法复杂度达到O(n^3),非常高。比如显示1000多个节点,最悲观的十亿比较要按顺序进行。因此,不同框架使用的对比算法,其实是一种稍微简化的算法。以React为例,由于在Web应用中很少将组件移动到不同的级别,因此大多数情况都是水平移动。因此,React试图逐层比较这两棵树。一旦出现不一致,下层将不再进行比较,在损失较小的情况下显着降低了比较算法的复杂度。前端框架的演进是非常快的,所以只有了解了演进的原因,才能了解各个框架的优劣,然后根据应用的实际情况选择最合适的框架。其他技术也是如此。