暴走的果果

  • 首页

  • 分类

前端性能优化——掘金小册学习笔记

发表于 2018-10-27 | 分类于 性能优化

从一道面试题说起

从输入 URL 到页面加载完成,发生了什么?

首先我们需要通过 DNS(域名解析系统)将 URL 解析为对应的 IP 地址,然后与这个 IP 地址确定的那台服务器建立起 TCP 网络连接,随后我们向服务端抛出我们的 HTTP 请求,服务端处理完我们的请求之后,把目标数据放在 HTTP 响应里返回给客户端,拿到响应数据的浏览器就可以开始走一个渲染的流程。渲染完毕,页面便呈现给了用户,并时刻等待响应用户的操作(如下图所示)
1

  • DNS 解析
  • TCP 连接
  • HTTP 请求抛出
  • 服务端处理请求,HTTP 响应返回
  • 浏览器拿到响应数据,解析响应内容,把解析的结果展示给用户

各个击破

  • 网络层面的性能优化
  1. DNS 解析花时间,能不能尽量减少解析次数或者把解析前置?
    能——浏览器 DNS 缓存和 DNS prefetch(后端)
  2. TCP 每次的三次握手都急死人,有没有解决方案?
    有——长连接、预连接、接入 SPDY 协议(后端)
  3. 那么 HTTP 请求呢?
    在减少请求次数和减小请求体积方面,是不是可以做些工作呢
  4. 资源所在服务器是不是越近越好?
    部署时就把静态资源放在离我们更近的 CDN 上是不是就能更快一些?
  • 浏览器端的性能优化
    前端工程师一展拳脚的地方——资源加载优化、服务端渲染、浏览器缓存机制的利用、DOM 树的构建、网页排版和渲染过程、回流与重绘的考量、DOM 操作的合理规避等等
    2

网络篇1:webpack性能调优

我们从输入 URL 到显示页面这个过程中,涉及到网络层面的,有三个主要过程:

  • DNS 解析
  • TCP 连接
  • HTTP 请求/响应

前端工程师优化方向——HTTP 请求/响应

  • 减少请求次数
  • 减少单次请求所花费的时间
    3

没错,这就是我们每天用构建工具在做的事情。时下主流构建工具主要是webpack。
4

包组成可视化工具
5

网络篇2:图片优化——质量与性能的博弈

《高性能网站建设指南》的作者 Steve Souders 曾在 2013 年的一篇博客中提到:

1
<strong style="color: gray">我的大部分性能优化工作都集中在 JavaScript 和 CSS 上,从早期的 Move Scripts to the Bottom 和 Put Stylesheets at the Top 规则。为了强调这些规则的重要性,我甚至说过,“JS 和 CSS 是页面上最重要的部分”。 几个月后,我意识到这是错误的。图片才是页面上最重要的部分。 我关注 JS 和 CSS 的重点也是如何能够更快地下载图片。图片是用户可以直观看到的。他们并不会关注 JS 和 CSS。确实,JS 和 CSS 会影响图片内容的展示,尤其是会影响图片的展示方式(比如图片轮播,CSS 背景图和媒体查询)。但是我认为 JS 和 CSS 只是展示图片的方式。在页面加载的过程中,应当先让图片和文字先展示,而不是试图保证 JS 和 CSS 更快下载完成。</strong>

雅虎军规和 Google 官方的最佳实践也都将图片优化列为前端性能优化必不可少的环节——图片优化的优先级可见一斑。
图片优化?——权衡,图片体积压缩->牺牲部分成像质量。

  • 关键点:寻求质量与性能之间的平衡点

截止到 2018 年 10月,过去一年总的 web 资源的平均请求体积是这样的:
6

具体到图片这一类的资源,平均请求体积
7

当然,随着我们工程师在性能方面所做的努力越来越有成效,平均来说,不管是资源总量还是图片体积,都在往越来越轻量的方向演化。这是一种值得肯定的进步。
但同时我们不得不承认,如图所示的这个图片体积,依然是太大了。图片在所有资源中所占的比重,也足够“触目惊心”了。为了改变这个现状,我们必须把图片优化提上日程。

不同场景下的图片方案选型

常用的格式:JPEG/JPG、PNG、WebP、Base64、SVG

  • 前置知识:在计算机中,像素用二进制数来表示。不同的图片格式中像素与二进制位数之间的对应关系是不同的。一个像素对应的二进制位数越多,它可以表示的颜色种类就越多,成像效果也就越细腻,文件体积相应也会越大。 一个二进制位表示两种颜色(0|1 对应黑|白),如果一种图片格式对应的二进制位数有 n 个,那么它就可以呈现 2^n 种颜色

  • Joint Photographic Experts Group——联合图像专家小组
    “JPEG标准”,针对图像的压缩而制定的标准,使用JPEG标准压缩的图片文件,被称为“JPEG文件”,这种文件的扩展名通常是JPG、JPEG、JPE、JFIF以及JIF

8
9
10
11
12

存储篇1:浏览器缓存机制

缓存可以减少网络 IO 消耗,提高访问速度。浏览器缓存机制有四个方面,它们按照获取资源时请求的优先级依次排列如下:

  1. Memory Cache
  2. Service Worker Cache
  3. HTTP Cache
  4. Push Cache
    13
    14

1.Memory Cache

内存中的缓存,浏览器最先尝试命中的缓存,效率上,它是响应速度最快的缓存。
当进程结束后,也就是 tab 关闭以后,内存里的数据也将不复存在。
那么哪些文件会被放入内存呢?
我们发现,Base64 格式的图片,几乎永远可以被塞进 memory cache,这可以视作浏览器为节省渲染开销的“自保行为”;
此外,体积不大的 JS、CSS 文件,也有较大地被写入内存的几率;
相比之下,较大的 JS、CSS 文件就没有这个待遇了,内存资源是有限的,它们往往被直接甩进磁盘。

2.Service Worker Cache

Service Worker 是一种独立于主线程之外的 Javascript 线程。脱离于浏览器窗体,因此无法直接访问 DOM。
这个“幕后工作者”可以帮我们实现离线缓存、消息推送和网络代理等功能。
借助 Service worker 实现的离线缓存就称为 Service Worker Cache。

3.HTTP Cache

15

  • Chrome官方给出的缓存决策图
    当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;
    否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;
    否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;
    然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;
    最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数
    16

存储篇2:本地存储——Cookie/Web Storage/indexDB

  • Cookie
    17

  • Web Storage
    18
    19
    20

渲染篇1:服务端渲染

  • SSR:服务端渲染
    服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。
    客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。
    使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。
    比如知乎就是典型的服务端渲染案例:
    21

  • SSR:服务端渲染解决了什么性能问题?
    首屏加载速度过慢。在客户端渲染模式下,我们除了加载 HTML,还要等渲染所需的这部分 JS 加载完,之后还得把这部分 JS 在浏览器上再跑一遍。这一切都是发生在用户点击了我们的链接之后的事情,在这个过程结束之前,用户始终见不到我们网页的庐山真面目,也就是说用户一直在等!相比之下,服务端渲染模式下,服务器给到客户端的已经是一个直接可以拿来呈现给用户的网页,中间环节早在服务端就帮我们做掉了

