Coder Social home page Coder Social logo

bearblog's People

Contributors

winniebear avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

bearblog's Issues

web性能优化—内容请求优化

页面请求过程优化主要是减少获取页面资源的时间,主要通过几个方面:

  • 减少(或避免)请求数
  • 减少请求和响应的数据量
  • 缩短传输的路径和延迟
  • 增加传输带宽

当浏览器请求资源时,需要通过发送网络请求来获取资源,网络请求是比较耗费时间的,如果浏览器减少网络请求的次数,甚至不发送网络请求就直接拿到需要的资源,就可以大大减少资源加载的时间,浏览器的本地缓存机制可以满足这一点。

Local cache

浏览器发送请求时,会根据url查找本地缓存,看是否本地缓存中存在该url的内容,如果不存在,则向服务器请求该url的内容;如果本地存在缓存,则按照如下策略:
浏览器本地缓存机制

优化点:

  • 给静态资源设置适当的缓存时间 expire:xxx /cache-control:max-age=xxx,对更新不频繁的资源尽量利用本地缓存来减少网络请求的时间;
  • 尽量利用条件查询请求,对资源返回的http头设置Last-Modified/Etag,当内容过期时,进行条件查询请求,尽量利用本地缓存的资源;

关于本地缓存机制,进一步阅读

浏览器缓存机制

服务器与浏览器缓存协商控制机制的总结

充分利用本地存储LocalStorage

优化点

  • 高级浏览器一般都支持本地存储,可以把需要网络请求的资源存放再浏览器的LocalStorage,需要资源时从本地存储获取,首次加载时通过服务端内联的方式返回,减少资源的网络请求;

静态资源多域名部署

在PC端或者强网络环境下,因为浏览器在同一个域名下的并发请求数有限制(4-6个url),为了能够加快静态资源的请求,可以

  • 把静态资源分发到多个域名,让浏览器同时建立多个连接,并发进行多个静态资源的请求

注意:这种方法只适用于强网络环境,譬如移动端弱网络环境下,建立链接的耗费很多时间时,此方法不适用

DNS query

浏览器向服务器请求资源,是通过http协议进行通讯的,http协议是应用层协议,底层是TCP协议。因此浏览器发送请求时,需要和服务器先建立TCP连接,而建立TCP连接需要知道服务器的IP和PORT。而请求的url一般都是通过域名而不是IP提供给用户,因此浏览器和服务器建立连接之前,首先是获取服务器的IP。浏览器通过检查是否本地DNS缓存中是否有服务器host的ip,如果没有通过DNS查询来获取服务器的ip。

无线网络的现状:

  • 2G:一般只有10KB/S下行和1KB/S左右的上行速度,延迟基本 >= 400ms;
  • 3G网络:一般为下行100-200KB/S,上行10-100KB/S,延迟0-400ms,带宽方面基本逼近2M有线网络,但延迟较高,稳定性不够

DNS查询耗时:

  • 在相对较快速的有线网络环境下,平均值为几十毫秒左右;
  • 2G网络环境下,平均值在几百毫秒左右;
  • 在网络很差的移动环境下,平均值达到几秒左右

优化点:

<link rel="dns-prefetch" href="xxx.com" >
  • 在弱网络环境下减少页面中的资源的域名的个数,或者url中直接ip而不是域名

Connect To Server 和服务器建立连接

浏览器根据DNS查询得到ip+url中的端口跟服务器建立TCP连接。
因为用户和服务器之间的距离(不仅是物理距离,还有网络链路距离,以及跨运营商中转等)不同,建立连接的时间不同。

优化点:

  • 采用CDN,让用户与它最近的服务器建立连接

TCP 3 handshake TCP建立连接3次握手

TCP建立连接3次握手
TCP建立连接3次握手

因为HTTP协议是无连接的,每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。每一个请求都要建立TCP连接,都要进行3次握手,建连成本大;

优化点:

  • 减少建立连接的次数:merge CSS/js/image,合并资源,减少请求的资源的个数
  • 减少建立连接的次数:remove empty src/href/URL
  • 减少建立连接的次数:keep-alive, 若connection 模式为close,则服务器处理完一个请求之后会主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则当服务器处理完一个请求之后,该连接会保持一段时间,在该时间内可以继续接收请求;

Send HTTP Request 发送http请求

当浏览器发送http请求头时,会把相关一些信息发送,这些信息包括:UA,Localsetting,Referer,Cookie等,为了加快发送请求的效率,减少发送请求的时间,需要减少发送给服务器的数据量。

优化点:

  • 能用get,不用post请求url,post至少发送两个包
  • 减少发送的http header的大小:避免发送不需要的cookie,静态资源放到独立域名的服务器上
  • 减少发送的http header的大小:缩短url的长度,不同浏览器和服务器对url的长度也有限制
GET / HTTP/1.1
Host:www.taobao.com
User-Agent:Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420Firefox/3.6.17
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Encoding:gzip,deflate
Accept-Language:en-us,en;q=0.5
Accept-Charset:ISO-8859-1,utf-8;q=0.7,*;q=0.
Keep-Alive:115
Connection: keep-alive
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 12May 2011 11:02:36 GMT
Content-Type:text/html; charset=GB2312
Transfer-Encoding:chunked
Connection:keep-alive

进一步阅读

IE6、IE7、IE8 URL长度支持问题

HTTP中的URL长度限制

服务器的工作

服务器接到请求后,根据请求的url和数据等,经过一系列处理,最后返回一个大字符串就是网页的内容给浏览器。一般服务器处理一个动态请求的工作过程大概分几个部分:

  • 接收请求
  • 解析请求
  • 路由转发
  • 通过缓存、接口、数据库查询等途径获取数据
  • 返回文件/渲染模版
  • 输出内容

浏览器是边下载边解析,因此优化的方法是尽量减少浏览器的首包等待时间TTFB (TIME TO FIRST BYTE) ,

优化点:

  • 服务端优化,通过各级缓存(页面缓存/数据缓存/数据库缓存/模板缓存/)及服务器多IDC部署等方法,减少服务端的数据处理时间;
  • 采用bigPipe方案,flush buffer early 不必等所有数据都拿到再拼页面,有一点就输出,早点刷新缓冲区,减少浏览器的首包时间TTFB (TIME TO FIRST BYTE) ,让
    浏览器尽早的进行解析和渲染。

传统页面展现

传统页面展现

bigpipe页面展现

bigpipe页面展现

HTTP Response Header

再服务器端配置Last-Modified 和Etag,和设置缓存时间,让浏览器端尽量利用本地缓存;当缓存过期时,如果请求的资源没有修改,通过304响应,返回很少的响应头而不用返回全部的页面内容到浏览器。

  • 304 Not Modified 浏览器还是利用老的缓存数据
  • 200 Ok 新的内容

HTTP Response Body

返回的数据越少,传输的越快

优化点:

  • 按需加载和返回页面需要的资源,优先返回首屏依赖的资源,剩余的资源按需加载
  • 资源懒加载,例如图片,依赖的模块等,滚动加载剩余
  • 异步刷新,不需要返回全部内容,只返回变化的部分,ajax等
  • 返回的内容进行压缩HTML/CSS/js/img compres gzip

首屏优化

优化点:

  • 按需加载和返回首屏需要的资源,服务端一开始只返回首屏依赖的资源,剩余的资源按需加载
  • 首屏依赖的css和js除了类库,进行页面内联
  • 页面的小图标进行base64编码之后,页面内联
  • 首屏最好不要放js资源,js资源都放在首屏的下面进行加载

采用CDN服务

通过CDN服务,把资源部署到更接近用户的物理机房,

  • 减少浏览器建立连接的时间
  • 减少数据传输的链路长度
  • 同时CDN会对数据进行缓存
  • CDN会根据用户所在的运营商,在DNS解析时返回访问最快的机房IP

通过CDN服务,可以加快浏览器的获取数据的时间

采用SPDY/ HTTP/2

针对http1及http1.1存在的问题,产生了SPDY/ HTTP/2,尤其是HTTP/2 大大提高资源的请求和响应的时间。主要体现在:

优化点:

  • HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。
  • HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。而 HTTP/1.x 每次请求,都会携带大量冗余头信息,浪费了很多带宽资源。头压缩能够很好的解决该问题。
  • 多路复用,直白的说就是所有的请求都是通过一个 TCP 连接并发完成。HTTP/1.x 虽然能利用一个连接完成多次请求,但是多个请求之间是有先后顺序的,后面发送的请求必须等待上一个请求返回才能发送响应。这会很容易导致后面的请求被阻塞,而 HTTP/2 做到了真正的并发请求。同时, 流还支持优先级和流量控制。
  • Server Push:服务端能够更快的把资源推送给客户端。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。

Babel 总结

Babel

ECMAScript 的变化

ECMAScript Current Proposals

  • stage0 只是一个美好激进的想法,有 Babel 插件实现了对这些特性的支持,但是不确定是否会被定为标准;
  • stage1 值得被纳入标准的特性;
  • stage2 该特性规范已经被起草,将会被纳入标准里;
  • stage3 该特性规范已经定稿,各大浏览器厂商和 Node.js 社区开始着手实现;
  • stage4 在接下来的一年将会加入到标准里去。

stageX

Babel的作用

  • 想在代码中使用新的js语法提议,例如async/await
  • 添加对新的框架语法的支持,例如jsx
  • 添加对新的语言支持,例如Flow and TypeScript

ES特性分类

分为三种

  • 最新ES 语法:比如,let, const, 箭头函数,import,解构赋值,对象属性展开,class, jsx, flow 等等
  • 最新ES API:,比如,Promise,Aync/await等等
  • 最新ES 实例方法:比如,String.protorype.includes

Babel处理方法

  1. 对于上述第1种新的ES语法,Babel通过plugin的方式进行转换,把新的语法转换成为ES5支持的语法,例如transform-es2015-arrow-functions
  2. 对于上述2,3种特性,Babel通过添加polyfills进行兼容

插件列表

Babel 插件列表

Babel工作原理

Babel 的三个主要处理步骤分别是:

解析(parse),转换(transform),生成(generate)。

Babel工作原理过程

Babel变化

  • Babel7 不再建议使用stage-x presets,而是根据你配置的目标环境的兼容情况来加载相应的plugins
    各 stage 的插件

  • Babel7 配置的语法也发生变化,包的名字和插件名字都变化了

Babel配置的语法

{
    "presets": [//数组
        "presetA", // 字符串
        ["presetA"], // 数组包裹的字符串
        ["presetA", {}]// 数组,第一项是字符串,第二项是个对象,对象里面配置各种属性
    ],
    "plugins": [
        "plugin-A",
        ["plugin-A"],
        ["plugin-A", {}]
  ]
}




  • 执行的顺序
    plugins是先于presets之前执行
  • 配置项的执行顺序
    presets 的执行顺序是倒序,最后一个先执行,最后执行的是第一个
    而plugins的执行顺序是顺序,按照配置的顺序依次执行
// presets执行顺序:倒序,下面的preset的执行属性是c,b,a
{ "presets":
    [
        "a",
        "b",
        "c"
    ]
}

// plugins是顺序执行,依次执行array-includes,transform-class-properties,transform-decorators-legacy
{
    "plugins": [
        "array-includes",
        "transform-class-properties",
        "transform-decorators-legacy"
    ]

}

Babel的使用方法

  1. 自己根据代码用到的语法,自己添加相应的polyfill和配置需要的plugins
  2. 使用presets

presets

  1. 如果没有配置presets和plugins,babel是如何选择哪些插件进行处理?
    如果没有配置presets,babel什么也不转换

  2. babel推荐使用preset-env,属性说明

targets 属性

preset-env 根据配置的targets属性,就是目标浏览器,加载需要的插件;如果没配置targes属性,默认会加载ES2015,ES2016,ES2017 需要的插件;

即使配置了targets属性,babel不会根据你代码中用到了哪些新语法而加载相应的插件,而是根据你的preset配置来加载需要的插件;如果没有配置targets属性,但是package.json配置了browserslist,会把这个属性作为targets来使用。

useBuiltIns属性(Babel7才支持下面前2中属性)

  • useBuiltIns: 'entry' 导致引用了太多不需要的polyfill,因为是根据targets来决定来引用哪些polyfill

需要在入口文件中直接添加对 @babel/polyfill的引用,注意只能导入一次;例如

import "@babel/polyfill";
// 或
require("@babel/polyfill")

// 或在webpack的entry配置里添加

module.exports = {
  entry: ["@babel/polyfill", "./app/js"],
};

babel根据target来替换成对应的polyfill同时把@babel/polyfill 添加到项目的依赖中,例如

