前端架构设计

  • Post author:
  • Post category:其他

前端架构师们认为有多个关键的决策需要在项目启动之初就制定下来,如果等到开发阶段的后期再考虑,不是已经用不上,而是一开始错误的决定已经造成了无法挽回的损失。一旦做出这些决策,我们的任务就是去辅助视觉设计、平台开发、底层结构,使之能最大程度满足需求。
如果我们有这样的机会,那么可以创建一个很长的愿望清单:

  • 模块化内容。希望尽可能复用小的组件,而不是弄出几十个不同的内容块。
  • 全面测试。我们之前经常出现这样的情况,大量的前端代码合入到主干,然后导致几个月前的代码出现运行问题。这样太浪费时间了,所以我们决定要像测试后端代码一样测试我们的新框架,达到一样高的代码覆盖水平。
  • 流式处理。引入Git流程。
  • 详细的文档。

模块化标记

我们都在追求的理想的状态是,网站每一行HTML都由程序自动生成,而作为前端开发人员,我们只需要管理这个用来产生标记的模板和流程,遗憾的是,现实通常并非如此。即使在最好的情况下,也存在用户生成的内容,而这些内容几乎都无法自动添加CSS类名来标记。无论CMS系统(可以理解为后台)自动生成HTML的能力如何,让CMS决定类似表单和导航栏这样的标记,有时候会更简单。
模块化标记和手写的静态标记的区别在于,程序化地执行完之后,我们还可以通过一套类名系统给标记动态添加CSS类名。而且不再通过元素标签和层级关系来决定视觉外观。让我们看看如何用BEM原则(一个CSS规则,下面会说到)模块化地实现一个简单的导航:

   <nav class="nav">
     <ul class="nav__container">
        <li class="nav__item">
           <a href="/products" class="nav__link">
              <ul class="nav__container--secondary">
                 <li class="nav__item--secondary">
                    <a href="/socks" class="nav__link--secondary">

要使用这种模块化方法,我们首先需要改变构建页面的方法和思路。作为前端开发人员,我们的工作就是把视觉语言拆解成最小单元,拆解之后,我们可以创建规则,对这些最小单元进行重组
上面提到的BEM(Block Element Modifier,块元素修饰符)是一个CSS类名命名规则,它建议每个元素都添加带有如下内容的CSS类名:

  • 块名。所属组件的名称。
  • 元素。元素在块里面的名称
  • 修饰符。任何与块或元素相关联的修饰符

过多的CSS依赖

如果我们想渲染我们样式中的某一块内容,首先需要加载以下内容

  • Boostrap CSS: 114KB(未压缩)。其实网站本身没怎么调用Boostrap库的代码,但我们编写所有CSS的前提条件都是Boostrap已经重置了基本样式。
  • 核心的网站CSS: 500KB。虽然一般来说,每块内容都有一个单独的关联文件。但这个文件不是这块内容的单一样式来源。样式不仅来源位置多样,并且常常基于位置和页面的类被覆盖重写。

自上而下的样式命名方法意味着,每次修改我们都要写一个更长的、更具体的选择器,同时,因为标记顺序极为严格,每块内容都很难重排或者替换。当然我们可以抽出一个单独文件,并把它需要的所有样式合并到单独的一个文件里,但是这么做基本意味着完全重做这个组件里的CSS文件,每块内容都很难重排或者替换。
因此我们指定了一些规则,以下是我们指定这些规则时需遵循的规范:

  • 只包含不可变的规则,而不是笼统的说明。
  • 总是把规则提炼成最简单的表达。
  • 总是首先说明规则时什么,再说明“如果不这样,那么会怎么样”
  • 每个规则必须包含以下词的一个——总是、永远不要、只有、每一个、不要、要

最后指定的设计系统的规则如下

  • 永远不要给布局的子内容强加内边距和元素样式。布局只关注垂直对齐、水平对齐和文字间距。
  • 主题和别的数据属性值永远不要强制改变外观,他们必须保持布局、组件和元素可以应用于其上。
  • 组件总是贴着它的父容器的四个边,元素都没有上外边距和左外边距、所有的最后节点(最右边和最下边的节点)的外边距都会被清楚。
  • 组件本身永远不要添加背景、宽度、浮动、内边距和外边距的样式,组件样式是组件内元素的样式。
  • 每个元素都有且只有一个唯一的且作用于只在组件内的CSS类名,所有的样式都是直接应用到这个选择器上, 并且只有上下文和主题能修改元素的样式。

JavaScript

为js创建规范是非常有必要的,可以参考以下建议:
保持代码整洁。JS Hint是这写工具中一个很好的例子,这里有几条可以通过JS Hint检查的规则:

  • 强制使用===和!==代替==和!=
  • 限制代码块嵌套深度
  • 限制函数的参数数量
  • 如果函数重复定义,发出警告
  • 如果变量创建后未被使用,发出警告

创建可复用的函数。比如我们常用的addClass操作,如果需要多次创建新的.green-alert类名,只需要修改定义好的add_background方法:

   $.fn.add_background = function(color){
       this.css('background-color', color);
       return this;
   }

测试核心

在开始为应用程序规划测试时,请记住以下几条建议:

  • 测试用例应该在建站的同时或之前开始编写
  • 测试代码是真实的代码,应该一起或立即提交到系统代码库中。
  • 必须在所有的测试用例都通过之后,才能把代码合并到主干上。
  • 在主干上运行测试工具,结果都应该为通过。

单元测试时将应用程序分解为尽可能小的函数,并创建可重复的、自动化的测试用例的过程。“一次只做一件事,并把它做好”是构建基于单元测试的应用程序的原则(函数式),我们在写函数时经常想同时实现很多功能,结果最后不仅降低了了效率,还增加了测试的难度。
单元测试的基本思路是调用要测试的函数,传递一些预先设置好的参数,并描述结果是什么。

   function calculateShipping(distance){
      switch(distance){
          case(distance < 25):
             shipping = 4;
             break;
          case(distance < 100):
             shipping = 5;
             break;
          case(distance < 1000):
             shipping = 6;
             break;
          return shipping;   
      }
   }
   QUnit.test('Calculate Shipping', function(assert){
       assert.equal(calculateShipping(24), 4, '24 Miles');
       assert.equal(calculateShipping(99), 5, '99 Miles');
       assert.equal(calculateShipping(999), 6, '999 Miles');
       assert.equal(calculateShipping(1000), 7, '1000 Miles');
   })

确定合适的测试覆盖率是一件很难权衡的事,在我们的项目中,如果一个新功能需要花费8小时开发完成,我们要确保另外预留2个小时来编写用例并验证测试覆盖率。尽管这会多花费25%的时间,但这其实会节省很多后续回头追查bug的时间。

性能测试

把你的网站性能基线与行业平均水准和通用的最佳实践相比较是必不可少的。
HTTPArchive(http://httparchive.org/)是个不错的服务,它测试并记录了几十万个网站,有几个值得注意的数据如下:

  • 页面大小:2061KB
  • 总请求数:99
  • 可缓存资源所占比例

因此,如果想让自己的网站比大部分网站都快,可以考虑设定一个目标:一个1648KB的网站,包括79个请求,其中44个请求可以缓存,这将使你的网站领先平均水平20%。