SSR存在什么问题呢?
服务器资源稀少而宝贵,不到万不得已不使用

渲染篇2:浏览器端优化

浏览器内核可以分成两部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎。早期渲染引擎和 JS 引擎并没有十分明确的区分,但随着 JS 引擎越来越独立,内核也成了渲染引擎的代称(下文我们将沿用这种叫法)。渲染引擎又包括了 HTML 解释器、CSS 解释器、布局、网络、存储、图形、音视频、图片解码器等等零部件。
22

目前市面上常见的浏览器内核可以分为这四种:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。
23
24
25

DOM操作

JS 引擎和渲染引擎(浏览器内核)是独立实现的。当我们用 JS 去操作 DOM 时,本质上是 JS 引擎和渲染引擎之间进行了“跨界交流。
26

当我们对 DOM 的修改会引发它外观(样式)上的改变时,就会触发回流或重绘。
27

  • 回流:当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)
  • 重绘:当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。
    由此我们可以看出,重绘不一定导致回流,回流一定会导致重绘。硬要比较的话,回流比重绘做的事情更多,带来的开销也更大。但这两个说到底都是吃性能的,所以都不是什么善茬。

  • 怎么办?
    1.减少DOM操作
    2.考虑JS 的运行速度,比 DOM 快得多这个特性。我们减少 DOM 操作的核心思路,就是让 JS 去给 DOM 分压

    Event Loop与异步更新策略

  • 事件循环中的异步队列有两种:macro(宏任务)队列和 micro(微任务)队列。
  • 常见的 macro-task 比如: setTimeout、setInterval、 setImmediate、script(整体代码)、 I/O 操作、UI 渲染等。
  • 常见的 micro-task 比如: process.nextTick、Promise、MutationObserver 等
    28

一个完整的 Event Loop 过程,可以概括为以下阶段:

  1. 初始状态:调用栈空。micro 队列空,macro 队列里有且只有一个 script 脚本(整体代码)。
  2. 全局上下文(script 标签)被推入调用栈,同步代码执行。在执行的过程中,通过对一些接口的调用,可以产生新的 macro-task 与 micro-task,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出 macro 队列,这个过程本质上是队列的 macro-task 的执行和出队的过程。
  3. 上一步我们出队的是一个 macro-task,这一步我们处理的是 micro-task。但需要注意的是:当 macro-task 出队时,任务是一个一个执行的;而 micro-task 出队时,任务是一队一队执行的。因此,我们处理 micro 队列这一步,会逐个执行队列中的任务并把它出队,直到队列被清空。
  4. 执行渲染操作,更新界面(敲黑板划重点)。
  5. 检查是否存在 Web worker 任务,如果有,则对其进行处理 。
    (上述过程循环往复,直到两个队列都清空)
    29

我们更新 DOM 的时间点,应该尽可能靠近渲染的时机。
当我们需要在异步任务中实现 DOM 修改时,把它包装成 micro 任务是相对明智的选择。

回流与重绘

  • 最“贵”的操作:改变 DOM 元素的几何属性
  • “价格适中”的操作:改变 DOM 树的结构
  • 最容易被忽略的操作:获取一些特定属性的值

如何规避呢?

  • 将敏感属性缓存起来,避免频繁改动

  • 避免逐条改变样式,使用类名去合并样式

应用篇

懒加载

30
31

事件的节流与去抖

输入框、滚动条、窗口resize……
节流去抖

前端性能检测——可视化检测

Performance面板
32

可视化检测LightHouse
33

可编程性能上报方案——W3C性能API
34
关键性能指标:firstbyte、fpt、tti、ready 和 load 时间

雅虎35条军规——前端性能优化

发表于 2018-08-13 | 更新于 2018-08-15 | 分类于 性能优化

本文主要考虑客户端性能、服务器端和网络性能,内容框架来自Yahoo Developer Network,包含 7 个类别共 35 条前端性能优化最佳实践,在此基础上补充了一些相关或者更符合主流技术的内容。

前端性能优化

前端性能的一个重要指标是页面加载时间,不仅事关用户体验,也是搜索引擎排名考虑的一个因素。

  • 来自Google的数据表明,一个有10条数据0.4秒能加载完的页面,变成30条数据0.9秒加载完之后,流量和广告收入下降90%。
  • Google Map 首页文件大小从100KB减小到70-80KB后,流量在第一周涨了10%,接下来的三周涨了25%。
  • 亚马逊的数据表明:加载时间增加100毫秒,销量就下降1%。

以上数据更说明「加载时间就是金钱」,前端优化主要围绕提高加载速度进行。

一、页面内容

(1)减少HTTP请求数

Web 前端 80% 的响应时间花在图片、样式、脚本等资源下载上。最直接的方式是减少页面所需资源,但并不现实。所以,减少HTTP请求数主要的途径是:

合并JS/CSS文件。服务器端(CDN)自动合并,基于Node.js的文件合并工具,通过把所有脚本放在一个文件中的方式来减少请求数。

使用CSS Sprite将背景图片合并成一个文件,通过background-image 和 background-position 控制显示

行内图片(Base64编码)。使用Data URI scheme将图片嵌入HTML或者CSS中;或者将CSS、JS、图片直接嵌入HTML中,会增加文件大小,也可能产生浏览器兼容及其他性能问题。

减少页面的HTTP请求数是个起点,这是提升站点首次访问速度的重要指导原则。

(2)减少DNS查询

用户输入URL以后,浏览器首先要查询域名(hostname)对应服务器的IP地址,一般需要耗费20-120毫秒时间。DNS查询完成之前,浏览器无法从服务器下载任何数据。

基于性能考虑,ISP、局域网、操作系统、浏览器都会有相应的DNS缓存机制。

  • IE缓存30分钟,可以通过注册表中DnsCacheTimeout项设置;
  • Firefox缓存1分钟,通过network.dnsCacheExpiration配置;

另外减少不同的主机名可减少DNS查找,减少不同主机名的数量同时也减少了页面能够并行下载的组件数量,避免DNS查找削减了响应时间,而减少并行下载数量却增加了响应时间。原则是把组件分散在2到4个主机名下,这是同时减少DNS查找和允许高并发下载的折中方案。

(3)避免重定向

HTTP重定向通过301/302状态码实现。下面是一个有301状态码的HTTP头

1
2
3
HTTP/1.1 301 Moved Permanently 
Location: http://example.com/newuri
Content-Type: text/html

浏览器会自动跳转到Location域指明的URL。重定向需要的所有信息都在HTTP头部,而响应体一般是空的。其实额外的HTTP头,比如Expires和Cache-Control也表示重定向。除此之外还有别的跳转方式:refresh元标签和JavaScript,但如果你必须得做重定向,最好用标准的3xxHTTP状态码,主要是为了让返回按钮能正常使用。