npm install @babel/polyfill --save
  • useBuiltIns: 'usage' 按需引用需要的polyfill,不需要在入口文件中添加引用,但依然需要把@babel/polyfill 添加到项目的依赖中

根据文件中使用情况,添加相应的polyfill

  • useBuiltIns: false
    不会自动添加polyfill

polyfill

Babel7 如果不设置useBuiltIns属性,babel转换时不会自动插入需要的polyfill,例如promise等,因此需要自己在代码中自行引入。
当运行环境中不支持一些方法时,babel-polyfill 会给其做兼容。 但是这样做也有一个缺点,就是会污染全局变量,而且项目打包以后体积会增大很多,当不使用useBuiltIns: 'usage'属性时,会把整个依赖包也打了进去。所以并不推荐在一些方法类库中去使用。

Babel-plugin-transform-runtime 与Babel-runtime

  • Babel在转换代码时,有时候会在全局polyfill一些方法,例如,array.prototype.includs,Promise等
  • 会添加一些附加的方法,例如asyncGeneratorStep, _asyncToGenerator, _createClass,_defineProperty 等等,每一个被转换的文件中都会根据需要添加对应的辅助方法

基于以上,一方面为了开发一个lib时不污染全局变量,另一方面为了减少由于辅助代码带来的冗余代码,有了Babel-runtime

runtime的使用方法

  1. 需要添加项目依赖babel-runtime
npm install --save @babel/runtime

  1. 添加dev依赖babel-plugin-transform-runtime,作为babel的一个插件使用
npm install --save-dev @babel/plugin-transform-runtime

注意: babel-runtime 不会转码实例方法,比如这样的代码,

'!!!'.repeat(3);
'hello'.includes('h');

只会通过添加相应的polyfill来处理

webpack使用

浏览器的渲染过程

我们可能只知道写一些脚本,然后一张好看的页面就出现在了屏幕上。但是浏览器究竟是怎样将我们的HTML,CSS和JavaScript脚本渲染成屏幕上的像素的呢?

从浏览器接收到HTML,CSS和JavaScript脚本,到将它们渲染为像素点的过程中有许多中间步骤。要优化性能,就要了解这些步骤,也就是所谓的关键渲染路径(Critical Rendering Path)。

优化和没优化的页面渲染过程对比图

opt_v_unopt_render

渲染过程

浏览器渲染大致需要如下的步骤:

  1. 处理HTML脚本,生成DOM树
  2. 处理CSS脚本,生成CSSOM树
  3. 将DOM树和CSSOM树合并为渲染树
  4. 对渲染树中的内容进行布局,计算每个节点的几何外观
  5. 将渲染树中的每个节点绘制到屏幕中

这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

DOM树和CSSOM树合并成一棵渲染树

DOM树和CSSOM树合并成一棵渲染树

  • 从DOM树的根节点开始,遍历所有的可视节点
    有些不可见元素(比如脚本标签,元数据标签之类)会被忽略,因为它们不影响渲染的结果
    有些通过CSS隐藏掉的元素也会被忽略,比如上图中的span元素。由于该元素上显式地设置了属性“display:none”,所以不会出现在渲染树上
CSS中“visibility:hidden”和“display:none”是不同的。前者将元素隐藏起来,但是隐藏元素仍然会在页面最
终的布局中占据相应的空间(其实就是一块空白)。然而后者会直接将元素从渲染树中删除,不仅不可见,也不属于最终布局的一部分。

  • 对于每个可视节点,从CSSOM中寻找对应的样式规则,并付诸节点
  • 输出可视的节点,以及每个节点计算出来的样式
最终的渲染树既包含了所有可视的内容,又包含了相应的样式信息。快要大功告成了!有了这棵渲染树,我们就能进入下一步,布局。

在此之前,我们已经计算了什么节点是可视的以及它们对应的样式是什么,但是我们还没有计算它们在当前设备中准确的位置和尺寸。这正是布局阶段要做的的工作,该阶段在英语中也被称为“回流”(reflow).
  • 布局layout 为了算出每个对象的准确大小和位置,浏览器从渲染树的根节点开始遍历,计算页面中每个对象的几何样式。
布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
  • 最后阶段,已经知道了哪些节点是可视的、它们的样式和它们的几何外观,我们终于能够将这些信息渲染为屏幕上每个真实的像素点了。这个阶段称为“绘制”,或者“格栅化”(rasterizing)。

css对渲染的影响

在前部分中我们看到了关键的渲染途径需要同时具备DOM(Document Object Mode,文件对象模型)和CSSOM(CSS Object Model,CSS对象模型)来构造渲染树,这证明了一个重要的性能上的可能的结果:HTML和CSS都是阻止渲染的资源。

默认情况下,CSS会被当做渲染中的阻塞性资源,也就是说浏览器即使已经处理好了某些页面内容,只要CSSOM没有构建好,便不会进行渲染。浏览器会在同时具备了DOM和CSSOM时才渲染界面。

js对渲染的影响

JavaScript让我们能够修改页面的每个方面:内容、样式和用户交互行为。然而,JavaScript也能阻止DOM的构建,并延迟页面的渲染。确保你的JavaScript是异步的,并且从关键渲染路径中消除任何不必要的JavaScript,以提供最佳性能。

  • JavaScript可以查询和修改DOM和CSSDOM
  • CSSDOM阻止JavaScript执行
  • JavaScript阻止DOM的构建,除非明确地声明为异步的

脚本是在它被插入到文本中的位置处执行的。当HTML解析器遇到一个脚本标签时,它会暂停对构建DOM的处理,并让出控制权给JavaScript引擎。一旦JavaScript引擎完成了运行,浏览器再从它离开的地方重新开始,并继续进行DOM构建。
执行内联脚本会阻止DOM的构建,这也会延迟初始化渲染。

假如浏览器在我们运行脚本时还没有完成CSSOM的下载和创建会怎么样?答案很简单,并且对于性能不会很好:浏览器会延迟脚本的执行,直到它完成了CSSOM的下载和构建,当我们在等待时,DOM的构建也被阻止了!

脚本在文本中的位置是很关键的
当遇到script标签时DOM的构建会被暂停,直到脚本完成了执行。
JavaScript可以查询和修改DOM和CSSDOM
直到CSSOM准备好了,JavaScript才会执行

在使用外部的JavaScript文件的情况下,浏览器将不得不暂停来等待从磁盘、缓存或远程服务器上获取这个脚本,这会给关键路径的渲染增加数十到数千毫秒的延迟。

event timestamp

event_time

  • domLoading:这是整个加载进程开始的时间戳。浏览器从这个时间点开始解析收到的HTML页面的第一个字节。
  • domInteractive:标记了浏览器完成解析HTML,DOM树构建完毕的时间。
  • domContentLoaded:标记了DOM准备就绪且没有样式资源阻碍JavaScript执行的时间点,我们可以开始构建渲染树了。
    很多JavaScript框架会在这个事件发生后才开始执行它们自己的逻辑。因此浏览器会通过捕获domContentLoadedEventStart和domContentLoadedEventEnd来计算执行框架的代码逻辑需要多长时间。
  • domComplete:不言自明,所有的处理过程结束,所有的页面资源下载完成。浏览器窗口上表示页面还在加载的图标停止旋转。
  • loadEvent:作为所有页面加载的最后一步,浏览器会在此时触发onLoad时间,以便开始附加的应用逻辑。

domInteractive:表示DOM准备就绪。
domContentLoaded:表示DOM和CSSOM都准备就绪。如果没有JavaScript阻塞渲染,该事件会在domInteractive事件之后立即触发。

domComplete:表示页面及其附属资源都已经准备就绪。

JS(不包括动态插入的JS)执行完之后,才会触发DOMContentLoaded事件
The DOMContentLoaded event is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading
Note: Stylesheet loads block script execution, so if you have a <script> after a <link rel="stylesheet" ...>, the page will not finish parsing – and DOMContentLoaded will not fire – until the stylesheet is loaded.
这么看来,至少可以得出这么一个理论:**DOMContentLoaded事件本身不会等待CSS文件、图片、iframe加载完成**。
它的触发时机是:加载完页面,解析完所有标签(不包括执行CSS和JS),并如规范中所说的设置 interactive 和执行每个静态的script标签中的JS,然后触发。
而JS的执行,需要等待位于它前面的CSS加载(如果是外联的话)、执行完成,因为JS可能会依赖位于它前面的CSS计算出来的样式。

异步添加的脚本

  1. 异步添加的脚本(动态插入的JS)不会阻塞DOMContentLoaded事件;
    asyn_js_not_block_domcontent

  2. 同步和异步添加的对比

同步js
js_block_render

异步js
asyn_js

浏览器相关总结

浏览器对象(BOM)

描述了与浏览器进行交互的方法和接口,可以对浏览器窗口进行访问和操作,譬如可以弹出新的窗口,改变状态栏中的文本,对Cookie的支持,IE还扩展了BOM,加入了ActiveXObject类,可以通过js脚本实例化ActiveX对象等等)。
bom

BOM对象的属性

  • Window对象:

    是整个BOM的核心,所有对象和集合都以某种方式回接到window对象。

  • Document对象:

    实际上是window对象的属性。这个对象的独特之处是唯一一个既属于BOM又属于DOM的对象。从BOM角度看,document对象由一系列集合构成,这些集合可以访问文档的各个部分。

  • Location对象:

    它是window对象和document对象的属性。Location对象表示载入窗口的URL,此外它还可以解析URI.

  • Navigator对象:

    Navigator包含大量Web浏览器相关的信息。各种浏览器支持该对象的属性和方法不尽相同。

  • Screen对象:

    通过其可以获取用户屏幕相关的信息.

  • 全局的方法和属性

    • alert/confirm/prompt
    • setTimeout/setInterval/eval/JSON
    • ...

浏览器的本地存储

localstore

from

cookie

  • 兼容性好,基本上所有浏览器都适用
  • 容量小,约4kb
  • 占用http请求头,每次请求都上传
  • 其他
    • 如果不想让js操作某个cookie,设置cookie时HttpOnly 为true
    • 如果只能https时发送cookie,设置secure为true

IE userdata

  • IE独有,在win2000(IE5.5)、XP(IE6、IE7),Vista(IE7)下都是可以正常使用的
  • 被杀毒软件删除本地存储文件后,会出现一些异常问题
    code
(function(window, document) {
  "use strict";
  var userData, attr, attributes;
  
  if (!window.localStorage && (userData = document.body) && userData.addBehavior) {
    if (userData.addBehavior("#default#userdata")) {
      userData.load((attr = "localStorage"));
      attributes = userData.XMLDocument.documentElement.attributes;
      
      window.localStorage = {
        "length" : attributes.length, 
        "key" : function(idx) { return (idx >= this.length) ? null : attributes[idx].name; }, 
        "getItem" : function(key) { return userData.getAttribute(key); }, 
        "setItem" : function(key, value) {
          userData.setAttribute(key, value);
          userData.save(attr);
          this.length += ((userData.getAttribute(key) === null) ? 1 : 0);
        }, 
        "removeItem" : function(key) {
          if (userData.getAttribute(key) !== null) {
            userData.removeAttribute(key);
            userData.save(attr);
            this.length = Math.max(0, this.length - 1);
          }
        }, 
        "clear" : function() {
          while (this.length) { userData.removeAttribute(attributes[--this.length].name); }
          userData.save(attr);
        }
      };
    }
  }
})(this, this.document);

Flash SharedObject

  • 容量适中,基本上不存在兼容性问题
  • 缺点是浏览器要安装flash,同时要在页面中引入特定的swf和js文件,增加额外负担

Local Storage/Session storage

  • 容量大、易用、强大、原生支持
  • 兼容性差些(chrome, safari, firefox,IE 9,IE8都支持 localStorage,主要是IE8以下版本不支持)、安全性也差些(所以请勿使用localStorage保存敏感信息)

web SQL/IndexedDB

  • html5新增,只有部分浏览器支持

application cache

  • 离线应用存储,整个url对应的资源进行存储
  • 使用过程也有很多坑

