前端UI框架


    可以看到前端要做的工作还是比较直观,简单的。但是,当一个页面很复杂,比如SPA的时候,就需要有一个成熟的架构来提升前端开发的效率,前端框架提供一套成熟的解决方案来组织前端代码,前端数据流等。前端框架的核心作用有且并不完全是:
        模块化,组件化,提高可复用性
        数据流清晰,提高可维护性
    常见的前端框架模式有:MVC, MVP, MVVM(详细参考:http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html)

一、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渲染&更新

    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会失去焦点。

     整个在浏览器的渲染过程中(页面初始化,用户行为改变界面样式,动画改变界面样式等),reflow(回流)和repaint(重绘) 会大大影响web性能,尤其是手机页面。因此我们在页面设计的时候要尽量减少reflow和repaint。
    什么是reflow和repaint?(原文链接:http://www.cnblogs.com/Peng2014/p/4687218.html)
    reflow:例如某个子元素样式发生改变,直接影响到了其父元素以及往上追溯很多祖先元素(包括兄弟元素),这个时候浏览器要重新去渲染这个子元素相关联的所有元素的过程称为回流。
    reflow:几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
    repaint:如果只是改变某个元素的背景色、文 字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器     repaint(重绘)。repaint 的速度明显快于 reflow
    下面情况会导致reflow发生
        1:改变窗口大小
        2:改变文字大小
        3:内容的改变,如用户在输入框中敲字
        4:激活伪类,如:hover
        5:操作class属性
        6:脚本操作DOM
        7:计算offsetWidth和offsetHeight
        8:设置style属性
    那么为了减少回流要注意哪些方式呢?
        1:不要通过父级来改变子元素样式,最好直接改变子元素样式,改变子元素样式尽可能不要影响父元素和兄弟元素的大小和尺寸

        2:尽量通过class来设计元素样式,切忌用style

var bstyle = document.body.style; // cache
bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint 
bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint
bstyle.fontSize = "2em"; // reflow, repaint
// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

对上面代码优化:

.b-class{
  padding:20px;
  color:blue;
  border:10px solid red;
  background-color:#fad;
  font-size:2em;
}
$div.addClass("b-class");
        3:实现元素的动画,对于经常要进行回流的组件,要抽离出来,它的position属性应当设为fixed或absolute
        4:权衡速度的平滑。比如实现一个动画,以1个像素为单位移动这样最平滑,但reflow就会过于频繁,CPU很快就会被完全占用。如果以3个像素为单位移动就会好很多。
        5:不要用tables布局的另一个原因就是tables中某个元素一旦触发reflow就会导致table里所有的其它元素reflow。在适合用table的场合,可以设置table-layout为auto或fixed,
        6:这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。
        7:css里不要有表达式expression
        8:减少不必要的 DOM 层级(DOM depth)。改变 DOM 树中的一级会导致所有层级的改变,上至根部,下至被改变节点的子节点。这导致大量时间耗费在执行 reflow 上面。
        9:避免不必要的复杂的 CSS 选择器,尤其是后代选择器(descendant selectors),因为为了匹配选择器将耗费更多的 CPU。
        10: 尽量不要过多的频繁的去增加,修改,删除元素,因为这可能会频繁的导致页面reflow,可以先把该dom节点抽离到内存中进行复杂的操作然后再display到页面上。

在div.first里面加入div.second,在div.second里面加入div.third:

$divS = $("<div class='second'></div>");
$(div.first).append($divS));//reflow
$divT = $("<div class='third'></div>");
$divS.append($divT);//reflow

优化代码:

$divS = $("<div class='second'></div>");
$divT = $("<div class='third'></div>");
$divS.append($divT);
$(div.first).append($divS));//reflow

或者:

var $divF = $(div.first);
$divS = $("<div class='second'></div>");
$divS.hide();
$(div.first).append($divS));
$divT = $("<div class='third'></div>");
$divS.append($divT);
$divS.show();//reflow
    11:请求如下值offsetTop, offsetLeft, offsetWidth, offsetHeight,scrollTop/Left/Width/Height,clientTop/Left/Width/Height,浏览器会发生reflow,建议将他们合并到一起操作,可以减少回流的次数。

如果我们要经常去获取和操作这些值,则可以先将这些值缓存起来例如:

var windowHeight = window.innerHeight;//reflow
for(i=0;i<10;i++){
  $body.height(windowHeight++);
  一系列关于windowHeight的操作.......
}

三、UI框架

3.1Bootstrap

    Bootstrap 是一个框架吗? 是的,就算它只是一系列常用 CSS 和 jQuery plugin 的集合,它还是一个框架。不过它更多的是一个常用代码片段的集合,而不是一个那种能带来设计模式的框架。

    Bootstrap 带给你的是栅格系统,是预置的按钮、表单、列表、导航、响应式等等样式,它在你独立开发的时候非常有用,因为能节约用来「设计网页」的时间,而且这套预置的样式本身「也不丑」,自然是受很多公司「前期快速开发」的青睐了。
    Bootstrap 更像UI library,就像与之类似的 purecss、 Foundation 一样。当然最近比较火的微信移动端使用的 UI 库 weui 自然也属于这个范畴。

3.2jQuery

    jQuery 是一个现象,它重新定义了 dom 操作这一行为。从某种意义讲,jQuery 是一个 framework,因为它让直接操作 dom 来进行 UI 交互成为了一种设计模式(笑)。这是个玩笑, jQuery 更多只能看做一个更抽象的 DOM API,弥补的原生 DOM API 的不足,并增加了很多常用的 helper。常见的 Web 设计模式就是 MVC 和 MVVM,它们和 jQuery 之间另一个不同就是对于数据的处理。用 jQuery 写的代码常常直接用 dom 来和 data 打交道,需要「手动地」更新页面。反之 MVC 和 MVVM 通常把数据存储在 model 这个 layer 上,通过一定的绑定机制实施更有效率的页面渲染和更新。

3.3BackBone

    BackBone 经常和 jQuery 一起用, MVC 模式 + 一个高度抽象的 DOM API 曾经征服了很多开发者的心。然而 BackBone 经常被人诟病为「不是 MVC」的 MVC,原因在于你很难把用 jQuery 写出的代码合理地用 MVC 模式来进行归纳,在我看来,这是因为没有一个规范的 View layer 来组织 dom 相关的代码。

3.4 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.5React ( 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,它也可以是复杂的整个子树的增删移动,这时就可以使用方式二,重新渲染整个子树。

    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操作可以最大限度的利用浏览器的优化机制。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很快。

    本质上,需要做的工作都是一样的,只是react把store的计算分离出去而已,但这也正体现了react的内聚性。另外还有一点:AngularJs,vue,avalon等vm库,都是用watcher模式,watcher是长存的。react是实时计算的,在diff之后,old tree就会被销毁,然后保留new tree作为下一次diff的old tree。因此在内存占用方面,也是react有优势。

3.6Vue(MVVM数据劫持+观察者模式)


    js中有两种方法可以侦测到变化,Object.defineProperty 和 ES6 的proxy。到目前为止vue还是用的 Object.defineProperty。

   initData,这里主要做了两件事,一是将_data上面的数据代理到vm上,二是通过执行 observe(data, true / asRootData /)将所有data变成可观察的,即对data定义的每个属性进行getter/setter操作,这里就是Vue实现响应式的基础。

    Observer类是将每个目标对象(即data)的键值转换成getter/setter形式,用于进行依赖收集以及调度更新。

   Watcher是一个观察者对象。依赖收集以后Watcher对象会被保存在Dep的subs中,数据变动的时候Dep会通知Watcher实例,然后由Watcher实例回调cb进行视图的更新。

   Dep:被Observer的data在触发 getter 时,Dep 就会收集依赖的 Watcher ,其实 Dep 就像刚才说的是一个书店,可以接受多个订阅者的订阅,当有新书时即在data变动时,就会通过 Dep 给 Watcher 发通知进行更新。

    在 Vue 中初始化渲染时,视图上绑定的数据就会实例化一个 Watcher,依赖收集就是是通过属性的 getter 函数完成的,Observer 、Watcher 、Dep 都与依赖收集相关。其中 Observer 与 Dep 是一对一的关系, Dep 与 Watcher 是多对多的关系,Dep 则是 Observer 和 Watcher 之间的纽带。依赖收集完成后,当属性变化会执行被 Observer 对象的 dep.notify() 方法,这个方法会遍历订阅者(Watcher)列表向其发送消息, Watcher 会执行 run 方法去更新视图。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值