客户端收到服务器的重定向响应后,会根据响应头中Location的地址再次发送请求。重定向会影响用户体验,尤其是多次重定向时,用户在一段时间内看不到任何内容,只看到浏览器进度条一直在刷新。

  • 最浪费的重定向经常发生、而且很容易被忽略:URL 末尾应该添加/但未添加。比如,访问http://astrology.yahoo.com/astrology将被301重定向到 http://astrology.yahoo.com/astrology/(注意末尾的 /)。如果使用 Apache,可以通过Alias或mod_rewrite或DirectorySlash解决这个问题。
  • 网站域名变更:CNAME结合Alias或mod_rewrite或者其他服务器类似功能实现跳转。

(4)缓存Ajax请求

最重要的的优化方式是缓存响应结果。有尚未过期的Expires或者Cache-Control HTTP头,那么之前的资源就可以从缓存中读出。必须通知浏览器,应该继续使用之前缓存的资源响应,还是去请求一个新的。可以通过给资源的Ajax URL里添加一个表明用户资源最后修改时间的时间戳来实现。如果资源从上一次下载之后再没有被修改过,时间戳不变,资源就将从浏览器缓存中直接读出,从而避免一次额外的HTTP往返消耗。详见服务器-添加Expires或Cache响应头。

(5)延迟加载

页面初始加载时哪些内容是绝对必需的?不在答案之列的资源都可以延迟加载。比如:

  • 非首屏使用的数据、样式、脚本、图片等;
  • 用户交互时才会显示的内容。

遵循「渐进增强」理念开发的网站:JavaScript用于增强用用户体验,但没有(不支持) JavaScript也能正常工作,完全可以延迟加载JavaScript。

将首屏以外的HTML放在不渲染的元素中,如隐藏的<textarea>,或者type属性为非执行脚本的<script>标签中,减少初始渲染的DOM元素数量,提高速度。等首屏加载完成或者用户操作时,再去渲染剩余的页面内容。

(6)预加载

预先加载利用浏览器空闲时间请求将来要使用的资源,以便用户访问下一页面时更快地响应。

  • 无条件预先加载:页面加载完成(load)后,马上获取其他资源。以 google.com 为例,首页加载完成后会立即下载一个 Sprite 图片,此图首页不需要,但是搜索结果页要用到。
  • 有条件预先加载:根据用户行为预判用户去向,预载相关资源。比如search.yahoo.com开始输入时会有额外的资源加载。Chrome 等浏览器的地址栏也有类似的机制。
  • 有「阴谋」的预先加载:页面即将上线新版前预先加载新版内容。网站改版后由于缓存、使用习惯等原因,会有旧版的网站更快更流畅的反馈。为缓解这一问题,在新版上线之前,旧版可以利用空闲提前加载一些新版的资源缓存到客户端,以便新版正式上线后更快的载入。

(7)减少DOM元素数量

复杂的页面不仅下载的字节更多,JavaScript DOM操作也更慢。例如,同是添加一个事件处理器,500个元素和5000个元素的页面速度上会有很大区别。

从以下几个角度考虑移除不必要的标记:

  • 是否还在使用表格布局?
  • 塞进去更多的<div>仅为了处理布局问题?也许有更好、更语义化的标记。
  • 能通过伪元素实现的功能,就没必要添加额外元素,如清除浮动。

浏览器控制台中输入以下代码可以计算出页面中有多少 DOM 元素:

document.getElementsByTagName(‘*’).length;

为什么不使用表格布局?

  • 更多的标签,增加文件大小;
  • 不易维护,无法适应响应式设计;
  • 性能考量,默认的表格布局算法会产生大量重绘

(8)划分内容到不同域名

浏览器一般会限制每个域的并行线程(一般为6个,甚至更少),使用不同的域名可以最大化下载线程,但注意保持在2-4个域名内,以避免DNS查询损耗。

例如,动态内容放在csspod.com上,静态资源放在static.csspod.com上。这样还可以禁用静态资源域下的Cookie,减少数据传输,详见Cookie 优化。

(9)尽量减少iframe的使用

用iframe可以把一个HTML文档插入到父文档里,重要的是明白iframe是如何工作的并高效地使用它。

<iframe>的优点:

  • 可以用来加载速度较慢的第三方资源,如广告、徽章;
  • 可用作安全沙箱;
  • 可以并行下载脚本。

<iframe>的缺点:

  • 加载代价昂贵,即使是空的页面;
  • 阻塞页面 load 事件触发;

    Iframe 完全加载以后,父页面才会触发 load 事件。 Safari、Chrome 中通过 JavaScript 动态设置 iframe src 可以避免这个问题。

  • 缺乏语义。

(10)避免404错误

HTTP请求很昂贵,返回无效的响应(如404未找到)完全没必要,降低用户体验而且毫无益处。
一些网站设计很酷炫、有提示信息的404页面,有助于提高用户体验,但还是浪费服务器资源。尤其糟糕的是外部脚本返回404,不仅阻塞其他资源下载,浏览器还会尝试把404页面内容当作JavaScript解析,消耗更多资源。

二、服务器

(1)使用CDN

用户与服务器的物理距离对响应时间也有影响。把内容部署在多个地理位置分散的服务器上能让用户更快地载入页面。但具体要怎么做呢?

网站80-90%响应时间消耗在资源下载上,减少资源下载时间是性能优化的黄金法则。相比分布式架构的复杂和巨大投入,静态内容分发网络(CDN)可以以较低的投入,获得加载速度有效提升。

内容分发网络(CDN)是一组分散在不同地理位置的web服务器,用来给用户更高效地发送内容。典型地,选择用来发送内容的服务器是基于网络距离的衡量标准的。例如:选跳数(hop)最少的或者响应时间最快的服务器。

(2)添加Expires或Cache-Control响应头

  • 静态内容:将 Expires 响应头设置为将来很远的时间,实现「永不过期」策略;
  • 动态内容:设置合适的 Cache-Control 响应头,让浏览器有条件地发起请求。

Cache-Control头在HTTP/1.1规范中定义,取代了之前用来定义响应缓存策略的头(例如 Expires、Pragma)。当前的所有浏览器都支持Cache-Control,因此,使用它就够了。

(3)启用Gzip

前端工程师可以想办法明显地缩短通过网络传输HTTP请求和响应的时间。毫无疑问,终端用户的带宽速度,网络服务商,对等交换点的距离等等,都是开发团队所无法控制的。但还有别的能够影响响应时间的因素,压缩可以通过减少HTTP响应的大小来缩短响应时间。

Gzip压缩通常可以减少70%的响应大小,对某些文件更可能高达90%,比Deflate更高效。主流 Web 服务器都有相应模块,而且绝大多数浏览器支持gzip解码。所以,应该对HTML、CSS、JS、XML、JSON等文本类型的内容启用压缩。

注意!!! 图片和 PDF 文件不要使用 gzip。它们本身已经压缩过,再使用 gzip 压缩不仅浪费 CPU 资源,而且还可能增加文件体积。

从HTTP/1.1开始,web客户端就有了支持压缩的Accept-Encoding HTTP请求头。

Accept-Encoding: gzip, deflate

如果web服务器看到这个请求头,它就会用客户端列出的一种方式来压缩响应。web服务器通过Content-Encoding响应头来通知客户端。