浏览器协议

  • 浏览器本身支持,与获取文档相关的协议

    • http:/https:
    • ftp:
    • file:
    • ...
  • 第三方应用或插件支持的协议

    • mailto:
    • tel:
    • sms:
    • ...
    <a href="mailto:[email protected]?subject=This%20is%20the%20subject&[email protected]&body=This%20is%20the%20body">Send email</a>
    
    <a href="tel:+468123456">Call</a>
    
    <a href="sms:+15105550101?body=hello,你好,我是宋史超"> 带中文 </a> 
  • 伪协议

    是浏览器内部保留协议用于方便访问浏览器的脚本解析引擎和某些内部功能

    • javascript:

    javascript:协议允许后面执行javascript代码,并且继承了调用的当前域。有些情况会对后面的内容处理两次,如果代码正确的话,会把后面的代码当成html解析,覆盖掉原来的html代码,所以很多javascript:void+ 一段代码:

    <iframe src='javascript:"hello"'> </iframe>
    • data:
      该协议会创建一个短小的内置式文档,

    例子

    <!-- 仅一行代码,打造一个在线编辑器 -->
    data:text/html, <html contenteditable>
    
    <!-- 图片采用datauri协议,减少请求 -->
    <img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD...">
    • about:blank

    about:blank这个URL可以用来被创建DOM对象,例如:

    <iframe src="about:blank" name="test"></iframe>
    <script> 
    frames["test"].document.body.innerHTML = "<h1>Hi!</h1>";
    </script>

    在浏览器中,创建一个about:blank页面,它继承的域为创建它的页面的域。例如,点击一个链接,提交一个表单,创建一个新窗口,但是当用户手动输入about:或者书签中打开的话,他的域是一个特殊的域,任何其他的页面都不可以访问。

    • view-scource:

    查看源代码协议

同源策略(Same Origin policy,SOP),

浏览器通过隔离来自不同源的文件之间的访问来提供各种安全保障。
同源策略一开始是为了管理DOM之间的访问,后来逐渐扩展到Javascript对象,但并非是全部。例如非同源的脚本之间可以调用location.assign()和location.replace()。

什么是源

协议-域名-端口 三元素组合在一起组成一个“源(Origin)",这三个要素必须都相同,不满足这三个要素相同的条件的被称为跨域。

原始资源 要访问的资源 非IE浏览器 IE浏览器
http://example.com/a/ http://example.com/b/ 可以访问 可以访问
http://example.com/ http://www.example.com/ 主机不匹配 主机不匹配
http://example.com/a/ https://example.com/a/ 协议不匹配 协议不匹配
http://example.com:81/ http://example.com/ 端口不匹配 可以访问

同源策略的影响

  • cookie

js只能操作当前页面的域名下的cookie,不能操作不同源的域名下的cookie;

cookie设置总结:
当在foo.example.com设置cookie,domain设置为不同值时,浏览器的cookie最终生效的域如下:

domain设置为: 非IE浏览器 IE浏览器
设置为空 foo.example.com(当前页面所在域) *.foo.example.com
bar.foo.example.com cookie设置失败,设置的域是当前域的一个子域
foo.example.com *.foo.example.com
baz.example.com cookie设置失败,域名不匹配
example.com *.example.com
ample.com cookie设置失败,域名不匹配
.com 设置失败,域名太广,存在安全风险。
  • XMLHttpRequest

    XMLHttpRequest请求严格遵守同源策略,非同源不可以请求。

    跨域请求解决方案

    • 当协议和端口号相同,两个域名具有相同的父域名时,采用document.domain= 公共的父域名
    • 服务器端代理
    • JSONP
    • html5的postMessage + onmessage
    • XHR2的跨源资源共享(CORS)
      CORS机制为跨域的ajax请求提供了可能,需要被跨域请求的服务器设置
      Access-Control-Allow-Origin:被跨域的源,例如
    http响应头:
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin:http://another-domain.com
    
  • iframe

    • 页面中的js只能访问同源的子节点iframe/同源的顶层页面中的dom节点
    • 执行同源的子节点iframe/同源的顶层页面中的js。

    跨域iframe解决方案
    10种方案

  • localstorage

    现在的浏览器都支持,除了IE6与IE7。

    localStorage对象可以长时间保存,并且遵守同源策略。

    但是在IE8中localStorage会把域名相同但是协议分别为HTTP和HTTPS的内容放在一起,IE9中已修改。

    在Firefox中,localStorage没有问题,但是sessionStorage也是会把域名相同的HTTP与HTTPS放在一起。

  • Adobe Flash

    AllowScriptAccess参数:用来控制flash通过ExternallInterface.call()函数调用javascript的时的限制。

    有三个值:always,never和sameorigin,最后一个值只允许同域的JavaScript操作(08年之前默认为always,现在默认为sameorigin)。

    AllowNetworking参数:用来控制flash与外部的网络通讯。

    可选的值为:all(允许使用所有的网络通讯,默认值),internal(flash不能与浏览器通讯如navigateToURL,但是可以调用其他的API),none(禁止任何的网络通讯)

浏览器的工作过程

  • 本地缓存查找

    • 如果本地缓存中存在该url的内容
      • 不过期时,返回本地缓存的内容
      • 过期时,条件查询请求url的内容
    • 直接请求url的内容
  • 向服务器发出请求url

    • DNS查询,如果本地DNS缓存有,直接得到ip,否则通过DNS查询获取服务器ip
    • 建立连接(http/https)
    • 向服务器发出http请求
  • 数据在网络上转发

  • 服务器端处理请求并返回结果

    • 路由转发
    • 获取数据(缓存/file/db/...)
    • format数据(或渲染模版,输出数据)
    • 输出内容
  • 浏览器接收和解析返回的内容

    • 接收内容

    • 解析内容和绘制

      • 解析HTML/SVG/XHTML,构建文档对象模型(DOM树) 参考
      • 解析CSS,构建 CSS对象模型(CSSOM树) 参考
      • 将DOM树和CSSOM树合并为渲染树 参考
      • layout/reflow,计算每个节点的几何外观

      计算每个Element的位置,布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。

      • paint或格栅化”(rasterizing),将渲染树中的每个节点绘制到屏幕中

      通过调用操作系统Native GUI的API绘制,将渲染树转化为屏幕上的每个像素点

浏览器单线程机制 参考

浏览器的UI更新和js的执行共用同一个线程UI Thread,同一时间要么进行UI更新,要么执行js代码。他们通过共享同一个任务队列UI Queue来实现这一机制。

浏览器的限制

  • iframe 不同iframe是不同的运行空间,互相不受干扰,同域是可以通过dom提供的api访问以及执行js。
  • html5的api,例如文件访问,地理位置查询等,低版本浏览器不支持
  • css布局只能按照矩形块来布局,除了css3的变形,css3的属性,新的属性
  • ...

web性能优化—浏览器解析和渲染优化

对浏览器解析和渲染页面的优化,主要通过以下几个方面:

  • 优化页面的关键路径
  • 减少(或避免)渲染需要的html/css/js
  • 优化html/css/js的代码,加快浏览器解析html、css的时间,提高js的执行效率
  • 适当的触发浏览器硬件加载

首先需要了解浏览器的解析和渲染的过程

浏览器的解析和渲染过程

主要分以下几个步骤:

  1. 解析HTML/SVG/XHTML,build DOM树
  2. 解析CSS,build CSSOM树
  3. build rendering tree. Rendering Tree 渲染树并不等同于DOM树,因为一些像Header或display:none的东西就没必要放在渲染树中了。
  4. layout/reflow.计算每个Frame(也就是每个Element)的位置,这又叫layout和reflow过程
  5. paint. 最后通过调用操作系统Native GUI的API绘制

渲染过程

解析过程优化

根据http响应返回的数据根据页面编码进行解码,而浏览器判断编码主要是依据以下方法:

  1. HTTP 头中的 Content-Type: text/html; charset= 信息,这一般有最高的优先级;
  2. 网页本身 meta header 中的 Content-Type 信息的 charset 部分,对于 HTTP 头未指定编码或者本地文件,一般是这么判断;
  3. 假如前两条都没有找到,浏览器菜单里一般允许用户强制指定编码;
    部分浏览器 (比如 Firefox) 可以选择编码自动检测功能,使用基于统计的方法判断未定编码。
  4. 编码确定后,网页就被解码成了 Unicode 字符流,可以进行HTML 解析.

解析返回的内容,一个字符一个字符解析,边下载边解析

优化点:

  • 通过HTTP头信息或meta标签尽早指定正确的字符编码,省去浏览器判断字符编码的时间

解析过程遇到css,js等资源,就会发起请求,下载css,js,..,但是浏览器同一个域名并行下载个数有限制

优化点:

  • js/css 多域名存放,增加并行下载的个数,注意与cdn查询成本从和浏览器处理性能之间的平衡

进一步阅读

各浏览器的并行连接数(同域名)

build dom tree

字节(Bytes) -> 字符(characters) -> 标记(tokens) -> 节点(nodes) -> 对象模型
建立dom树过程

  1. 转换(Conversion): 浏览器从磁盘或者网络上读取HTML的原始字节,然后根据指定的编码规则转换成单独的字符(比如按UTF-8编码)。
  2. 标记分解(Tokenizing):浏览器将字符串按照W3C HTML5标准转换成确定的标记,比如、以及其他带尖括号的字符。每个标记都有特定的意义以及一套规则。
  3. 词法分析(Lexing):分解出来的标记被转换成能定义其属性和规则的对象。
  4. DOM 构造: 最终由于HTML标记创建出来的对象被关联到一个树形数据结构。这颗树会反映在原先标签里定义的父子关系,比如HTML对象就是body对象的父对象,body对象又是paragraph对象的父对象等等。

优化点:

  • 去除HTML/CSS/js/中的空白字符,省去浏览器解析空格的时间
  • 删除无用的html标签,精简html结构,尽量减少不必要的dom的深度

进一步阅读

谷歌 Web 开发最佳实践手册(4.1.1):创建对象模型

build CSSOM tree

当css下载完成后,与HTML一样,进行构建cssom:
cssom-construction

cssom-tree

提高CSS解析的效率,主要通过几个方面:

  • 避免通配符选择器
  • 删除多余的修饰语
  • 避免使用后代选择器,特别是那些指定多余祖先的
  • 使用class选择器代替后代选择器
  • 在IE中避免对非链接元素应用:hover
  • 避免CSS expressions

进一步阅读

谷歌 Web 开发最佳实践手册(4.1.1):创建对象模型

优化浏览器渲染

对《优化浏览器渲染》的补充

Mozilla里的高效CSS规则

build render tree

浏览器只有同时具备DOM(Document Object Mode,文档对象模型)和CSSOM(CSS Object Model,CSS对象模型)才能来构造渲染树。

render tree

为了创建渲染树,浏览器大致需要如下的步骤:

  1. 从DOM树的根节点开始,遍历所有的可视节点
  2. 有些不可见元素(比如脚本标签,元数据标签之类)会被忽略,因为它们不影响渲染的结果
    有些通过CSS隐藏掉的元素也会被忽略,比如上图中的span元素。由于该元素上显式地设置了属性“display:none”,所以不会出现在渲染树上。(CSS中“visibility:hidden”和“display:none”是不同的。前者将元素隐藏起来,但是隐藏元素仍然会在页面最终的布局中占据相应的空间(其实就是一块空白)。然而后者会直接将元素从渲染树中删除,不仅不可见,也不属于最终布局的一部分)
  3. 对于每个可视节点,从CSSOM中寻找对应的样式规则,并付诸节点
  4. 输出可视的节点,以及每个节点计算出来的样式

谷歌 Web 开发最佳实践手册(4.1.2):渲染树结构、布局和绘制

浏览器即使已经处理好了某些页面内容,只要CSSOM没有构建好,便不会进行渲染,CSS是渲染过程中的阻塞性资源

js前面的css会阻塞js的执行

js执行阻塞页面的解析,domtree的构建

css优化点:

  • gzip压缩,提高传输速度,css尽早加载
  • 优化css代码,css代码瘦身,压缩,去除冗余
  • put css head, js bottom

js优化点:

  • js压缩提高传输速度
  • put css head, js bottom
  • 页面中不要插入js的代码,根据实际情况对js进行defer/asyn,或创建标签异步加载js;
  • 代码优化,使用id选择器,使用标准的css选择器,
    〉* 对常用变量暂存
  • 对长时间执行的代码用定时器来分片执行或html5 worker

reflow

我们已经计算了什么节点是可视的以及它们对应的样式是什么,但是我们还没有计算它们在当前设备中准确的位置和尺寸。这正是布局阶段要做的的工作,该阶段在英语中也被称为“回流”(reflow)。布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。

paint

最后阶段,已经知道了哪些节点是可视的、它们的样式和它们的几何外观,我们终于能够将这些信息渲染为屏幕上每个真实的像素点了。这个阶段称为“绘制”,或者“格栅化”(rasterizing)。一旦布局阶段完成,浏览器便开始绘制阶段,将渲染树转化为屏幕上的每个像素点

show

通过调用操作系统Native GUI的API绘制每一个像素点,更新显存,给显示器发送信号,显示器根据得到的信号进行显示。
如果浏览器刷新频率为60hz,那么定时器的时间小于1/60s(16.666..ms)就没有意义。

