深入探讨ui框架

深入探讨前端UI框架

1 前言

先说说这篇文章的由来

最近看riot的源码,发现它很像angular的dirty check,每个component ( tag )都保存一个expressions数组,更新时,遍历expressions数组,重新求值,对比旧值,如果有变更则更新DOM。

这不就是dirty check吗?为什么riot还声称它实现了virtual DOM?

疑惑之下,就去复盘了一下各大前端框架,把一些收获分享给大家

本文内容很多,实在不知道怎么取标题,最终取了一个泛泛的标题,请读者不要纠结

本文将会涉及的内容有:

MV*前端框架,UI框架,UI更新相关介绍

UI更新机制原理及其代表框架介绍

深入探讨各个UI更新机制(为什么virtual DOM会快)

浏览器渲染机制

riot的真相(virtual DOM的本质,给我自己一个交代!)

裹脚布较长,读者慎入!

2 理解前端框架

2.1 前端的工作

说起前端的工作,其实很简单,主要是:

页面加载之后,如果有初始数据的话,则处理这些数据,并将其展示到UI上(通过DOM操作)

用户与UI交互,比如点击某个button,或者某些异步事件,比如setTimeout,Ajax,产生了一个事件,事件监听者进行相应的处理,然后把变动体现到UI上,或者把用户的输入数据上传到服务器

2.2 前端框架

可以看到前端要做的工作还是比较直观,简单的

但是,当一个页面很复杂,比如SPA的时候,就需要有一个成熟的架构来提升前端开发的效率

前端框架提供一套成熟的解决方案来组织前端代码,前端数据流等

前端框架的核心作用有且并不完全是:

模块化,组件化,提高可复用性

数据流清晰,提高可维护性

常见的前端框架模式有:MVC, MVP, MVVM,可以查看阮大大的blog

上图是MVVM框架的图示,取自阮大大的blog

MVVM把model和view分离,把model和view的通信以及处理逻辑封装在vm对象中

使得vm对象可复用,同一个vm对象可以绑定不同的view

另外view和vm对象进行双向绑定,它们之间的数据流也非常清晰,提高可维护性

2.3 UI & UI框架

什么是UI?

UI实际上是View层,用户看到的内容就是UI

对于前端,web站点来说,UI就是HTML+CSS

html在js的表现就是dom tree

前端可以通过js脚本操作DOM,浏览器会根据最新的dom tree 和 css 进行渲染操作

这个过程叫做UI更新

UI框架是针对UI层的一套解决方案,提高了UI的组件化,提高复用性

另外UI框架同时也会对UI更新有一套解决方案,提高UI更新的效率

一些大型成熟的前端框架会有自己的一个UI框架,比如ember.js,extjs等

一个比较典型的UI框架就是大家都熟悉的react

2.4 UI更新及其策略

前端界都知道,DOM操作(UI更新)通常都是前端页面的性能高消费者

因此一个框架需要在UI更新这方面考虑的更加仔细,才能让系统获得更好的性能

一般UI更新的策略有两种,大家也经常使用到

直接上代码:

// 1 需要改的才去改

$(‘.我就是要找到你1‘).text(‘改文案‘);

$(‘.我就是要找到你2‘).css(‘color‘, ‘改颜色‘);

$(‘.我就是要找到你3‘).width(‘改宽度‘);

// 2 使用模板