Content-Encoding: gzip

(4)配置 Etag

实体标签(ETags),是服务器和浏览器用来决定浏览器缓存中组件与源服务器中的组件是否匹配的一种机制(“实体”也就是组件:图片,脚本,样式表等等)。添加ETags可以提供一种实体验证机制,比最后修改日期更加灵活。一个ETag是一个字符串,作为一个组件某一具体版本的唯一标识符。唯一的格式约束是字符串必须用引号括起来,源服务器用相应头中的ETag来指定组件的ETag。

1
2
3
4
HTTP/1.1 200 OK
Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT
ETag: "10c24bc-4ab-457e1c1f"
Content-Length: 12195

然后,如果浏览器必须验证一个组件,它用If-None-Match请求头来把ETag传回源服务器。如果ETags匹配成功,会返回一个304状态码,这样就减少了12195个字节的响应体。Etag 通过文件版本标识,方便服务器判断请求的内容是否有更新,如果没有就响应 304,避免重新下载。

1
2
3
4
5
GET /i/yahoo.gif HTTP/1.1
Host: us.yimg.com
If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT
If-None-Match: "10c24bc-4ab-457e1c1f"
HTTP/1.1 304 Not Modified

(5)尽早输出(flush)缓冲

用户请求页面时,服务器通常需要花费200 ~ 500毫秒来组合 HTML 页面。在此期间,浏览器处于空闲、等待数据状态。使用PHP中的flush()函数,可以发送部分已经准备好的 HTML到浏览器,以便服务器还在忙于处理剩余页面时,浏览器可以提前开始获取资源。

可以考虑在</head>之后输出一次缓冲,HTML head一般比较容易生成,先发送以便浏览器开始获取<head>里引用的CSS等资源。

Example:

1
2
3
4
5
<!-- css, js -->
</head>
<?php flush(); ?>
<body>
<!-- content -->

(6)Ajax请求使用GET方法

浏览器执行XMLHttpRequest POST请求时分成两步,先发送Http Header,再发送data。而GET只使用一个TCP数据包(Http Header与data)发送数据,所以首选GET方法。

根据HTTP规范,GET用于获取数据,POST则用于向服务器发送数据,所以Ajax请求数据时使用GET更符合规范。

(7)避免图片src为空

图片src属性值为空字符串可能以下面两种形式出现:

HTML:

1
<img src="" />

JavaScript:

1
2
var img = new Image(); 
img.src = "";

虽然src属性为空字符串,但浏览器仍然会向服务器发起一个HTTP请求:

  • IE 向页面所在的目录发送请求;
  • Safari、Chrome、Firefox向页面本身发送请求;
  • Opera不执行任何操作。

空src产生请求的后果不容小觑:

  • 给服务器造成意外的流量负担,尤其时日 PV 较大时;
  • 浪费服务器计算资源;
  • 可能产生报错。

空的href属性也存在类似问题。用户点击空链接时,浏览器也会向服务器发送HTTP请求,可以通过JavaScript阻止空链接的默认的行为。

三、Cookie

(1)减少 Cookie 大小

Cookie被用于身份认证、个性化设置等诸多用途。Cookie通过HTTP头在服务器和浏览器间来回传送,减少Cookie大小可以降低其对响应速度的影响。

  • 去除不必要的 Cookie;
  • 尽量压缩 Cookie 大小;
  • 注意设置 Cookie 的 domain 级别,如无必要,不要影响到 sub-domain;
  • 设置合适的过期时间。

(2)静态资源使用无Cookie域名

静态资源一般无需使用Cookie,可以把它们放在使用二级域名或者专门域名的无Cookie服务器上,降低Cookie传送的造成的流量浪费,提高响应速度。

四、CSS

(1)把样式表放在<head>中

把样式表放在<head>中可以让页面渐进渲染,尽早呈现视觉反馈,给用户加载速度很快的感觉。

这对内容比较多的页面尤为重要,用户可以先查看已经下载渲染的内容,而不是盯着白屏等待。

如果把样式表放在页面底部,一些浏览器为减少重绘,会在 CSS 加载完成以后才渲染页面,用户只能对着白屏干瞪眼,用户体验极差。把样式表放到文档的HEAD部分能让页面看起来加载地更快。

(2)不要使用CSS表达式

CSS表达式可以在CSS里执行JavaScript,仅IE5-IE7支持,IE8标准模式已经废弃。
CSS表达式超出预期的频繁执行,页面滚动、鼠标移动时都会不断执行,带来很大的性能损耗。

(3)使用<link>替代@import

对于IE某些版本,@import的行为和放在页面底部一样。所以,不要用它。

(4)不要使用 filter

AlphaImageLoader为IE5.5-IE8专有的技术,和CSS表达式一样,放进博物馆吧。IE专有的AlphaImageLoader滤镜可以用来修复IE7之前的版本中半透明PNG图片的问题。在图片加载过程中,这个滤镜会阻塞渲染,卡住浏览器,还会增加内存消耗而且是被应用到每个元素的,而不是每个图片,所以会存在一大堆问题。

注意!!!这里所说的不是 CSS3 Filter

五、Javasript

(1)把脚本放在页面底部

浏览器下载脚本时,会阻塞其他资源并行下载,即使是来自不同域名的资源。因此,最好将脚本放在底部,以提高页面加载速度。

一些特殊场景无法将脚本放到页面底部的,可以考虑<script>的以下属性:

  • defer 属性;
  • HTML5 新增的async属性。

(2)使用外部JavaScript和CSS

外部JavaScript和CSS文件可以被浏览器缓存,在不同页面间重用,也能降低页面大小。

当然,实际中也需要考虑代码的重用程度。如果仅仅是某个页面使用到的代码,可以考虑内嵌在页面中,减少HTTP请求数。另外,可以在首页加载完成以后,预先加载子页面的资源。

(3)压缩JavaScript和CSS

压缩代码可以移除非功能性的字符(注释、空格、空行等),减少文件大小,提高载入速度。