优化点:

  • 设置适当的定时时间,小于1/60是没意义
  • 使用requestAnimationFrame(rAF)来代替定时器动画
  • 利用GPU加速

进一步阅读

Javascript高性能动画与页面渲染

High Performance Animations

Accelerated Rendering in Chrome
Jank Busting for Better Rendering Performance

Leaner, Meaner, Faster Animations with requestAnimationFrame

Optimizing Visual Updates

GPU Accelerated Compositing in Chrome

reflow & repaint

以下这些会引起reflow & repaint:

  1. 改变文字大小

  2. 应用新的样式或者修改任何影响元素外观的属性,修改class/style

  3. 激活伪类,如:hover

  4. DOM元素的添加、修改(内容)、删除( Reflow + Repaint),添加新的页面内容,例如输入框输入文字等

  5. 读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE))

  6. 当一个元素的外观被改变,但没有改变布局的情况下发生,如改变visibility、outline、前景色,scroll等,会引起repain

  7. Resize浏览器窗口、滚动页面,

  8. 没有指定的图片尺寸,或者如果指定的尺寸不符合图片的实际尺寸,一旦图片下载,浏览器将需要reflows和重新绘制页面

  9. 在页面中间通过style或外链加载样式,也会导致reflow

  10. 不指定页面的编码,浏览器开始渲染页面之后发现指定的编码设定与其设定或默认值不同,都会导致重新解析文档并重绘页面。如果编码的变化影响到了外部资源(例如css\js\media),浏览器甚至会重新对资源进行请求;

优化点:

  • 尽可能限制reflow的影响范围,避免在document上直接进行频繁的DOM操作,如果确实需要可以采用off-document的方式进行
    1. 先将元素从document中删除,完成修改后再把元素放回原来的位置
    2. 将元素的display设置为”none”,完成修改后再把display修改为原来的值
    3. 如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入
  • 最好通过设置class的方式,避免通过style设置
  • 实现元素的动画,它的position属性应当设为fixed或absolute,这样不会影响其它元素的布局
  • 不要用table布局,用table的场合,可以设置table-layout为auto或fixed,这样可以让table一行一行的渲染
  • 避免使用css expression
  • 在HTML的标签中或在CSS中为所有图片指定宽度和高度,或它的块级父元素指定宽度和高度;指定与图片本身相一致的尺寸
  • 把外部样式表和内联样式块放在页面的中,确保样式表首先被下载和解析
  • 通过HTTP头信息或meta标签尽早指定正确的字符编码

其他优化点

  • 能用css实现的不用img来做,能用css3实现,不用js来做(当然要综合考虑考虑兼容性和成本)
  • 不要在线上使用@import 加载css
  • 尽可能使用标准的CSS属性

进一步阅读

Rendering: repaint, reflow/relayout, restyle

Web页面Repaint和Reflow

如何减少浏览器repaint和reflow(上)

如何减少浏览器repaint和reflow(中)

如何减少浏览器repaint和reflow(下)

适当的触发硬件加速

在Webkit内核的浏览器中,硬件加速会把需要渲染的元素放到特定的『Composited Layer』中,表示放到了一个新的『复合层(composited layer)』中渲染。每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中 transform是不会触发 repaint 的,这一点非常类似3D绘图功能,最终这些使用 transform的图层都会由独立的合成器进程进行处理。
层创建标准:

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 元素
  • 拥有 3D (WebGL) 上下文或加速的 2D 上下文的 元素
  • 混合插件(如 Flash)
  • 对自己的 opacity 做 CSS 动画或使用一个动画 webkit 变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个 z-index 较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染), 如果有一个元素,它的兄弟元素在复合层中渲染,而这个兄弟元素的z-index比较小,那么这个元素(不管是不是应用了硬件加速样式)也会被放到复合层中。最可怕的是,浏览器有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染,

jQuery的使用优化

jQuery代码优化:选择符篇

jQuery代码优化:遍历篇

jQuery代码优化:基本事件篇

jQuery代码优化:事件委托篇

js事件总结

事件概述

事件是javascript和HTML交互基础, 任何文档或者浏览器窗口发生的交互, 都要通过绑定事件进行交互;

事件响应是异步处理的

事件响应是异步处理的, 当某个事件触发时,不一定马上会得到响应,而是异步处理的,响应的时间是不一定的。

事件的分类

DOM0/原始 事件模型

  • 所有的浏览器都支持
  • 绑定速度最快,直接写在元素上
  • 只能绑定一个handler
  • 没有冒泡还是捕获之说
  • handler中获取event对象各浏览器差异
  • 删除 element.on+event = null;
<!-- DOM0 绑定的方法 -->
<a id="foo" href="javascript;" onclick="alert('did stuff inline');">Click me</a>
<script type="text/javascript">
	// HTML 中的 onclick="" 属性 和 element.onclick 等同
	document.getElementById('foo').onclick = fucntion(){
		alert('did stuff inline');
	}
</script>

DOM2/标准 事件模型

IE事件模型(>=IE9,同时也支持标准事件模型)

标准事件模型与IE的事件模型的区别

选项 标准事件模型 IE事件模型(IE9之前)
支持冒泡和捕获 都支持 只支持冒泡
绑定方法 addEventListener attachEvent(ie5及+)
解绑方法 removeEventListener detachEvent(ie5及+)
事件的名称参数 事件名字,例如click on+事件名字,例如onclick
事件hanlde中对事件对象的获取 事件处理函数的第一个参数或window.event(FF不支持) 通过window.event或事件处理函数的第一个参数
事件hanlde中的this 事件绑定的对象 window
阻止冒泡的方法 event.stopPropagation(); event.cancelBubble = true;
阻止默认行为的方法 event.preventDefault(); event.returnValue = false;
事件target event.target event.srcElement
事件relatedTarget event.relatedTarget event.fromElement/event.toElement
冒泡过程中currentTarget event.currentTarget
单个元素添加多个事件处理程序,执行的顺序 执行顺序按照它们添加的顺序执行 执行顺序按照添加的顺序相反的顺序执行
  • 支持冒泡和捕获

    • 支持冒泡和捕获
    • 只支持冒泡
  • 绑定方法

    • addEventListener
    • attacthEvent
     var eventUtil = {
     on : function(el, type, handler) {
         if(el.addEventListener) {
             el.addEventListener(type, handler, false);
         }else if( el.attachEvent ) {
             el.attachEvent("on"+type, handler);
         }else{
             el["on"+type] = handler;
         }
     },
     off : function(el, type, handler) {
         if( el.removeEventListener ) {
             el.removeEventListener(type, handler, false)
         }else if( el.detachEvent ) {
             el.detachEvent(type, handler);
         }else{
             el["on"+type] = null;
         }
     }
     };
  • 解绑方法

    • removeEventListener
    • detachEvent
  • 事件的名称参数

    • 事件名字,例如click
    • on+事件名字,例如onclick
  • 事件hanlde中对事件对象的获取

    • 支持标准事件模型的浏览器,不管用addEventListener还是ele.onclick方式绑定事件,通过事件处理的handle函数第一个参数就可以获取event对象;
    • 而IE不支持标准事件模型的浏览器(<IE9),ele.onclick方式绑定事件时,只能用window.event来获取
     function handle(event){
     	var event = event || window.event;
     	//...
     }
    
     ele.attachEvent && ele.attachEvent("onclick", handler);
    ele.addEventListener && ele.addEventListener("click", handler, false);
    ele.onclick = handler;
  • 事件hanlde中的this

    • addEventListener绑定事件的handle中的this指向被绑定的元素ele
    • attacthEvent绑定事件的handle中的this指向window,而不是被绑定的元素ele
  • 阻止冒泡的方法

    • event.stopPropagation();
    • event.cancelBubble = true;
     function handle(event){
     	var event = event || window.event;
    
     	if(event.stopPropagation){
     		event.stopPropagation();
     	}else
     	{
     		event.cancelBubble= true;
     	}
    
     	//...
     }
  • 阻止默认行为的方法

    • event.preventDefault();
    • event.returnValue = false;
     function handle(event){
     	var event = event || window.event;
    
     	if(event.preventDefault){
     		event.preventDefault();
     	}else{
     		event.returnValue = false;
     	}
     	//...
     }
  • 事件target

    • event.target
    • event.srcElement
     function handle(event){
     	var event = event || window.event;
     	var target = event.target || event.srcElement;
     	//...
     }
  • 事件的relatedTarget

    • relatedTarget
    • fromElement/toElement
     function handle(event){
     	var event = event || window.event;
     	var relatedTarget = event.relatedTarget || event.fromElement/*toElement*/;
     	//...
     }

事件对象的其他参数

冒泡和捕获

冒泡和捕获

冒泡带来的便利和不便

  • 通过冒泡机制来实现事件代理
  • 冒泡对某些事件的处理带来困扰例如mouseover/mouseout

事件代理

因为事件有冒泡机制,所有子节点的事件都会顺着父级节点跑回去,所以我们可以通过监听父级节点来实现监听子节点的功能,这就是事件代理。

使用事件代理主要有两个优势:

  • 减少事件绑定,提升性能。之前你需要绑定一堆子节点,而现在你只需要绑定一个父节点即可。减少了绑定事件监听函数的数量。
  • 动态变化的 DOM 结构,仍然可以监听。当一个 DOM 动态创建之后,不会带有任何事件监听,除非你重新执行事件监听函数,而使用事件监听无须担忧这个问题。

不能冒泡的事件

  • focus/blur事件不冒泡,但focusin and focusout Fire at the same time as focus and blur会冒泡。
    from

  • mouseenter/mouseleave(only ie/opera 支持)不冒泡,在mouseover/mouseout时触发会冒泡

  • submit/reset事件
    功能:监听表单的提交/重置;

    在IE6到IE8下只会冒泡到当前提交表单所在的form标签中,也就说document下直接代理监听多个表单提交会有兼容性问题。

  • change事件
    功能:监听元素value值的改变,input和select 在blur才触发;

    change事件在IE6到IE8,chrome,safari下都不会冒泡,也不能像focus和blur一样可以用focusin和focusout替代,在IE下也不能通过设置captrue实现对事件的捕获,其他高级浏览器下可以。

  • select事件
    功能:监听文本域中文字的选中。

    select事件在IE6到IE8,chrome,safari下都不会冒泡,但是比change好的一点是,在IE下可以使用selectstart替代,而在高级浏览器中可以直接设置captrue为捕获即可。

键盘事件

键盘事件的触发流程

一个典型的按键输入,依次触发的事件依次是

  • keydown
  • keypress
  • keypress事件触发后,input元素更新value属性
  • keyup

如果一个按键被按下并重复按下,则可能在keydown和keyup事件之间触发多个keypress事件。

键盘事件的兼容性

选项 其他浏览器 IE
不能打印的功能按键(退格,回车,escape)和箭头方向键,翻页键,F1-F12 有时候会触发keypress事件 IE只有当按键有一个ASCII码的时候,才会触发keypress
获取按键码的事件属性 keyeCode/charCode keyCode
keydown/keyup keyeCode为虚拟按键码/charCode为0 keyCode为虚拟按键码
keypress keyeCode为虚拟按键码/charCode为虚拟按键码,FF为0 keyCode为字符的ASCII值
输入中文时 不触发keypress,大部分浏览器触发keydown,keycode为229 不触发keypress,触发keydown,keycode为229

常见案例

  • 过滤输入框的字符只能输入特定的字符
    • keyup + change(粘贴)
  • 搜索框的suggest
    • keyup + paste(粘贴) ?

键盘事件的兼容性列表

鼠标事件

鼠标事件的触发流程

一个典型的鼠标拖动操作,依次触发的事件依次是

  • mouseover
  • mousedown
  • mousemove
  • mouseup
  • click
  • mouseout

如果中间鼠标移动,将会触发多个mousemove事件。

鼠标事件的问题

  • mousedown/mouseup不一定都会触发
    当按下鼠标进入目标或按下鼠标移出目标区域

  • mouseover和mouseout的冒泡问题
    适当的用mouseenter和mouseleave替代mouseover和mouseout

jQuery/zepto的事件绑定

//绑定
$(ele).on( events [, selector ] [, data ], handler );

//触发事件
$(elementA).trigger( events );

js的this

在js中this关键字永远都指向当前正在调用该函数(方法)的所有者。

在js中的this是运行时绑定的,不是编写时绑定,跟函数的作用域链只与函数的编写的位置有关是完全相反的,this指代的对象是函数执行时的context,跟函数的声明位置没有任何关系,而这个context随函数调用的方法的不同而不同。