$(‘.我是你们的公共父节点!‘).html(tpl({

text: ‘改文案‘,

color: ‘改颜色‘,

width: ‘改宽度‘

});

方式一是找到要改的节点,然后进行相应的DOM操作

方式二是直接利用模板,直接更新一块dom tree

方式一的优点是直观;缺点是代码很难维护

方式二的优点是简单,只有一次UI更新;缺点是不需要改的也更新了!

不需要变更的都一起更新会引发以下问题:

重新生成dom tree

原来绑定的事件没了

input, textarea会失去焦点

backbone 是方式二

3 理解那些你所知道的前端框架

现在有许多优秀的前端框架,下面分别介绍一下这些框架,以及这些框架与UI更新相关的内容

3.1 AngularJs ( dirty check )

AngularJs是mvvm框架,它的组件是vm组件,scope是vm组件的数据集合

AngularJs通过directive来声明vm的行为,它实现为一个watcher,监听scope的属性的变化,把最新的属性更新UI

另外当用户操作DOM的时候,产生事件,也通过watcher来把用户的输入修改到scope的属性中,这个技术称为双向绑定

有一个关键的问题是,AngularJs如何实现监听scope的属性变更的呢?

AngularJs使用的是dirty check技术,dirty check方案是在某个关键点,进入$digest循环,遍历所有的scope的属性,如果发现变更,则触发相应的watcher

需要注意的是,watcher在执行的过程中有可能会修改scope的属性值,因此$digest要一直检查,直到scope完全稳定为止

每个directive都是关注某一个点,比如修改css,class操作,text操作等

因此Angular的UI更新机制本质上是方式一,它只是把定位元素节点的逻辑封装起来,并绑定了scope的字段,然后自动监控而已

3.2 Vue、Avalon ( setter & getter )

这些库的架构基本与AngularJs一致,唯一不同的就是如何实现监听scope的属性变更

它们使用defineProperty的特性来监听scope的属性变更

这种方式和使用setter,getter来实现属性变更入口的框架比较类似

3.3 React ( virtual DOM )

react和前面的框架不一样,因为它只是单纯的ui框架

react组件没有scope的概念,虽然可以把state看作scope,但是react组件并不强制要定义state

另外,react的实现与上面两者也不一样,它的处理逻辑如下图所示

react组件根据输入:props【静态】& this.state【动态】

输出一个virtual DOM 树,然后用它与原来的virtual DOM 树通过DIFF算法,找出它们的差异PATCHES

最后,根据这些差异PATCHES再去执行UI更新

React与AngularJs比较类似,都是在某些关键点(程序自己决定什么时候开始执行更新算法)

AngularJs通过dirty check算法找到差异,并更新UI

React则是通过virtual DOM的对比找到差异,然后更新UI

React的UI更新策略包含了两种方式

PATCHES有很多种类型

它可以是简单的某个属性改变,比如text,class

它也可以是复杂的整个子树的增删移动,这时就可以使用方式二,重新渲染整个子树

详情可以参考react的Reconciliation算法

3.4 那些我不知道的

前端框架太多了,那些作者没看过的不做任何点评。。。

4 考虑性能

4.1 UI更新性能核心

提起浏览器渲染机制这个高级话题,可能大多数同学只知道大概原理吧(其实作者也是的)

大部分知道浏览器渲染的基本过程,然后还有repaint和reflow是什么即可

但是其他呢?

接下来需要介绍关于浏览器渲染机制的两个话题

浏览器对渲染的优化

浏览器UI渲染线程

4.1.1 浏览器渲染机制的优化

直接上一个测试代码就能说明这两个话题了

var ul = document.getElementById(‘list‘);

var e;

var s = +new Date();

for (var j = 0, l = 10000; j < l; ++j) {

e = document.createElement(‘li‘);

e.innerText = j;

ul.appendChild(e);

}

console.log(‘>>> cost1:‘, +new Date() - s);

// 到这句的时候,页面还是一片空白!

s = +new Date();

for (var k = 0, kl = 10000; k < kl; ++k) {

e = document.createElement(‘li‘);

e.innerText = kl;

ul.appendChild(e);

ul.offsetHeight; // 这句会引发浏览器渲染

}

console.log(‘>>> cost2:‘, +new Date() - s);

// 直到js执行结束,页面才有内容出来!

这段代码执行之后的结果如下

可以看到,两个test case只相差了一句代码:ul.offsetHeight

但是最后测出来的耗时差了1w倍

原因是这一句代码影响了浏览器渲染机制的优化

浏览器会缓存一些DOM操作,直到它必须要reflow为止

一些读取元素的位置信息的代码就让浏览器立刻进行reflow,因为浏览器需要返回元素最新的位置信息

这个test case也可以看到,reflow对性能的损耗有多大。。。

另外还需要注意的,在第一个test case执行完了之后,页面还是一片空白,第一个test case插入的节点并没有展示出来

即使执行了reflow,页面也没有展示UI

直到js执行完才展示

原因是reflow并不是就会执行UI渲染,UI渲染需要等待js执行完毕才会执行,可以理解为浏览器对js的执行和UI渲染都是同一个线程(虽然表现是这样,但是底层应该是js一个线程,UI渲染一个线程,只是浏览器只能执行一个线程)

从上面的例子可以看到,浏览器每次计算reflow都会消耗很多性能,因此浏览器对这块做了优化

浏览器的优化是浏览器会缓存一些DOM操作,直到以下两个条件之一才会进行真正的reflow

浏览器必须要立刻进行reflow,比如上面test case展示的那样,浏览器需要返回元素最新的位置信息

一段时间之后

详见:Rendering: repaint, reflow/relayout, restyle

4.1.2 浏览器原生事件循环

从【2.1 前端的工作】中可以看到,用户对于前端页面的大部分交互都是通过事件

实际上,浏览器在运行过程中,也有一个原生的事件循环

当一个事件被触发,浏览器就会执行该事件的注册callbacks,这时浏览器就进入了js的context

直到js执行完毕,浏览器就会执行UI更新线程,对新的UI改变进行渲染(如果有的话)

上图是AngularJs解释$digest loop时的配图,很好的说明了浏览器的原生事件循环

AngularJs提到$digest loop扩展了在js context里的过程

实际上,$digest loop就是一个类似死循环的逻辑,直到dirty check执行完毕才退出

因此,AngularJs保证了每次dirty check只有1次UI刷新

那么图上面的$evalAsyncqueue是什么呢?

实际上是需要在$digest loop异步执行的callback队列

要知道平常js的异步callback是插入到浏览器原生的事件循环队列里面的,比如setTimeout等

在AngularJs,如果需要在$digest loop里面执行异步callback

就需要把callback放到$evalAsyncqueue里

让异步callback可以在$digest loop内执行

4.1.3 UI更新性能目标

从前面两节可以看到

reflow是在执行js的过程中执行的,它对性能有很大的影响

而UI渲染是js执行之后才执行的,它对性能的消耗更加巨大

因此,UI更新的性能目标有两个:

减少reflow

减少UI渲染次数

4.2 为什么 virtual DOM 快?

下面我们讨论一下为什么virtual DOM会比其他框架的UI更新(dirty check & setter)策略要快

首先,使用defineProperty自动检测变化或者setter类型的就不参与讨论了,每次改属性都会进入绑定流程,想想都可怕

剩下AngularJs和react,他们的更新逻辑的入口都是在关键点调用更新接口

它们的共同点都是一次更新逻辑只会造成一次UI更新

AngularJs通过类似死循环的$digest循环扩展浏览器的原生事件循环,所有更新逻辑都是在js中执行完

react通过virtual DOM的diff得出改动,然后再统一的更新UI,这个过程也是一个js过程结束

两者都有同样的特征:通过大量的js计算完成所有的DOM操作,结束之后才返回浏览器的UI渲染线程

下面根据两者不同点来分析:

AngularJs 的DOM操作是分布式的,DOM操作封装在watcher里面,每当有属性变更,就会触发watcher,然后执行DOM操作

而react的DOM操作是集中式的,在diff之后,根据最终的patches执行DOM操作

集中式的DOM操作可以最大限度的利用浏览器的优化机制,详见【4.1.1 浏览器渲染机制的优化】

AngularJs 组件自带store,组件之间的互相影响可能会引起震荡

具体的是当组件A的属性变化之后,对应watcher里面的操作导致了B组件的属性变化,这时就需要触发相对应的watcher,这个过程有可能无穷无尽

另外AngularJs的dirty check是基于循环的,所以有可能watcher改变的是已经经过dirty check的store,因此dirty check要一直循环,直到所有的store都保持稳定,不再有任何新的变化,才能结束,当这个过程很长的时候,页面就会假死,因为浏览器不能执行UI更新,UI事件不能被处理,因为这个过程本身就在一个UI事件的处理期间,其他新的UI事件还在队列里面等着

这个问题的根本原因是AngularJs不能很好的控制组件之间的store

react没有这个问题就是因为react不是vm库,它没有store,看到这个估计大家都会傻眼,确实,AngularJs和react根本就不是一个可对比的库,本质都不一样

react应用,不管是配合flux还是redux,他们都是先把store计算稳定之后,再交给react去更新UI,这整个过程并不会劫持浏览器的原生事件循环,因此不会有页面的假死现象出现

另外,store计算完全是js计算,不会执行DOM的写操作,需要的只有甚至没有DOM的读操作,对于已经稳定的dom tree来说(浏览器的渲染队列里面已经没有缓存的DOM操作),批量的读操作是不会导致浏览器的repain和reflow的,因此store的计算过程会很快

因此,结论:store的稳定计算很快,react本身渲染也很快,所以使用virtual DOM的react很快

然后大家得出:virtual DOM很快

本质上,需要做的工作都是一样的,只是react把store的计算分离出去而已,但这也正体现了react的内聚性

另外还有一点也需要提及:

AngularJs,vue,avalon等vm库,都是用watcher模式,watcher是长存的

react是实时计算的,在diff之后,old tree就会被销毁,然后保留new tree作为下一次diff的old tree

因此在内存占用方面,也是react有优势

5 回到我的疑惑

5.1 virtual DOM 的本质

根据前面的讨论,我们得出virtual DOM的本质是

根据稳定的输入【state & props】,通过js计算,得出UI更新语句序列

稳定的输入,是指在js计算过程中,不接受新的输入

如果在js计算过程中,需要改变输入源store,那么会通过另外的机制(事件机制)把这些改变放到下一个UI更新事件

感兴趣的同学可以去试试,不过我们一般不会在virtual DOM计算过程中改变store,这也算是react的设计模式的约定之一

通过js计算是指不会插入任何的DOM写操作语句

得出UI更新的语句序列,在web是DOM写操作,在react native就是app的UI更新语句

这也是virtual DOM的一大优势,在这里就不详述了

5.2 riot 做了什么?

riot主要解决react的两个痛点:

jsx难以理解

react库太大

解决方案:

参考web component组织html,js,css

实现粗粒度的virtual DOM

第一点就不多说了

关于第二点,粗粒度的virtual DOM的意思是riot为每个组件创建一个tag对象

tag对象保存了所有它里面的expressions,tag之间和dom tree一样的父子结构组织

这种方式有点类似vm库,但是riot参考react,也有props(静态)和本身数据(动态),具有和react一样的输入

检查更新的过程就是dirty check,但是和AngularJs的做法不同,riot只做一轮,它和react一样,没有sotre,因此没有watcher,也不需要等待store稳定

至于输出,riot没有与react一样,UI更新语句序列也是分布式的

最终得出的结论,riot的实现实际上就是react + angular,另外组件代码组织方式是参考Polymer

正如riot官网上介绍的那样,riot是从已有的工具中提取精华

6 结语

本文主要讲解UI更新这个主题

介绍了浏览器的UI更新相关的内容

并介绍了几个比较流行的前端框架的设计核心

同时讲解了这些设计核心在UI更新方面的分析

实际上这些框架都是老生常谈的内容了

但是通过UI更新这点来剖析这些框架的设计也是一件有趣的事情

也让作者对这些框架有了更深的认识

另外,这些框架的设计理念以及设计模式都非常值得回味

如果有熟悉本文没有介绍到的框架的同学,可以分享出来供大家一起学习

前端

原文地址:https://www.cnblogs.com/xiaocongcong888/p/9490850.html

时间: 2024-11-09 01:07:30

深入探讨ui框架的相关文章

不断学习UI框架的写法

在web开发的过程中,我们会需要用到很多大大小小的插件,比如文本框,下拉树,下拉框等等各种各样的都需要.或许在开发的网页中会用到同一种插件来满足各种各样复杂的业务逻辑,比如简单的一个下拉树,有的地方需要进行拖拽排序,有的地方需要能够进行模糊搜索,有的地方需要权限控制等等.如果仅仅一次被使用到,那么我们在使用的时候,写一个满足需求的插件就可以了.但是当许多地方需要用到时候,你就会发现自己会不停去复制曾经写过的部分代码,随着越用越频繁,复制的量也会越来越大.当其中的一处代码出现bug时,所有复制过该

跨平台UI框架杂思——00

其实我写<我应该用什么界面方案>其实就是想要学习和研究 跨平台的,即时或半即时渲染的 UI 框架.这或许能跟 DirectUI技术 扯上关系——传统的 Windows 界面控件都是一个个的 HWND,然而 DirectUI 的思想就是不用 Win32 原生的控件,而是自己渲染(GDI或其他)上去,并且管理他们的各种输入消息(鼠标.键盘). 当然了,关于渲染部分,可能主要就使用 Direct2D 和 OpenGL 以及 移动端的 Open GL ES.但是关于控件的设计比较需要探讨.我一开始会自

C++UI框架

WTL都算不上什么Framework,就是利用泛型特性对Win API做了层封装,设计思路也没摆脱MFC的影响,实际上用泛型做UI Framework也只能算是一次行为艺术,这个思路下继续发展就会变得没法用了,比如 代码过于复杂,编译太慢,出错不好调试等问题难以解决. 而且封装得也不完全,还是随处可见 HWND HDC之类的东西. 用途主要是写一些很小的程序,或者作为其他UI框架的后端实现部分,比如我写过一个小框架用来做安装卸载程序,非常小,其中创建管理窗口部分是用WTL的.MFC是更高级点的W

ASP.NET MVC搭建项目后台UI框架—5、Demo演示Controller和View的交互

目录 ASP.NET MVC搭建项目后台UI框架—1.后台主框架 ASP.NET MVC搭建项目后台UI框架—2.菜单特效 ASP.NET MVC搭建项目后台UI框架—3.面板折叠和展开 ASP.NET MVC搭建项目后台UI框架—4.tab多页签支持 ASP.NET MVC搭建项目后台UI框架—5.演示Controller和View的交互 这一节,我将用一个Demo来演示在此UI框架中,控制器和视图的交互.以渠道管理为例.效果图如下: 这里我使用了基于jquery的模态窗体组件lhgdialo

基于jquery开发的UI框架整理分析

根据调查得知,现在市场中的UI框架差不多40个左右,不知大家都习惯性的用哪个框架,现在市场中有几款UI框架稍微的成熟一些,也是大家比较喜欢的一种UI框架,那应该是jQuery,有部分UI框架都是根据jQuery研发出来的产品,现在也很常见了. 国产jQuery UI框架 (jUI) DWZ DWZ富客户端框架(jQuery RIA framework), 是中国人自己开发的基于jQuery实现的Ajax RIA开源框架.设计目标是简单实用,快速开发,降低ajax开发成本. jQuery 部件布局

Android酷炫实用的开源框架(UI框架)

前言 忙碌的工作终于可以停息一段时间了,最近突然有一个想法,就是自己写一个app,所以找了一些合适开源控件,这样更加省时,再此分享给大家,希望能对大家有帮助,此博文介绍的都是UI上面的框架,接下来会有其他的开源框架(如:HTTP框架.DB框架). 1.Side-Menu.Android分类侧滑菜单,Yalantis 出品.项目地址:https://github.com/Yalantis/Side-Menu.Android2.Context-Menu.Android可以方便快速集成漂亮带有动画效果

常用响应式 Web UI 框架

1. Bootstrap Bootstrap是快速开发Web应用程序的前端工具包.它是一个CSS和HTML的集合,它使用了最新的浏览器技术,给你的Web开发提供了时尚的版式,表单,buttons,表格,网格系统等等.官方网站: http://twitter.github.com/bootstrap/github: https://github.com/twitter/bootstrap 2. Foundation Foundation 是一个易用.强大而且灵活的框架,用于构建基于任何设备上的 W

Chisel辅助iOS 应用程序调试,MusicApp模仿酷狗4.0 UI框架

本文转载至 http://www.cocoachina.com/ios/20140825/9446.html Chisel Chisel集合了大量的LLDB 命令来辅助iOS 应用程序调试,并支持添加本地和自定义的命令.以下是其中所包含的一些命令,并对其适用于iOS还是OS X进行了区分: M13ProgressSuite 该项目包含了多种不同的风格的进程指示图,比如普通圆环形.分段圆形加载.圆形饼图加载以及条形加载等等,比如其中UINavigationBar的进程动画非常像苹果的Messag

游戏UI框架设计(三) : 窗体的层级管理

游戏UI框架设计(三) ---窗体的层级管理 UI框架中UI窗体的"层级管理",最核心的问题是如何进行窗体的显示管理.窗体(预设)的显示我们前面定义了三种类型: 普通.隐藏其他.反向切换.代码如下: "普通显示"模式允许多个窗体同时显示,这种类型应用最多.例如RPG中的主城界面(见下图). "隐藏其他界面" 模式一般应用于全局性的窗体.我们在开发此类窗体时,为了减少UI渲染压力.提高Unity渲染效率,则设置被覆盖的窗体为"不可见&qu