得益于Node.js的流行,开源社区涌现出许多高效、易用的前端优化工具,JavaScript 和CSS压缩类的,不敢说多如牛毛,多入鸡毛倒是一点不夸张,如[UglifyJS 2] (https://github.com/mishoo/UglifyJS2)、csso、cssnano 等。

对于内嵌的CSS和JavaScript,也可以通过htmlmin等工具压缩。

这些项目都有Gulp、Webpack等流行构建工具的配套版本。

(4)移除重复脚本

重复的脚本不仅产生不必要的HTTP请求,而且重复解析执行浪费时间和计算资源。

(5)减少DOM操作

JavaScript 操作 DOM 很慢,尤其是 DOM 节点很多时。

使用时应该注意:

  • 缓存已经访问过的元素;
  • 使用DocumentFragment暂存DOM,整理好以后再插入DOM树;
  • 操作className,而不是多次读写style;
  • 避免使用JavaScript修复布局。

(6)使用高效的事件处理

  • 减少绑定事件监听的节点,如通过事件委托;
  • 尽早处理事件,在DOMContentLoaded即可进行,不用等到load以后。

六、图片

(1)优化图片

尝试把GIF格式转换成PNG格式,看看是否节省空间。在所有的PNG图片上运行pngcrush(或者其它PNG优化工具)。

YDN列出的相关工具缺乏易用性,建议参考以下工具

  • imagemin
  • imageoptim.com

TODO:

  • PNG 终极优化;
  • Webp 相关内容;
  • SVG 相关内容。

PNG终极优化

  • Most Effective Method to Reduce and Optimize PNG Images
  • Clever PNG Optimization Techniques

(2)优化CSS Sprite

  • 水平排列Sprite中的图片,垂直排列会增加图片大小;
  • Spirite中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以适用PNG8格式;
  • 不要在Spirite的图像中间留有较大空隙。减少空隙虽然不太影响文件大小,但可以降低用户代理把图片解压为像素图的内存消耗,对移动设备更友好。

(3)不要在HTML中缩放图片

不要使用<img>的width、height缩放图片,如果用到小图片,就使用相应大小的图片。如果需要

<img width="100" height="100" src="mycat.jpg" alt="My Cat" />

那么图片本身(mycat.jpg)应该是100x100px的,而不是去缩小500x500px的图片。

很多 CMS 和 CDN 都提供图片裁切功能。

补充:设置图片的宽和高,以免浏览器按照「猜」的宽高给图片保留的区域和实际宽高差异,产生重绘。

(4)使用体积小、可缓存的favicon.ico

Favicon.ico一般存放在网站根目录下,无论是否在页面中设置,浏览器都会尝试请求这个文件。

所以确保这个图标:

  • 存在(避免 404);
  • 尽量小,最好小于 1K;
  • 设置较长的过期时间。

对于较新的浏览器,可以使用PNG格式的favicon。

七、移动端

(1)保证所有组件都小于25K

这个限制是因为iPhone不能缓存大于25K的组件,注意这里指的是未压缩的大小。这就是为什么缩减内容本身也很重要,因为单纯的gzip可能不够。

保证所有组件都小于25K

(2)打包内容为分段(multipart)文档

把各个组件打包成一个像有附件的电子邮件一样的复合文档里,可以用一个HTTP请求获取多个组件(记住一点:HTTP请求是代价高昂的)。用这种方式的时候,要先检查用户代理是否支持(iPhone就不支持)。

总结

写到这里,雅虎的35条军规算是介绍完了。条目虽然很多,但经过分类,可以发现,性能优化主要切入点可以从以下几个方面去考虑:

  • 资源本身大小的压缩优化(想办法减少资源的体积)
  • 网络请求的全过程(从url地址栏输入发送请求开始到返回响应包的每个环节)
  • 浏览器渲染的全过程(拿到资源后浏览器渲染的每个环节)

因此,要彻底掌握优化的方法,必须对http请求的全过程以及浏览器的渲染全过程都有深入的理解。

前端性能优化

常用的CSS小技巧(二)

发表于 2018-08-10 | 更新于 2018-08-11 | 分类于 CSS

tranform知多少

transform是变换,包括旋转rotate、扭曲skew、缩放scale和移动translate,可以改变对象的外形和位置。transform的转换,并不是动态的,而是最终时刻的状态。

transform: rotate | scale | skew | translate | matrix

CSS3中的坐标系如图所示。后续介绍会用上。
XYZ坐标轴

(1)rotate

可进行2D或者3D旋转。其中常用的rotate(angle)定义2D旋转,angle定义旋转的角度,正数表示顺时针旋转,负数表示逆时针旋转。

  • 定义
值 描述
rotate(angle) 定义 2D 旋转,在参数中规定角度(正数顺时针、负数逆时针)
rotate3d(x,y,z,angle) 定义 3D 旋转
rotateX(angle) 定义沿着 X 轴的 3D 旋转
rotateY(angle) 定义沿着 Y 轴的 3D 旋转
rotateZ(angle) 定义沿着 Z 轴的 3D 旋转
  • 使用方法
1
2
3
4
5
6
div
{
transform: rotate(30deg);
-ms-transform: rotate(30deg); /* IE 9 */
-webkit-transform: rotate(30deg); /* Safari and Chrome */
}
  • 效果

rotate

(2) scale

scale(x,y)方法让元素可以在x,y轴方向上进行拉伸或缩放。伸缩程度由x,y参数决定。scale的参数如果只有一个,则默认为等比例变化。(例如scale(1.5)代表放大1.5倍)。

  • 定义
值 描述
scale(x[,y]?) 定义 2D 缩放转换
scale3d(x,y,z) 定义 3D 缩放转换
scaleX(x) 通过设置 X 轴的值来定义缩放转换
scaleY(y) 通过设置 Y 轴的值来定义缩放转换
scaleZ(z) 通过设置 Z 轴的值来定义 3D 缩放转换
  • 使用方法
1
2
3
4
5
6
div
{
transform: scale(2,3); /* 标准语法 */
-ms-transform:scale(2,3); /* IE 9 */
-webkit-transform: scale(2,3); /* Safari */
}
  • 效果

scale

(3) translate

translate(x,y)方法,让元素相对当前位置在x、y方向上偏移给定参数的距离。它是translateX()和translateY()的结合。

  • 定义
值 描述
translate(x,y) 定义 2D 转换
translate3d(x,y,z) 定义 3D 转换
translateX(x) 定义转换,只是用 X 轴的值
translateY(y) 定义转换,只是用 Y 轴的值
translateZ(z) 定义 3D 转换,只是用 Z 轴的值
  • 使用方法
1
2
3
4
5
6
div
{
transform: translate(50px,100px);
-ms-transform: translate(50px,100px); /* IE 9 */
-webkit-transform: translate(50px,100px); /* Safari and Chrome */
}
  • 效果

scale

Q:tranlateZ(0)为什么可以提高浏览器渲染的性能?

      在这里要特别提出tranlateZ(0),可以利用GPU加速,提高浏览器渲染的性能。为什么translateZ(0)可以提升渲染的性能呢??这得从浏览器的渲染原理说起。

浏览器渲染原理
在从服务器拿到数据后,浏览器会先解析三类东西:

  • 解析html,xhtml,svg这三类文档,形成dom树。
  • 解析css,产生css rule tree。
  • 解析js,js会通过api来操作dom tree和css rule tree。

      解析完成之后,浏览器引擎会通过dom tree和css rule tree来构建rendering tree:

  • rendering tree和dom tree并不完全相同,例如:或display:none的东西就不会放在渲染树中
  • css rule tree主要是完成匹配,并把css rule附加给rendering tree的每个element

      在渲染树构建完成后,

  • 浏览器会对这些元素进行定位和布局,这一步也叫做reflow(回流)或者layout
  • 浏览器绘制这些元素的样式,颜色,背景,大小及边框等,这一步也叫做repaint(重绘)
  • 然后浏览器会将各层的信息发送给GPU,GPU会将各层合成;显示在屏幕上

即渲染树构建完成后,浏览器要做的步骤是:
reflow —> repaint —> composite

reflow和repaint都是耗费浏览器性能的操作,为了仅发生composite,我们做动画的css property必须满足以下三个条件:

  1. 不影响文档流。
  2. 不依赖文档流。
  3. 不会造成重绘。

满足以上以上条件的css property只有transform和opacity。

这样做有两个优势:

  1. 动画将会非常流畅
  2. 动画不在绑定到CPU,即使js执行大量的工作;动画依然流畅。

GPU有2个问题:

  1. 一个或多个没有自己复合层的元素要出现在有复合层元素的上方,它就会拥有自己的复合层;这种情况被称为隐式合成。
  2. 使用GPU动画需要发送多张渲染层的图像给GPU,GPU也需要缓存它们以便于后续动画的使用。

所以——

  • 保持动画的对象的z-index尽可能的高。理想的,这些元素应该是body元素的直接子元素。当然,这不是总可能的。所以你可以克隆一个元素,把它放在body元素下仅仅是为了做动画。

  • 将元素上设置will-change CSS属性,元素上有了这个属性,浏览器会提升这个元素成为一个复合层(不是总是)。这样动画就可以平滑的开始和结束。但是不要滥用这个属性,否则会大大增加内存消耗。

tranlateZ(0)为什么可以提高浏览器渲染的性能?
这个问题是因为使用transform和opacity做CSS动画的时候,会将元素提升为一个复合层;而使用js操作css属性做动画时,必须使用translateZ或will-change才能将元素强行提升至一个复合层。

元素本身使用transform和opacity做CSS动画的时候,会提前告诉GPU动画如何开始和结束及所需要的指令;所以会创建一个复合层(渲染层),并把页面所有的复合层发送给GPU;作为图像缓存,然后动画的发生仅仅是复合层间相对移动。当元素使用css动画时,在控制台中的layers可以看到,会有两个layer。

而使用js做动画,js必须在动画的每一帧计算元素的状态;发送给GPU,但不会将元素提升至一个复合层;所以想让元素提升至一个复合层,必须使用translateZ或will-change: transform, opacity。当我们使用js动画时,在控制台,可以看到只有一个layer,添加translateZ(0)的时候,可以看到又有两个layer了。

简而言之,是因为transform不会触发repaint,而是会创建composited layer,
GPU会来执行transform的操作,将浏览器的渲染过程交给GPU处理,而不是使用自带的比较慢的渲染器。
这样就可以使得动画更加顺畅。

常用的CSS小技巧(一)

发表于 2018-07-25 | 分类于 CSS

实际开发过程中会遇到一些需要用CSS小技巧处理的布局问题,现在分享几个个人工作中遇到的小问题和解决方案。

1.inline元素间的空白间隙

这里要介绍一个神器font-size:0。
如果你写了个列表,因为元素节点有文本节点,在缩进代码时会占据宽度,比如:

1
2
3
4
5
6
<ul>
<li>我是第一项</li>
<li>我是第二项</li>
<li>我是第三项</li>
<li>我是第四项</li>
</ul>

设置了CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
<style>
ul {
list-style: none;
}
li {
width: 25%;
display: inline-block;
background: green;
text-align: center;
height: 40px;
line-height: 40px;
}
</style>

就会发现
有空白间隙
我们为了页面代码的整洁可读性,往往会设置一些适当的缩进、换行,但当元素的display为inline或者inline-block的时候,这些缩进、换行就会产生空白,所以出现上述问题。虽然还有其他方法能解决我们因为缩进、换行而产生的问题,但此时,最合适的方法就是给li的父级ul设置font-size: 0, 给li设置font-size: 16px,如此就达到了所需效果
空白间隙消失啦~

图片间的间隙问题也是因为换行、缩进。

1
2
3
4
<div>
<img src="pic1.jpg">
<img src="pic2.jpg">
</div>

如上,图片出现间隙后,在div设置font-size:0,间隙就会消失。

2.图片等比缩放

在做项目时遇到轮播图的图片从某服务器获取的情况,但图片大小比例却是不定的,这种情况该如何保证图片按比例显示?或者在移动端场景下,如何做图片的响应性布局?

在CSS3中,可使用background-size来解决针对响应性布局的背景图片自适应。

  • 浏览器支持的程度:IE9+, Firefox4+, opera, chrome, safari5+
  • 基本语法:background-size: length | percentage | cover | contain
  1. length该属性值是设置背景图像的宽度和高度的,第一个值是宽度,第二个值是设置高度的。如果只设置第一个值,那么第二个值会自动转换为 “auto”;调整图片到指定大小;
  2. percentage该属性是以父元素的百分比来设置图片的宽度和高度的,第一个值是宽度,第二个值是高度。如果只设置一个值,那么第二个值会被设置为 “auto”;调整图片到指定大小,百分比相对于包含元素的尺寸。
  3. cover把背景图像扩展至足够大,以使背景图像完全覆盖背景区域。扩展图片来填满元素(保持像素的长宽比);
  4. contain把图像扩展至最大尺寸,以使宽度和高度完全适应内容区域。 缩小图片来适应元素的尺寸(保持像素的长宽比)

那究竟是怎么使用的呢?举个🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// html部分
<div class="bsize1"></div>
// CSS部分
.bsize1 {
position: relative;
width: 28%;
height: 0;
padding-bottom: 17.5%;
overflow: hidden;
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
border: 1px solid #e2e5e7 !important;
}

3.文字折行

单行文本溢出

1
2
3
overflow: hidden;
text-overflow:ellipsis;
white-space: nowrap;

多行文本溢出

1
2
3
4
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;

-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。常见结合属性:
display: -webkit-box;必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 。
-webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 。

4.calc()

整体高度自适应布局怎么实现?在讲calc()之前先说一下vh和vw:

1
2
3
4
vw   相对于视口的宽度。视口被均分为100单位的vw
vh 相对于视口的高度。视口被均分为100单位的vh
vmax 相对于视口的宽度或高度中较大的那个。其中最大的那个被均分为100单位的vmax
vmin 相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin
  • calc 是 css3提供的一个在css文件中计算值的函数:用于动态计算长度值。
  • 需要注意的是,运算符前后都需要保留一个空格,例如:width: calc(100% - 10px);
  • 任何长度值都可以使用calc()函数进行计算;
  • calc()函数支持 “+”, “-“, “*”, “/“ 运算;
  • calc()函数使用标准的数学运算优先级规则;

注意!

如果用了CSS预处理器(LESS/SCSS)会被它们先解析,这时需要禁用解析calc(~ ‘100vh - 64px’)

脚手架架构设计——React&Mobx从0到1最佳实践

发表于 2018-07-01 | 更新于 2018-07-19 | 分类于 React全家桶

一、前言

React现在已经有很多脚手架工具,如create-react-app,支持一键创建一个React应用项目结构,很方便,但是享受方便的同时,也失去了对项目架构及技术栈完整学习的机会,而且通常脚手架创建的应用技术架构并不能完全满足我们的业务需求,需要我们自己修改,完善,所以如果希望对项目架构有更深掌控,最好还是从0到1理解一个项目。

二、项目结构与技术栈

文件目录

这次应用架构设计不使用任何脚手架,需要自己创建每一个文件,引入每一个技术和三方库,最终形成完整的应用,包括选择的完整技术栈。
项目结构图如下:1. src为应用源代码目录; 2. webpack.config.js为webpack配置入口文件; 3. package.json为项目依赖管理文件; 4. .babelrc文件,babel的配置文件,使用babel编译React和JavaScript代码; 5.README.md为项目说明文档; 6.bird-config是gulp-bird转发工具的配置文件;7.postcss.config.js是postcss的配置文件

Feature

  • 可以解析JSX语法
  • 可以解析ES6语法新特性
  • 支持SCSS预处理器
  • 单独分离CSS样式文件
  • 支持文件MD5戳,解决文件缓存问题
  • 支持图片、图标字体等资源的编译
  • 区分开发环境和生产环境
  • 分离业务功能代码和公共依赖代码

文件目录

技术栈

对整个技术栈进行分析,要考虑哪几个方面呢?

  1. react和react-dom库是项目前提;
  2. react路由;
  3. 应用状态管理容器;
  4. 如何优雅地发送ajax请求;
  5. 打包上线发版,测试环境线上环境
  6. css预编译及后处理,考虑兼容性写法;
  7. 采用ES6写码,辅助工具,写代码更容易;
  8. 此外,往往后端开发好的接口会部署在测试环境,而不是本地,因此还需要考虑联调时的代理转发。

根据以上划分决定选用以下第三方库和工具构成项目的完整技术栈:

  1. react,react-dom;
  2. react-router管理应用路由;
  3. mobx作为JavaScript状态容器,mobx-react将React应用与mobx连接,Mobx相关详情请参见Mobx官方文档;
  4. axios库发送ajax请求,解决接口调用问题,请参见Axios
  5. webpack配置;
  6. 安装postcss的autoprefixer;
  7. 需要安装babel,还可以使用lodash等可选辅助类,工具类库提升开发效率;
  8. 代理转发工具选择的是百度BEFE团队开发的gulp-bird。

组件划分

React组件化开发原则是组件负责渲染UI,组件不同状态对应不同UI,通常遵循以下组件设计思路:

  1. 路由组件:负责项目路由,可嵌套;
  2. 布局组件:仅仅涉及应用UI界面结构的组件,不涉及任何业务逻辑,数据请求及操作;
  3. 容器组件:负责获取数据,处理业务逻辑,通常在render()函数内返回展示型组件;
  4. UI组件:指抽象出的可重用的UI独立组件,通常是无状态组件;

以本项目section的组件划分为例,index.js是入口文件,同时也承担了路由组件的角色,layout是布局组件,仅仅负责UI界面结构,homeindex是容器组件,是首页的业务逻辑,islider则是轮播图的独立组件,可复用。
组件划分

三、项目搭建步骤

3.1准备工作

  • git新建项目
1
2
3
4
5
6
git init
touch README
git add README
git commit -m'first commit'
git remote add origin git@XXXXX.git
git push origin master
  • npm安装react相关依赖包

采用npm install XXX -save或npm install XXX -save-dev配置安装相关npm包,如果直接复制,可以直接npm install安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{
"name": "zhiqiu",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=production",
"dev": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=development",
"qa": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=qa",
"start": "./node_modules/.bin/webpack-dev-server --config webpack.config.js --env.NODE_ENV=local"
},
"proxy": {
"/mansion": {
"target": "http://localhost:8009"
}
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"axios": "^0.18.0",
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"classnames": "^2.2.5",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.1",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "^1.1.11",
"html-webpack-inline-source-plugin": "0.0.10",
"html-webpack-plugin": "^3.2.0",
"islider": "^0.1.0",
"mobx": "^4.1.0",
"mobx-react": "^5.0.0",
"node-sass": "^4.8.3",
"path": "^0.12.7",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2",
"sass-loader": "^6.0.7",
"style-loader": "^0.20.3",
"url-loader": "^1.0.1",
"webpack": "^3.10.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-server": "^2.11.2"
},
"dependencies": {
"autoprefixer": "^8.6.4",
"babel-plugin-lodash": "^3.3.4",
"gulp-bird": "^0.2.4",
"hi-ui": "0.0.9",
"islider.js": "^2.2.2",
"jquery": "^3.3.1",
"lodash": "^4.17.10",
"postcss-loader": "^2.1.5",
"vconsole": "^3.2.0"
}
}