this常见的几种情形

当函数作为对象的方法调用时,函数中的this指向该对象

例如:

var o1 = {
	name:'jack',
	getName:function () {
		console.log(this.name); // this 指的是o1这个对象
	}
};
o1.getName();

当函数作为构造函数使用时,函数中的this指向新建的对象

例如:

function Foo(name){
	console.log(this); // this -> 指向new Foo 创造出来的新对象
	this.name = name;
	this.getName = function(){
		console.log(this.name); // this -> 指向新对象
	};
}
var o2= new Foo('tom');
o2.getName();

当子类继承父类时,调用父类的方法时,父类方法中的this也指向新建的子类

function Parent(){
    this.name='parent';
    this.age = 50;
    this.setAge= function(age){
        this.age = age; 
    }
    this.setToy = function(toy){
        this.toy = toy;
    }
    this.setName = function(name){
        this.name = name;
    }
}
function Child(){
    this.name='child';
}

Child.prototype = new Parent();
Child.prototype.constructor = Child;

var o = new Child();
console.log('before:o.name:'+o.name);

//set name,child has prop name
o.setName('kimi'); // this指向新建的子类实例
console.log('o.name:'+o.name);

//age in parent,not in child,set prop  in child
o.setAge(12);  // this指向新建的子类实例
console.log('o.age:'+o.age);
console.log('o[[Prototype]].age:'+o.__proto__.age);

//toy not in ancestors
console.log('before:o.toy:'+o.toy);
o.setToy('OTT eggs'); // this指向新建的子类实例
console.log('after:o.toy:'+o.toy);
console.log('o[[Prototype]].toy:'+o.__proto__.toy);

当通过 apply 或 call 或 bind 来改变函数的this时,函数中的this指向指定的对象

例如:

function say(age){
    console.log("my name is " + this.name + ",I'm "+ age +" years old");
}
var o = {
    name: 'tom'
};

say.call(o,12);
say.apply(o,[12]);
var say2 = say.bind(o);
say2(12);

在事件处理函数中,this一般执行触发事件的dom对象

var value = 'value in global';
function $(id){
    return document.getElementById(id);
}
var t1 = $('t1'),
    t2 = $('t2'),
    t3 = $('t3'),
    t4 = $('t4'),
    t5 = $('t5');

function handle(){
    console.log(this.value);
}

t1.addEventListener('click' ,function(){
    console.log(this.value);  // this指向t1
},false);

//or 
t2.addEventListener('click', handle, false); // this指向t2


t3.onclick = handle; // this指向t3

//or
t3.onclick = function(){
    consoel.log(this.value); // this指向t3
}

//t4: inline js: onclick="console.log(this.value);" //this指向t4

// 注意如果在dom上通过内联js的方式绑定事件,事件处理的句柄是调用另外一个handle,那么handle里面的this不指向dom对象
//t5: inline js: onclick = "handle();" // *****global name ***

// 上述内联的js绑定等价于
t5.onclick = function(){
    // 这里this指向t5
    handle(); // 但是handle函数里面的this指向global对象
}

// 同时需要注意的是,通过ie的attachEvent绑定事件,this也是指向global对象
t5.attachEvent('onclick', function(){
    this; // this指向global对象,即window
});

当函数执行时,不是以上几种情况时,函数中的this一般指向global对象

例如:

var name="global name";
function say(age){
    console.log("my name is " + this.name + ",I'm "+ age +" years old");

    // 但是如果是在严格模式下,this不指向global对象
    console.log(this); // *undefined*,not global again
}
say(12);

但很多情况下,当一个函数执行时,不是看起来那么简单和纯粹,让人会错误的当成是前2种情况,例如

对象的方法脱离对象单独调用时,函数中的this不再指向对象

var name="global name";
var o1 = {
	name:'jack',
	getName: function () {
		console.log(this.name);
	}
};
var getName = o1.getName;
getName(); // this 指向gloabal对象
 /* 
    对getName进行标识符解析的结果:
    getNameReference = {
      base: global,
      propertyName: 'getName'
    };
    因为getName是一个引用类型的值,在该函数激活时,this被设置为bae对象global
*/

对象的方法中,执行另外一个函数,函数中的this不再指向对象

var name="global name";
var o1 = {
	name:'jack',
	getName: function () {
                function bar(){
		    console.log(this.name); // bar执行时,不是作为o1的方法调用,所以this不指向o1
                }
                bar();
                // bar() === AO(getName exec context).bar()
                // 活跃对象AO总是会返回this值为——null(用伪代码来表示,AO.bar()就相当于null.bar())。然后,如此前描述的,this的值最终会由null变为全局对象。
	}
};
o1.getName(); // this 指向global对象

setTimeout/setInterval 定时器处理函数中的this指向global对象

<input id="click-ok" type="button" value="Click me">
<script>
  document.getElementById('click-ok').onclick = function() {
    //bad
    setTimeout(function() { this.value='OK' }, 100); // this 指向global对象

    //good
    //var self = this
    //setTimeout(function() { self.value='OK' }, 100);
  }
</script>

在jQuery中的this

dom collects

$('.classname').each(function(){
    console.log(this);// #<HTMLXXXElement>,not jquery object
    var $this = $(this);
});

event handle

$('.classname').click(function(){
    console.log(this);//event #<HTMLXXXElement>,not jquery object
    var $this = $(this);
});

async callback

ajax

$.ajax({
    url:url,
    type:'GET',
    //context:$('#out'),//通过context参数改变回调函数中的this
    success:function(){
        console.log(this); // this指向调用本次AJAX请求时传递的options参数
    },
    error:function(XMLHttpRequest, textStatus, errorThrown){
        //	this; 调用本次AJAX请求时传递的options参数
    }
});

jsonp

function callbackfun(){
    console.log(this);// golbal
}

$.ajax({
    url:url,
    //context:$('#out'),//通过context参数改变回调函数中的this
    dataType:'jsonp',
    jsonpCallback:'callbackfun',
    success:function(){
        console.log(this);// this指向调用本次AJAX请求时传递的options参数
    }

})	

几种特殊情况

in with

var x = 10;

with ({
  foo: function () {
    alert(this.x);
  },
  x: 20
}){
  foo(); // 20
}
/*
    foo 函数的执行上下文的作用域链为: __withObject + foo[[Scope]]
    fooReference = {
      base: __withObject,
      propertyName: 'foo'
    };
    所以foo上下文的this被设置为fooReference的base对象,即with对象;
    不过,请注意x的值与this.x的值的不同
*/

数组的参数作为function执行时的this

例如arguments

function foo(){
 console.log(this.length);
 console.log(this[1])
}

function task(){
    arguments[0](); // arguments[0]=== foo, 当foo()执行时,this指向的是arguments
    // 上面等同于arguemnts.0(),就好像foo是arguments的方法执行一样,因此this指代的是arguments
}

task(foo,100); // 2, 100

// 不仅是arguments,当把函数作为数组的元素执行时都遵守一样的规则,例如
var arr = [foo,100];
arr[0]();// this指向arr这个数组,this[1] === 100


web性能优化概述

性能优化,是一个很笼统的概念,抛开具体的时代背景和应用场景谈性能优化,都是耍流氓。

什么时候需要性能优化

理论上来说,我们指定的技术方案,编写的代码都应该是质量完备、性能良好,运行的又快又好的,但是有时候因为人员的素质问题,项目的紧急问题等原因导致没有时间和人力等各种条件做一个完美的方案,而且过早的优化是万恶之源。但是一旦有时间和人力,就应该时刻准备着进行优化。

性能优化的指导方针

性能优化不能是眉毛胡子一把抓,而应该是逐步有序的进行,先抓主要的问题,再处理次要的问题,才能快速见效,因此性能优化之前首先要找到性能的瓶颈在哪里,然后有针对的进行优化。

同时性能优化需要具体问题具体分析,要针对具体的应用场景和上下文来进行优化。随着硬件和网速以及设备的升级,10年前的优化方法有可能现在已经不再适用;针对pc端有效的优化措施,拿到移动端有的也不再适用;别的大厂的优化方法和措施拿到自己的公司也不一定能贯彻和执行的起来,因此一切的优化措施都要因地制宜,有针对的进行实施。

虽然过去总结的很多优化措施现在有可能是不适合的,但是把web加载过程的各个环节进行分析,审视每一个步骤和环节的时间和资源成本,通过各种方法降低web加载过程每一个环节需要的时间,这些方法就是性能优化的方法,雅虎的前端优化的14条军规就是这些优化方法的子集而已。因此性能优化不是背背别人总结的要点,性能优化的方法也不是古老的神秘秘诀,是每一个人都可以总结和发现的。

分析web页面的加载过程

请求页面内容过程

  • 本地缓存查找

    • 如果本地缓存中存在该url的内容
      • 不过期时,返回本地缓存的内容
      • 过期时,条件查询请求url的内容
    • 直接请求url的内容
  • 向服务器发出请求url

    • DNS查询,如果本地DNS缓存有,直接得到ip,否则通过DNS查询获取服务器ip
    • 建立连接(http/https)
    • 向服务器发出http请求
  • 数据在网络上转发

  • 服务器端处理请求并返回结果

    • 路由转发
    • 获取数据(缓存/file/db/...)
    • format数据(或渲染模版,输出数据)
    • 输出内容
  • 浏览器接收内容

浏览器解析和渲染过程

  • 解析HTML/SVG/XHTML,构建文档对象模型(DOM树) 参考
  • 解析CSS,构建 CSS对象模型(CSSOM树) 参考
  • 将DOM树和CSSOM树合并为渲染树 参考
  • layout/reflow,计算每个节点的几何外观,计算每个Element的位置,布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
  • paint或格栅化”(rasterizing),将渲染树中的每个节点绘制到屏幕中,通过调用操作系统Native GUI的API绘制,将渲染树转化为屏幕上的每个像素点

用户交互过程

页面加载之后,用户通过鼠标,键盘,手touch等操作与浏览器发生交互,交互过程浏览器可能会重新进行布局和重绘,同时交互过程可能会涉及到js的执行。

对页面加载每一个环节进行优化

页面内容请求过程优化

页面请求过程优化主要是减少请求和响应的时间,主要通过几个方面:

  • 减少(或避免)请求数
  • 减少请求和响应的数据量
  • 缩短传输的路径和中间延迟
  • 增加传输带宽

页面解析和渲染过程优化

  • 优化页面的关键路径
  • 减少(或避免)首屏渲染需要的html/css/js
  • 优化html/css/js的代码,加快浏览器解析html、css的时间,提高js的执行效率
  • 适当的触发浏览器硬件加载

用户交互过程优化

交互过程优化的核心点是通过各种方法,减少交互过程的卡顿,让用户感觉起来流畅;

  • 通过预加载和本地缓存等手段缩短请求资源的可感知时间
  • 通过优化手段使帧率接近60fps,减少感知上的卡顿
  • 通过加载的动画,等待动画都手段减轻等待加载资源的焦虑
  • ...

性能优化的流程

监控

发现瓶颈和问题

针对某一点优化,进行AB test,灰度测试

验证

全面实施

循环上述步骤

js原型链

一切皆是对象

从某种意义上讲,js的世界里「一切皆是对象」。不过需要注意的是,对于js中的五种基本数据类型(Primitive Type: Undefined、Null、Boolean、Number、String, Es6 新增了symbol),他们本质上可以说不属于对象,但是当调用这些基本类型的变量的方法或属性时,此时可以看做是对象了,因为此时js内部调用的不再是变量本身,而是变量对应的包装对象(Wrapper Object);

var str = 'abc';
str.length
// 当调用str.length属性时,其实是调用的包装对象的属性,调用的时候,str可以看做是「对象」

什么时候涉及到原型链

当访问一个对象的属性或方法时,不仅会从这个对象自身上去查找该属性,同时也会涉及到该对象的原型上去查找(如果在自身没有找到的话),以及对象的原型的原型上去查找,等等以此类推,如果一直没找到,会一直找下去,直到原型链的终点(null),如下所示:

对象 -> 对象的原型 -> 对象的原型的原型 -> 对象的原型的原型的原型 -> ... -> 终点

称之为原型链。

对象的原型

每一个对象都有一个隐藏的属性[[Prototype]],该属性值是该对象的原型对象的引用。

获取一个对象的原型的方法

标准方法

Object.getPrototypeOf(obj) (ie9/FF3.5+/ Chrome5+)

个别浏览器(chrome/FF)暴露的属性__proto__

一个对象的__proto__属性就是该对象的原型对象。

对象的原型与对象的构造函数

定理:一个对象的原型恒等于该对象的构造函数的prototype属性。

例如在chrome浏览器下:

function Foo(){}
var foo = new Foo();
// foo的构造函数是Foo,每一个函数都有一个`prototype`属性,(只有函数才自动会添加prototype属性,其他变量不会自动添加该属性)
foo.__proto__ === Foo.prototype; // true

更多的例子:

var o = {};
// 因为对于o这样的对象自变量,就好比是 new Object()一样
o.__proto__ === Object.prototype; // true

// 同理对于a这样的数组自变量,就好比是new Array()一样
var a = [];
a.__proto__ === Array.prototype; // true

// 同时,另外一个比较容易理解错误的地方是
function Foo(){}
var foo = new Foo();
foo.__proto__ === Foo.prototype; // true
foo.__proto__.__proto__ === ?
// 因为Foo.prototype是系统自动生成的一个对象,就好比在生成Foo函数时,自动给Foo添加了一个prototype属性,且Foo.prototype = {},因此根据上面第一条推理,得到,
foo.__proto__.__proto__ === Object.prototype; // true

// 另外一个是关于function
function Foo(){}
Foo.__proto__ === ?
// 因为任意function都是基于Function 构造出来的,即any function = new Function() { /* ... */ },因此
Foo.__proto__ === Function.prototype; // true

// 一个意想不到的地方是关于Function
// 虽然每一个function都可以说是Function 构造出来的,那么Function 是谁构造出来的呢?
// 其实Function也是Function 构造出来的,即 Function = new Function(){ /* ... */},因此,
Function.__proto__ === Function.prototype; //true,是个对象,任意对象都是Object构造出来的
Function.__proto__.__proto__ === Object.prototype; // true

一个具体的例子

function Foo(y){
	this.y = y;
}
Foo.prototype.x = 10;
Foo.prototype.calculate = function(z){
	return this.x + this.y + z;
}

var b = new Foo(20);
var c = new Foo(30);

console.log(b.calculate(30)); //60
console.log(c.calculate(40)); //80

上述例子的原型关系图如下:

1

原型链

当访问一个对象的属性或方法时,先会查找对象本身是否存在该属性或方法,如果不存在,然后查找对象obj的原型 以及对象obj的原型的原型进行查找,这个由一系列原型组成的结构称之为原型链

例如查找obj.propName的过程

obj.propName
obj.__proto__.propName
obj.__proto__.__proto__.propName
obj.__proto__.__proto__.__proto__.propName
...,
Object.prototype.propName
null

原型链查找的过程都会达到Object.prototype,最终是null,当到找到到终点null,仍然没有找到属性propName,返回undefined;否则如果中间某个对象上找到了一个属性,就返回该属性值。

js常见类型的原型链图

2

对一个对象赋值

当读取一个对象的属性时,会涉及到原型链的查找,如果是对一个对象设置一个属性呢?会影响原型链上同名的值吗?

function F(){ this.x = 10;}
F.prototype.y = 100;
var foo = new Foo();
foo. x // 10
foo.y // 100

foo.x = 20; foo.x // 20
foo.y = 200; foo.y // 200
foo.__proto__.y // 100

如果你给一个属性赋值,你通常只能修改对象自身的属性值;

如果自身属性已经存在了,则改变这个属性的值,
否则,如果自身还没有这个属性,大部分情况会在对象上新建该属性,但不是一定,原型链上的已经定义的同名属性会有一定的影响,见下面的流程图所示。

default

详细参考:

原型链的意义和影响

通过原型链可以实现js的类的继承

因为构造函数new出来的对象在查找属性或方法时,如果自身不存在该属性或方法时,会回溯到原型链上去查找,因此对象可以直接调用原型的方法或属性,来实现继承,具体不在此详细阐述。

原型链查找对性能的影响

查找对象的属性或方法时

因为原型链的查找是一个逐层遍历的过程,因此当原型链比较长时,查找一个对象的属性或方法时,对性能有一定的影响,尤其是重复查找时,例如:

// max是很大的数
// arr是个数组
for(var i = 0, i<max; i++){
    arr.slice(/* */);
}

//可以
var Slice =Array.prototype.slice;
for(var i = 0, i<max; i++){
   Slice.call(arr,/* 其他参数 */);
}

for in 操作

for in 遍历对象的所有属性时和查找对象的属性一样也会遍历整个原型链,任何可以通过原型链访问到并且可枚举的属性(enumerable)都会被枚举,因此原型链很长时,也会影响for in的性能。

in操作符

使用in操作符来检查属性是否在对象中存在时,也会查找对象的原型链(无论属性是否可枚举),因此也会影响in操作符的性能。

浏览器单线程机制

browser single thread

The browser UI thread is responsible for both UI updates and JavaScript execution, Only one can happen at a time.


everything in the UI of your browser, including the page layout and style engine, the JS engine, the garbage collector, etc — all of that is on a single-thread, which means at any given moment, only one of the tasks can be processing

from javascript-and-the-browser-ui. high-performance-javascript-capitoljs-2011)
浏览器的UI更新和js的执行共用同一个线程UI Thread,同一时间要么进行UI更新,要么执行js代码。他们通过共享同一个任务队列UI Queue来实现这一机制。
例子1.

<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
</div>
<button onclick="test();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">
function long_run(sep) {
    console.log('start');
    var s = +new Date();
    while (+new Date() - s < sep) {
      //
    }
    console.log('end');
}

function test() {
    var el = document.getElementById("foo");
    el.style.backgroundColor = "blue";
    // do some "long running" task, like sorting data
    long_run(3000);
    el.style.backgroundColor = "red";
}

在js代码执行的时候,UI更新被冻结:

  1. 表现为UI失去响应,例如点击没反应;
  2. handleClick函数里对dom的修改不会立即更新;
  3. 点击等其他事件触发的handle不会立即执行,被放入队列中依次执行,但是事件对应的UI update 被抛弃。

例子2.

<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
</div>
<button onclick="test();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">

function long_run(sep) {
    console.log('start');
    var s = +new Date();
    while (+new Date() - s < sep) {}
    console.log('end');
}

function test() {
    console.log('setTimeout');
    var el = document.getElementById("foo");
    el.style.backgroundColor = "blue";
    
    setTimeout(function() {
        el.style.backgroundColor = "red";
    }, 50);
    
    long_run(3000);
}

 </script>

Figure 1 - JavaScript UI Queue and UI Thread lanes depicted: timed code is intercalated taking turns

Figure 1 - JavaScript UI Queue and UI Thread

pic from

Script run time Limits of every browser

  • Internet Explorer: 5 million statements
  • Firefox: 10 seconds
  • Safari: 5 seconds
  • Chrome: Unknown, hooks into normal crash control mechanism
  • Opera: none

“0.1 second [100ms] is about the limit for having the user feel that the system is reacting instantaneously, meaning that no special feedback is necessary except to display the result.”
- Jakob Nielsen

推荐每个js片段执行最长时间限制为50ms。

定时器

因为浏览器的单线程机制,所以定时器真正执行的时间是不能保证的。
即setTimeout(fn, 100) 或 setInterval(fn, 100),是不能确保在调用完之后100ms就会执行fn,具体的执行时间取决于于当前任务队列中的任务和等待把fn放入任务队列期间是否有其他立即执行的事件加入到队列中。
timer.png
例子3.

<div id="foo">
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 
</div>
<button onclick="handleClick();">test</button>
<button onclick="alert(2);">test2</button>
<script type="text/javascript">
function long_run(sep) {
    console.log('start');
    var s = +new Date();
    while (+new Date() - s < sep) {
      //
    }
    console.log('end');
}
function handleClick() {
    console.log('handle click');
    long_run(11);
    //执行了11ms,从18ms到29ms结束
}
function handleTimeout(){
    console.log('setTimeout called');
    long_run(7);//执行了7ms,从29ms到36ms结束
}
function handleInterval(){
    console.log('setInterval call');
    long_run(5);//执行了5ms
}

function exec18ms(){
    long_run(2); // 先执行2ms
    // 2ms后执行一个10ms的定时器
    setTimeout(handleTimeout, 10);

    long_run(8); //执行8ms

   //t=10ms,启动一个10ms setInterval定时器,
   setInterval(handleInterval ,10);

   long_run(8); // 执行8ms
}

//t = 0ms,启动一个脚本,执行18ms,此时队列为空 
    exec18ms();//执行18ms+
    
//t=2ms,启动一个setTimeout定时器,,此时队列为空       
//t=8ms,发生了鼠标点击事件,此时队列为空,handleClick立即加入队列
//t=10ms,启动一个10ms setInterval定时器,此时队列有handleClick待执行
//t=12ms,Timer fires,Timer加入队列,此时队列有handleClick和Timer待执行
//t=18ms,之前的exec18ms执行完成,此时队列中有handleClick和Timer, 从队列中拿出handleClick进行执行,执行时间为11s,18-29ms,此时队列中有Timer
//t=20ms,10ms interval fires,10ms interval 加入队列,此时队列中有Timer和10ms interval
//t=29ms, handleClick 执行完成,队列中有Timer和10ms interval,拿出Timer进行执行,队列中有10ms interval。 //此时距启动Timer已经过去了27ms(29-2),延时了17ms(27-10)才执行了一个timeout为10ms的程序
//t=30ms,10ms interval fires again,但是此时队列已经存在同一个interval还没执行,所以本次interval 被dropped,队列仍旧中只有10ms interval
// t=36ms,Timer执行完成,从队列中拿出10ms inerval 执行,此时队列为空,此时距interval启动已经过去了26ms(36-10)延时了16ms(26-10)才执行了一个10ms的interval。
// t=40ms,10ms interval fires again,队列为空,立马把这个10ms interval放入队列
//t=41ms,10ms interval 执行完,队列中有一个10ms interval,从队列中拿出interval执行,此时距上一个interval执行完成间隔为0,此后队列为空
//t=46ms,10ms interval 执行完,队列为空,
//t=50ms,10ms interval fires,队列为空,马上加入队列,因为线程空闲,立即执行,距离上一个interval执行完成间隔4ms。
  
 </script>

Timer总结

  1. setTimeout(fn,delay) 和setInterval(fn2,delay) fn的执行时间间隔不一定是delay,可能比delay大;而setInterval的fn2执行的间隔则也不一定是delay,可能比delay大,可能比delay小,可能被drop掉。
  2. setInterval触发的时候,如果队列中有同一个interval触发的程序未执行会被drop掉,setTimeout不会有这个问题。

RAF(requestAnimationFrame)ie>=10,chrome>=27,ios7.1,Android4.4

browser support

function test() {
  	console.log('requestAnimationFrame');
    requestAnimationFrame(function() {
      var el = document.getElementById("foo");
      el.style.backgroundColor = "blue";

      requestAnimationFrame(function() {
        long_run(3000);
        el.style.backgroundColor = "red";
      });
    });

  }
//兼容性处理requestAnimationFrame,对不支持的浏览器用setTimeout来替换,时间间隔为一帧的时间
window.requestAnimFrame = (function() {
    return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
        window.setTimeout(callback, 1000 / 60);
    };
})();

利用 定时器/RAF 分片执行来解决需要长时间执行的js的代码

timed-array-processing-in-javascript

Worker

高级浏览器支持web worker,每一个web worker新起一个线程,通过加载的一个js文件来初始化,不受单线程执行的限制,但是,有很多限制:

  1. 不能访问DOM,自然不能修改dom,
  2. 不能访问 any global variables or JavaScript functions within the main page
  3. access to some objects, including the window, document, and parent, is restricted.
    web work.png

pic from

他们通过postMessage/onmessage 来进行发送和接收消息进行交互。
example

CSS3 animations use transform

对于动画效果我们推荐是css3,对于不支持的浏览器才降级使用js来实现。
虽然用css3来实现动画,但是对于浏览器的单线程机制,当js执行的时候,浏览器的UI被冻结,不能update,自然css的动画也被冻结了。好消息是有的浏览器moves the CSS animations off of the UI thread,这样js执行的时候,不会影响css动画的执行。 from
但是有一点这个动画必须animations use transform

支持的浏览器

All Safaris and Andriod Chrome
IE10,Chrome

例子

<head>
<style>
/*旋转的动画*/
.spin {
  -webkit-animation: 3s rotate linear infinite;
  animation: 3s rotate linear infinite;
  background: red;
}
@keyframes rotate {
  from {transform: rotate(0deg);}
  to {transform: rotate(360deg);}
}
@-webkit-keyframes rotate {
  from {-webkit-transform: rotate(0deg);}
  to {-webkit-transform: rotate(360deg);}
}