3.2 路由和数据状态管理

路由安装mobx-react,react-router模块;
React Router是完整的React路由解决方案,也是开发React应用最常使用的路由管理库,它提供简单的API,以声明式方式实现强大的路由功能,诸如按需加载,动态路由等。
1.声明式:语法简洁,清晰;
2.按需加载:延迟加载,根据使用需要判断是否需要加载;
3.动态路由:动态组合应用路由结构,更灵活,更符合组件化开发模式;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @file index 入口页面
* @author guoyueting
*/
import React from 'react';
import {render} from 'react-dom';
import {HashRouter, Route, Redirect} from 'react-router-dom';
import {Provider} from 'mobx-react';
import storeTree from './storeTree';
import 'section/common/scss/common.scss';
import Layout from 'section/layout/layout.js';

class App extends React.Component {
render() {
return (
<Provider {...storeTree} >
<HashRouter>
<Route path="/" component={Layout}/>
</HashRouter>
</Provider>
);
}
}

render(<App/>, document.getElementById('app'));

数据状态管理,安装mobx,确定storetree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* @file storeTree 整个app的状态树
* @author guoyueting
*/
'use strict';

import {observable, action, extendObservable, runInAction} from 'mobx';
import NavState from 'section/nav/navStore';
import iSliderState from 'section/homeindex/component/islider/isliderStore';
import hotPointState from 'section/homeindex/component/hotpoint/hotpointStore';
import recommendState from 'section/homeindex/component/recommend/recommendStore';

import serviceState from 'section/service/serviceStore';
import repairState from 'section/myrepair/myrepairStore';


class AppState {
// 全局store
// ...
}

export default {
AppState: new AppState(),
NavState: new NavState(),
iSliderState: new iSliderState(),
hotPointState: new hotPointState(),
recommendState: new recommendState(),
serviceState: new serviceState(),
repairState: new repairState()
}

@observer 函数/修饰器用于react组件。通过mobx-react依赖包来提供。它通过mobx.autorun来包装了组件的render函数,以确保组件的render函数在任何数据的更改是强制重新渲染。

Autorun是用在一些你想要产生一个不用观察者参与的被动调用函数里面。当autorun被使用的时候,一旦依赖项发生变化,autorun提供的函数就会被执行。

action是任何改变状态的事物。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* @file serviceStore
* @author guoyueting
*/
'use strict';

import {observable, action, runInAction} from 'mobx';
import _ from 'lodash';
import * as model from 'src/itsm/model/model';
import {getSearchParam} from 'section/common/js/utils';

export default class serviceState {
@observable serviceList = [];

@action getServiceList() {
let params = {
mappedAppKey: getSearchParam('appKey')
};
model.getServiceListList(params).then(data => {
runInAction(()=>{
this.serviceList = _.get(data, 'data.data');
});
});
}

@action serviceClick(id) {
let params = {
mappedAppKey: getSearchParam('appKey'),
id: id
};
model.serviceClick(params);
}
}