.walkabout-old-school {
  -webkit-animation: 3s slide-margin linear infinite;
  animation: 3s slide-margin linear infinite;
  background: blue;
}
/*位置移动的动画,注意这里是通过transform修改位置*/
@keyframes slide-transform {
  from {transform: translatex(0);}
  50% {transform: translatex(300px);}
  to {transform: translatex(0);}
}
@-webkit-keyframes slide-transform {
  from {-webkit-transform: translatex(0);}
  50% {-webkit-transform: translatex(300px);}
  to {-webkit-transform: translatex(0);}
}


.walkabout-new-school {
  -webkit-animation: 3s slide-transform linear infinite;
  animation: 3s slide-transform linear infinite;
  background: green;
}
/*位置移动的动画,通过margin-left来修改位置,这种方法仍然受单线程的影响*/
@keyframes slide-margin {
  from {margin-left: 0;}
  50% {margin-left: 100%;}
  to {margin-left: 0;}
} 

@-webkit-keyframes slide-margin {
  from {margin-left: 0;}
  50% {margin-left: 100%;}
  to {margin-left: 0;}
}

div {
  width: 30px;
  height: 30px;
}
</style>
<script>
function kill() {
  var start = +new Date;
  console.log("" + new Date());
  while (+new Date - start < 2000){}
  console.log("" + new Date());
}
</script>
</head>
<body>
  <p><button onclick="kill()">kill 'em all for 2 sec</button></p>
  <p><div class="spin"></div>
  <p><div class="walkabout-old-school"></div>
  <p><div class="walkabout-new-school"></div>
</body>

how to solve browser thread limit

  1. setTimeout/setInterval
  2. RAF
  3. web worker
  4. css3 animations use transform

further read

  1. how-javascript-timers-work
  2. timed-array-processing-in-javascript
  3. Introduction to HTML5 Web Workers: The JavaScript Multi-threadingApproach
  4. web worker
  5. RAF optimizing-visual-updates
  6. high-performance-javascript
  7. css-animations-off-the-ui-thread

NaN 导致的死循环

function swap (arr, n, m) {
    [arr[n], arr[m]] = [arr[m], arr[n]]
}

function partion (a, l, r, p) {
    swap(a, p, l)
    let i = l, j = r, pivot = a[ l ]
    while(i < j) {
        while(a[j] >= pivot && i < j) {
            j--
        }
        a[i] = a[j]
        while(a[i] <= pivot && i < j) {
            i++
        }
        a[j] = a[i]
    }
    a[i] = pivot

    return i
}
var arr =  [-1, -1, -1, -1, -1, -1, undefined, -1, -1, -1, -1]
var p = partion(arr,0,arr.length-1,0) // 执行完跟没执行一样,

根本原因是:

NaN >= -1 => false
NaN <= -1  => false
NaN == -1  => false
NaN == NaN => false

js作用域链

作用域链概念

在js中查找一个变量/函数的时候,js首先会在当前的执行的上下文去查找该变量是否存在,如果不存在,js会从当前执行的上下文的上一级作用域去查找,同理,如果在上一级作用域找不到,会继续往上层作用域去查找,直到找到顶层作用域,即global上下文,如果找不到,则会报错Uncaught ReferenceError: xxx is not defined

一些基本概念

  • js 代码的执行是在一个上下文中执行的,这个上下文决定了去哪里查找变量和函数的声明和赋值,我们称当前执行的上下文为活动上下文
  • 所有的执行上下文在”逻辑上”组成一个栈—ECStack
  • 堆栈底部永远都是全局上下文(global context),堆栈顶部是当前(活动的)执行上下文
  • 变量对象 (缩写为VO)是执行上下文的属性,是与执行上下文相关的对象,它存储下列内容:
    • 变量 (var, VariableDeclaration);
    • 函数声明 (FunctionDeclaration, 缩写为FD);
    • 函数的形参

例子

//在全局上下文执行函数
var a = 1;

function foo(x){
	var b = 2;
	function bar(){
		var a = 3;
		//some code
	}
	bar();
}
foo(100);

上述代码的解析过程:

// 初始状态
ECStack = [];
// 程序启动,初始化程序时,把全局上下文压入堆栈,例如加载外部的js文件或者本地的在<script></script>标签内的代码
ECStack = [
  globalContext
];

// 当进入foo函数内执行时,foo 函数上下文被压入堆栈
ECStack = [
  <foo> functionContext
  globalContext
];

// 当执行bar函数时,bar函数上下文被压入堆栈
ECStack = [
  <bar> functionContext
  <foo> functionContext
  globalContext
];

// 当bar函数执行完,退出后,执行上下文堆栈会弹出bar的函数上下文,变成
ECStack = [
  <foo> functionContext
  globalContext
];

// 当foo函数执行完,退出后,执行上下文堆栈会弹出foo的函数上下文,变成
ECStack = [
  globalContext
];

全局上下文

全局上下文中的变量对象就是全局对象global本身

VO(globalContext) === global;
// 全局对象是在进入任何执行上下文之前就已经创建的对象;这个对象只存在一份,它的属性在程序中任何地方都可以访问,全局对象的生命周期终止于程序退出那一刻。

//上文中的全局上下文的变量对象
VO(globalContext) = {
  a: 1,
  foo:function(){...}
};

函数上下文

在函数执行上下文中,VO是不能直接访问的,此时由活跃对象(activation object,缩写为AO)扮演VO的角色。

VO(functionContext) === AO;

活跃对象AO是在进入函数上下文时刻被创建的,它通过函数的arguments属性初始化.

// foo函数上下文的变量对象
VO(foo functionContext) = {
  x: 100,
  b: 2,
  bar:function(){...}
};

函数的[[scope]]属性

js函数有一个特殊的属性 [[Scope]],这是一个私有属性,只有JS引擎可以访问,FireFox的几个引擎(SpiderMonkey和Rhino)提供了__parent__来访问它,它有几个特性:

  • 它指向创建函数的上下文的变量对象(可能是全局对象,也可能是对应于一个函数的执行上下文的活跃对象);
  • 是在函数创建的时候保存起来的,指向函数创建时的上下文的变量对象,可以说是"该函数的父作用域;
  • 是“静态的”,一直存在,直到函数销毁,哪怕函数永远都不会被调用到。

通过该属性,我们可以窥探当前的执行上下文的作用域链的内部情况,例如上面的例子可以通过

bar.__parent__ === VO(foo functionContext)
bar.__parent__.__parent__ === VO(globalContext)

js作用域

在ES6之前,js基本上就不存在块级作用域(除了个别特殊的情况下),只存在函数作用域,即函数声明的两个花括号{}之间才能构成一个作用域,而其他if/for/while等语句的花括号{}之间并不能构成一个作用域。

词法作用域

js的函数作用域是词法作用域,即这个作用域里面可以访问的变量和函数,只跟函数定义的位置有关,函数定义的位置已经决定了这个函数作用域的作用域链,即使这个函数在别的地方调用,它的作用域链仍旧跟函数定义时是一样的,不随函数调用的位置变化而变化。举例说明

var a = 1;

function foo(){
    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

function task(fun){
    var a = 3;
    fun();
}

var fun = foo();
task(fun); // 2

变量提升

因为ES6之前不存在块级作用域,因此在一个函数体内不管在哪里(即使在函数体的尾部声明)声明的变量或函数,也不管是什么方式(在if/else/while/for里面)声明的变量和函数,他们都会被一视同仁当成在函数的开头声明一样,js编译时,自动把这些不是在函数开头声明的变量和函数提升到函数体的头部,就是变量的提升。举例说明:

function foo(){
    var a = 1;
    function b(){}
    console.log(a,b,c,d,e,f);

    if(false){
        var c = 2;
    }

    while(false){
        var d = 3;
    }

    function e(){}
    var f = 5;
}

foo();

上面代码中的变量c,d虽然声明在if和while里面,f声明在尾部,同样被提升到函数体的头部,注意,提升的只是函数的声明,而不会把赋值提升,即上面提升的只是

var c;
var d;
var f;
// 不会提升 c=2; d = 3; f = 5;

函数中的变量的解析顺序和规则

当进入函数执行上下文(代码执行之前)时,会创建一个新的AO(Activation Object)活动对象,这个对象中包含了this、参数(arguments)、局部变量(包括命名的参数)的定义。
AO初始化的过程是依次按照形参->函数声明->变量声明的顺序进行,具体如下:

  1. AO首先利用arguments属性初始化,
AO={
	this,
	arguments,
	key1:val1,
	…
}
// key: 形式参数的名称,
// value:如果没有对应传递实际参数,那么值就是undefined
  1. 然后把function里声明的函数添加到AO对象中:
AO={ 
    …, // 前面已经初始化的此处省略显示
    funname1: function-object1,
    …,
}

注意:

  • 变量对象中如果已经存在相同名称的属性,则完全替换这个属性;如果有同名的形参,函数对象覆盖形参的值;如果后面有同名的函数声明,后面的覆盖前面的;
  • 此处的函数声明是指通过function fun_name(){ …}这样的标准函数声明/定义,不包括:
// 变量声明,把一个匿名函数赋值给该变量
var fun_name = function(){…}
// 同上,只不过函数声明是具名而不是匿名
var fun_name = function fun_name(){…}
// 立即执行的函数表达式
(funciton(){…}()) 
(function())()
  1. 最后把function里声明的变量添加到AO对象中:
AO={ 
…, // 前面已经初始化的此处省略显示
var_name1: undefined,
…,
}

注意:

  • 这里“函数中声明的变量”指的是通过var进行声明的变量,var foo = a; ,不带var声明的变量 foo = a不属于这种情形;
  • AO的属性名是变量名称,值是undefined,注意初始化时所有变量的值都是undefined
  • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性, 即 if AO存在属性var_name(可能该属性对应的值是undefined),则该变量声明语句跟不存在一样,就是个摆设,也就是说,如果变量声明的变量名称和函数声明或者形参声明的名字一样,则不会覆盖同名的形参和函数。
  • if语句,循环语句中的变量声明同样如此处理。

作用域链变量查找过程

AO经过初始化后,大部分属性值可能是undefined。
当前函数的作用域链

scope = AO/VO+fun[[scope]]

AO位于scope chain的最前端, 并将func的[[scope]]属性所指向的对象(定义函数func时候的活动对象);

在代码执行过程中,当遇到一个标识符,就会根据标识符的名称,在执行上下文的作用域链中进行搜索。
从作用域链的第一个对象(该函数的AO对象)开始,如果没有找到,就搜索作用域链中的下一个对象(function.parent,如此往复,直到找到了标识符的定义。如果在搜索完作用域中的最后一个对象,也就是全局对象(Global Object)以后也没有找到,则会抛出一个错误,提示用户该变量未定义(ReferenceError: xxx is not defined)。

读一个变量的过程

从作用域链中变量a的值的过程:

AO 有没有a?Fail,go on
function.__parent__ 有没有 a?Fail? go on
function.__parent__.__parent__?有没有 a?Fail? go on
…
global object 有没有a?
没有?抛出一个错误,提示用户该变量未定义
有?      找到了

对一个变量赋值的过程

当对变量b赋值的时候,如果在作用域链中找到了b,则直接赋值给它;如果没有找到,譬如没有通过var声明的变量b,当对此变量赋值时
b = 1
`因为最终作用域链的最后会到达全局对象,如果在全局对象中没找到b,当对b赋值时,就直接给全局对象增加了一个b的属性,即
global.b = 1;
因为b不是通过var a = 1这样声明的,所以b不是当前函数作用域的变量,而是全局对象的属性,而变量有一个特性(attribute):{DontDelete},这个特性的含义就是不能通过delete操作符直接删除变量的属性。

// 当前作用域的变量a不能delete
delete a;//false
// 而全局对象下设置的变量b可以被delete
delete b;//true

可以改变作用域链的几种情况

with 和 try/catch

当有with或catch的时候,作用域链修改为:

	Scope = withObject|catchObject + AO|VO + [[Scope]]

在with/catch从句结束后,作用域链同样也会恢复到原先的状态,也即是说with和try/catch语句会在当前的作用域链的顶端插入withObject|catchObject ,举例说明

var a = 1;
function foo(){
    var a = 10;
    with({
        a: 100
    }){
        console.log("a is:" + a);
        a = 1000;
    }
   
    console.log('foo a:',a);
}

foo();
console.log('global a: ',a);

// 结果
// a is:100
//  foo a: 10
// global a:  1

new Function

当函数通过new Function构造器创建的时候,新创建的函数[[Scope]]属性永远都只包含全局对象,举例:

var a = 1;
function foo(){
    var a = 10;
    var bar = new Function('', 'console.log("a is:" + a); a = 100');
    bar();
    console.log('foo a:',a);
}

foo();
console.log('global a: ',a)
// 结果
// a is:1
// foo a: 10
// global a:  100

eval

eval上下文的作用域链和调用eval的上下文有相同的作用域链,举例:

var a = 1;
function foo(){
    var a = 10;
    eval('function bar(){console.log("a is: " + a); a = 100;}')
    bar();
    console.log('foo a:',a);
}

foo();
console.log('global a: ',a);

// 结果是:
//  a is: 10
// foo a: 100
// global a:  1

作用域链的影响

闭包

因为js的函数作用域是词法作用域,它不随函数的调用位置和次数而改变,如果函数内部对上存在对上层作用域的变量的引用,即使函数调用结束也不会释放函数定义位置的上层作用域的变量,就形成了所谓的“闭包”。有时候可以利用闭包,例如对ul下面的多个li元素绑定事件,当每一个li被点击时,希望alert对应的里的序号,如果这样写,

var arrLi;
for(var i = 0, len=arrLi.length;  i<len;  i++){
    arrLi[i].onclick = function(){
        alert(i); // 所有li的click handler引用的上层作用域同一个变量i,当循环结束,i的值变成len,而不是arrLi[i]对应的序号i
    }
}

// 正确版本,需要给每一个li的click handler维护一个闭包,这样当循环结束,这个i值仍旧保持不变
for(var i = 0, len=arrLi.length;  i<len;  i++){
 arrLi[i].onclick  = (function(i){
        // 因为click handler存在对变量i的引用,i作为立即执行的匿名函数的形参而保留,形成一个闭包
        return function(){
            alert(i); 
       }
    })(i);
}

作用域链对变量读取和设置的性能影响

变量查找过程就是从当前上下文的作用域链最底层上下文(AO|VO)一直到最上层上下文(全局对象)进行变量名字作为属性查找的过程。在查询过程中,当前AO中的局部变量相比较上层上下文的变量会优先被查询到,换句话说,如果两个相同名字的变量存在于不同的上下文中时,处于底层上下文的变量会优先被找到。因此,提倡把多次用到的变量在函数内暂存一份,提升变量查找的性能:

function foo(){
  var item = document.getElementById('xxx'),
  slice= Array.prototype.slice,
  ua = window.navigator.userAgent,
 …
}  

响应式布局技术方案调研


响应式布局?

个人理解:

  • 同一份html代码,根据屏幕的大小,选择_合适_的方式把内容展现给用户,在小屏幕下有可能只显示核心的内容,隐藏不重要的内容区域.其中_合适_的方式,一般取决于设计师的特意设计;一般响应式布局不同于移动端的自适应布局,不会随屏幕变化时时变化,而是根据设计师设计的break point,在这个点发生变化。
  • 同一个内容区块在不同大小屏幕下展示的位置会发生变化,但是区块本身的宽度一般不变化,图片的大小也一般不变化,做的精细的会根据屏幕的dpi加载不同分辨率的图片,例如2x图,1.5x图等。
  • 有的响应式在不同大小的屏幕展示的字体大小也会变化。
  • 采用什么样的变化策略,以及屏幕尺寸的break point取决于需求方,没有固定的策略。

实现方案

Media Query 方案

响应式实现方案一般是采用Media Query方案,满足某种屏幕宽度时设置某些元素的css属性值来改变布局。例如

/** iPhone **/
@media only screen and (min-width: 320px) and (max-width: 767px) {}

/** iPad **/
@media only screen and (min-width: 768px) and (max-width: 1024px) {}

Media Query 方案也是最流行的响应式布局方案,优缺点如下:

优点:

  • 大部分浏览器原生支持,使用方便;
  • 浏览器自动随着窗口变化使用相适应的css;
  • 当使用sass来写样式时,可以把同一个区块的样式和其相关的Media Query 条件下的样式都写一块,便于维护,例如:
 body {
     background: navy;
     @media #{$phone} {
         background: yellow;
     }

     @media #{$pad} {
         background: green;
     }
     /* ... */
 }

 //编译后
 body{
     background: navy;
 }
 @media only screen and (min-width: 320px) and (max-width: 767px) {
     body{
         background: yellow;
     }
 }

 @media only screen and (min-width: 768px) and (max-width: 1024px) {
     body{
         background: green;
     }
 }

 /* ... */

缺点

  • ie8 不支持Media Query,不过可以通过一个ployfill的Respond.js来fix,但是这个js的原理是把所有css都通过一个函数进行转换,如果是外联的css需要异步请求这个css,如果你的css是放在CDN上,异步请求时存在跨域问题,这个ployfill提供了一种iframe通过window.name的方式来进行跨域请求的方案,但是需要一个proxy的html放在跟css放在同域名下才可以,我们这目前不能上传html到css的CDN上;我改了一个版本Respond.js,因为我们CDN都做了CORS的响应头,高级的浏览器直接通过ajax可以跨域请求,ie8,ie9只能通过XDomainRequest来进行跨域请求。
  • 样式可以通过Media Query的方法来实现,但是不同尺寸屏幕下的js交互逻辑也是需要跟屏幕尺寸匹配起来。例如不同屏幕下执行的js逻辑不一样,这个js逻辑同时要跟展现的dom要匹配起来。
    目前Window.matchMedia()支持通过js方法查询当前窗口的宽度,然后执行响应的js逻辑,例如:
if (window.matchMedia("(min-width: 400px)").matches) {
  /* the viewport is at least 400 pixels wide */
} else {
  /* the viewport is less than 400 pixels wide */
}

但是IE 10以下不支持,matchMedia.js提供了ployfill可以使得IE 9 及以下 的浏览器支持window.matchMedia,但是IE8不支持。
同时为了方便的使用js来查询Media Query和添加响应的逻辑,引入另外一个enquire.js,前提是添加了matchMedia.js ployfill,使用方法:

enquire.register("screen and (max-width:1000px)", {

    match : function() {},      // OPTIONAL
                                // If supplied, triggered when the media query transitions
                                // *from an unmatched to a matched state*

    unmatch : function() {},    // OPTIONAL
                                // If supplied, triggered when the media query transitions
                                // *from a matched state to an unmatched state*.
                                // Also may be called when handler is unregistered (if destroy is not available)

    setup : function() {},      // OPTIONAL
                                // If supplied, triggered once immediately upon registration of the handler

    destroy : function() {},    // OPTIONAL
                                // If supplied, triggered when handler is unregistered. Place cleanup code here

    deferSetup : true           // OPTIONAL, defaults to false
                                // If set to true, defers execution the setup function
                                // until the media query is first matched. still triggered just once
});

自己写js实现

在页面的head里添加js代码,判断屏幕的宽度,根据屏幕宽度来给dom根元素设置相对应的样式例如'small','large'等,同时在window下设置一个全局变量标识当前是哪一种类型,例如window.size=small;,然后在css中通过不同的前缀选择器设置某些元素的css属性值来改变布局。例如

//head中添加判断js
(function(doc, win) {
    var w, clsName, response = {
        size: void 0,
        width: void 0,
        calWinSize: void 0,
        arrCallback: []
    };
    win.response = response;

    var root = doc.documentElement,
        db = doc.body;

    //w3c: window.innerWidth
    //ie quirks mode: document.body.clientWidth
    //ie stantdard mode: document.documentElement.clientWidth

    function calWinSize() {
        //console.info('calWinSize ...')
        w = win.innerWidth || (root && root.clientWidth) || (db && db.clientWidth);
        clsName = 'normal';
        if (w <= 768) {
            clsName = 'small';
        } else if (w > 1600) {
            clsName = 'large';
        }
        if (response.clsName !== clsName) {
            var oldClsName = response.clsName;
            // 标记CSS
            root.className = clsName;
            // 标记JS
            response.clsName = clsName;
            //exec callback
            for (var i = 0, len = response.arrCallback.length; i < len; i++) {
                response.arrCallback[i](oldClsName, clsName);
            }
        }

        response.width = w;
        console.info(w, clsName)
    }
    response.calWinSize = calWinSize;


    var addEvent = (function() {
        if (window.addEventListener) {
            return function(element, eventName, callback) {
                element.addEventListener(eventName, callback, false);
            }
        } else {
            return function(element, eventName, callback) {
                element.attachEvent('on' + eventName, callback);
            }
        }
    })();

    //calculate window size
    calWinSize();
    addEvent(window, 'resize', calWinSize);

})(document, window);
/** small **/
.small body {
    background: yellow;
}

/** normal **/
.normal body {
    background: green;
}

/* large */
.large body{
    background: navy;
}

优点

  • 不存在浏览器兼容性,不需要额外的ployfill
  • css和js的逻辑保持一致,处理方便;

缺点

  • 当使用sass来写样式时,同一个区块的样式在不同前缀下的定义是分割开来的,导致维护稍有不便;
  • 浏览器渲染时需要先执行这段js,但对性能影响不大;

响应式布局下区块呈现的方法

有多种方式可以实现在保持dom元素不变,改变css的设置来变换元素的布局。

dom结构

<div class="wrapper a">
    <div class="a1">
        御林军VS延边队鹿死谁手?
    </div>
    <div class="a2">
        御林军VS延边队鹿死谁手?
    </div>
    <div class="a3">
        御林军VS延边队鹿死谁手?
    </div>
</div>

Float

.a {
    @extend %clearfix;
    .a1, .a2, .a3{
        float: left;
        padding-left: 1em;
        margin-bottom: 10px;
    }



     @include mq-phone {
         .a1, .a2, .a3 {

             float:none;
             width: 100%;
         }
     }

     @include mq-pad {
         .a1 {
             width: 100%;
         }
         .a2, .a3{
             width: 50%;
         }
     }
}

总结:float对于这种自适应布局处理起来比较简单方便。

Display:table-cell

Display:table-cell被ie8支持,可以应用在很多地方。

.b {
    display: table;
    width: 100%;

    .a1, .a2, .a3{
        display: table-cell;
        padding-left: 1em;
        margin-bottom: 10px;
    }

     @include mq-phone {
         .a1, .a2, .a3 {
             width: 100%;
             display: block;
         }
     }

     @include mq-pad {
         .a1 {
             display: table-caption;
             width: 100%;
         }
         .a2, .a3{
             width: 50%;
         }
     }
}

总结:这种方法适合所有都展示在一行,容器需要设置为display: table;如果需要把某个元素独占一行,需要设置为display: table-caption;
但是只能在任意位置独占一行,只能在所有元素的上面独占一行,有一定的局限性。

display:inline-block

.d {
    font-size: 0;

    .a1, .a2, .a3{
        display: inline-block;
        padding-left: 1em;
        margin-bottom: 10px;
        font-size: 16px;
        vertical-align: top;
    }

     @include mq-phone {
         .a1, .a2, .a3 {
             width: 100%;
         }
     }

     @include mq-pad {
         .a1 {
             width: 100%;
         }
         .a2, .a3{
             width: 50%;
         }
     }
}

总结:这种方法跟float在很多地方相似,处理也比较方便,唯一需要处理的是因为inline-block元素之间的间隙(父容器设置font-size:0,ie8以上和其他高级浏览器支持),还有inline-block元素之间的垂直方向的对齐。

postion:absolute

.c {
    position: relative;
    width: 100%;

    .a1, .a2, .a3, .a4 {
        padding-left: 1em;
        margin-bottom: 10px;
    }

     @include mq-phone {
         height: auto;
         .a1, .a2, .a3, .a4{
             position: static;
             width: 100%;
             display: block;
         }
     }

     @include mq-pad {
         .a1, .a4{
            position: static;
            width: 100%;
         }
         .a2, .a3{
             width: 50%;
         }
         .a2{
             margin-left: 0;
             margin-right: 50%;
         }
         .a3{
             position: absolute;
             top:143.33px;
             right: 0;
         }
     }

}

总结:这种方法如果只有独占一行或者并排显示这两种逻辑还可以,如果要实现个别独占一行,有的又想并排显示处理特别麻烦,不推荐使用。

响应式布局的窗口break-point

  • phone: < 768px
  • pad: 768px
  • 一般的笔记本:
  • 大屏幕显示器:
  • 超大屏幕显示器:
  • ...

建议

  • 写样式时尽量让每一个区块能随父容器自适应:不要写死子容器和子元素的属性值,例如,只在父容器统一控制尺寸,子元素不要写死宽度和高度等,而是继承父容器的属性;尽量采用能够自适应的方案,例如绝对定位在某些情况下就不能自适应;
  • 尽量把多屏幕下共同的属性提取出来, 不同屏幕下的样式定义里只写这个屏幕下特有的样式,便于修改;
  • 尽量把同一个区块不同屏幕的样式写到一块,方便查找和维护;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.