3.3 Axios库发送ajax请求

安装axios,并通过get/post/put/delete等方式请求接口,对于json数据,一般在post请求进行数据格式转换,并在请求头部设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @file 接口文件
* @author guoyueting
*/
import axios from 'axios';
let axiosConfig = {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Access-Control-Allow-Origin': '*'
}
};
// 获取列表
export let getHotList = function (data) {
return axios.post('/rdwtv2/api/hot', JSON.stringify(data), axiosConfig);
};

3.4 代理转发gulp-bird

gulp-bird是由百度BEFE团队开发的代理转发工具,并不是一个gulp插件,在bird基础上进行了一些优化并发布到了npm,配置方法和bird一样。
配置目标服务器host和port等,参考如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* @file bird-config.js
* @author guoyueting
*/
var bird = require('./node_modules/gulp-bird/index');
// 静态服务器配置,可同时配置多个,域名需host到127.0.0.1
var server = {
'8009': {
// 静态文件根目录
'basePath': './src/',
// 是否开启调试模式,true(表示server端不缓存),false(反之)
'debug': true
// 忽略的静态文件请求,与此正则匹配的请求将直接走转发规则(可选配置)
// 'ignoreRegExp': /\/js\/urls\.js/g

}
};
// 转发规则——静态服务器没有响应的或者忽略的请求将根据一下规则转发
var transpondRules = {
'8009': {
// 目标服务器的ip和端口,域名也可,但注意不要被host了
targetServer: {
'port': '8680',
// 'port': '8080',
'host': 'http://m1-ite-hidev04.m1.baidu.com',
// 'host': 'cp01-ps-dev373-liuchao31.epc.baidu.com',
// 当为true时,如果cookie or header中有相同key,则替换
'replaceHeaders': true,
'headers': {
'cookie': ''}
}
// 特殊请求转发,可选配置,内部的host、port和attachHeaders为可选参数
regExpPath: {
// '/oa-frontend-apply-1.0.0-SNAPSHOT': {
// 'host': 'cp01-dev-heliping.epc.baidu.com/',
// 'port': '8080',
// //'attachHeaders': {'app-id': 5},
// 'path': '\/'
// }npm
}
},
'ajaxOnly': false
};

var toolsConf = {
weinre: {
// 和移动调试工具条中的vconsole冲突, 当为true时vconsole自动关闭
open: false,
port: 8009
},
// 移动端调试工具条,PC端开发可关闭
showTools: false
};

bird.start(server, transpondRules, toolsConf);

3.5webpack配置打包

下面是一个较为基础的webpack文件配置。entry是配置模块的入口,webpack执行构建的第一步将从入口开始搜寻及递归解析出所有入口依赖的模块;output定义了打包后的输出文件名、路径;module配置模块的读取和解析规则,通常用来配置loader;resolve配置Webpack如何寻找模块对应的文件;Plugins用于扩展webpack的功能,几乎所有Webpack无法直接实现的功能都能在社区找到开源的Plugin去解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const path = require('path');
const webpack = require('webpack');

module.exports = function(env) {
console.log(env);
return {
entry: {
'index': './src/itsm/index.js'
},
output: {
filename: 'js/[name]-[chunkhash].js',
path: path.resolve(__dirname, './build'),
chunkFilename: '[name].[chunkhash:4].child.js'
},
module: {
rules: [{
test: /(\.jsx|\.js)$/i,
use: [{
loader: 'babel-loader'
}]
// exclude: /node_modules/
},
{
test: /(\.scss|\.sass)$/i,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [{
loader: 'css-loader',
options: {
module: false,
minimize: true
}
}, {
loader: 'postcss-loader',
}, {
loader: 'sass-loader',
options: {
sourceMap: false
}
}]
})
},
{
test: /(\.png|\.jpg|\.jpeg|\.gif)$/i,
use: [{
loader: 'url-loader',
options: {
limit: 100
}
}]
}]
},
resolve: {
alias: {
'src': path.resolve(__dirname, 'src'),
'section': path.resolve(__dirname, 'src/itsm/section'),
'img': path.resolve(__dirname, 'src/itsm/img'),
'modules': path.resolve(__dirname, 'node_modules/islider.js/build')
}
},
plugins: [
new CleanWebpackPlugin(['./build']),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(env.NODE_ENV || 'development'),
'isDev': JSON.stringify(env.isDev || 'true')
}),
new ExtractTextPlugin('style.css'),
new HtmlWebpackPlugin({
template: './src/itsm/index.html',
filename: './index.html',
chunks: ['index']
})
],
devServer: {
host: '0.0.0.0',
port: 9822,
proxy: {
'/rdwtv2': 'http://localhost:8009'
}
}
}
}

3.6打包指令配置

配置不同环境的变量值,生产环境,QA环境,开发环境某些变量可能需要根据环境进行配置,然后用webpack的definePlugin插件,定义全局变量,可以保证在和环境有关的变量取值的正确性。

1
2
3
4
5
6
"scripts": {
"build": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=production",
"dev": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=development",
"qa": "./node_modules/.bin/webpack --config webpack.config.js --env.NODE_ENV=qa",
"start": "./node_modules/.bin/webpack-dev-server --config webpack.config.js --env.NODE_ENV=local"
}

四、未来改进

未来架构设计还需要完善的点:

  1. Immutable数据,考虑引入Immutable.js;
  2. 测试,考虑jest集成应用测试
  3. 函数库,如ramda,underscore;
  4. 调试工具,reactotron,react-devtools

五、Demo地址

最后,附上demo地址:https://github.com/guoyueting/react-mobx-seed

Hexo安装与发布

发表于 2018-06-23 | 更新于 2018-07-01 | 分类于 工具&配置

安装

  1. 安装hexo

    npm install hexo-cli -g

  2. 初始化hexo

    hexo init <文件名>

  3. 打开文件

    cd <文件名>

  4. 安装node_modules

    npm install

  5. 启动hexo服务,启动后可通过http://localhost:4000/就可以看到博客啦~

    hexo server

发布

  1. github新建一个项目(start a project),新建分支,如hexo,打开settings,将repository name命名为XXX.github.io,该仓库可以被发布到https://XXX.github.io

  2. 打开站点的配置文件_config.yml,修改为:

    1
    2
    3
    type: git
    repo: https://github.com/guoyueting/guoyueting.github.io
    branch: master

    deploy的配置是让hexo知道你要把blog部署在哪个位置,因此hexo分支可以保留当前源代码,master分支则用来发布的html等文件。

  3. 为了使用hexo d来部署到git上,需要先安装Git部署插件。

    npm install hexo-deployer-git --save

  1. 清除旧public文件

    hexo clean

  2. 生成并发布html,本地生成不用加-d

    hexo g -d

国悦婷

6 日志
4 分类
© 2018 国悦婷
由 Hexo 强力驱动 v3.7.1
|
主题 — NexT.Muse v6.3.0