winniebear / bearblog Goto Github PK
View Code? Open in Web Editor NEWthe bog of WinnieBear ...
the bog of WinnieBear ...
页面请求过程优化主要是减少获取页面资源的时间,主要通过几个方面:
当浏览器请求资源时,需要通过发送网络请求来获取资源,网络请求是比较耗费时间的,如果浏览器减少网络请求的次数,甚至不发送网络请求就直接拿到需要的资源,就可以大大减少资源加载的时间,浏览器的本地缓存机制可以满足这一点。
浏览器发送请求时,会根据url查找本地缓存,看是否本地缓存中存在该url的内容,如果不存在,则向服务器请求该url的内容;如果本地存在缓存,则按照如下策略:
优化点:
- 给静态资源设置适当的缓存时间 expire:xxx /cache-control:max-age=xxx,对更新不频繁的资源尽量利用本地缓存来减少网络请求的时间;
- 尽量利用条件查询请求,对资源返回的http头设置Last-Modified/Etag,当内容过期时,进行条件查询请求,尽量利用本地缓存的资源;
关于本地缓存机制,进一步阅读
优化点
- 高级浏览器一般都支持本地存储,可以把需要网络请求的资源存放再浏览器的LocalStorage,需要资源时从本地存储获取,首次加载时通过服务端内联的方式返回,减少资源的网络请求;
在PC端或者强网络环境下,因为浏览器在同一个域名下的并发请求数有限制(4-6个url),为了能够加快静态资源的请求,可以
- 把静态资源分发到多个域名,让浏览器同时建立多个连接,并发进行多个静态资源的请求
注意:这种方法只适用于强网络环境,譬如移动端弱网络环境下,建立链接的耗费很多时间时,此方法不适用
浏览器向服务器请求资源,是通过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网络环境下,平均值在几百毫秒左右;
- 在网络很差的移动环境下,平均值达到几秒左右
优化点:
- 让浏览器提前查询dns: dns-prefetch
<link rel="dns-prefetch" href="xxx.com" >
- 在弱网络环境下减少页面中的资源的域名的个数,或者url中直接ip而不是域名
浏览器根据DNS查询得到ip+url中的端口跟服务器建立TCP连接。
因为用户和服务器之间的距离(不仅是物理距离,还有网络链路距离,以及跨运营商中转等)不同,建立连接的时间不同。
优化点:
- 采用CDN,让用户与它最近的服务器建立连接
因为HTTP协议是无连接的,每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。每一个请求都要建立TCP连接,都要进行3次握手,建连成本大;
优化点:
- 减少建立连接的次数:merge CSS/js/image,合并资源,减少请求的资源的个数
- 减少建立连接的次数:remove empty src/href/URL
- 减少建立连接的次数:keep-alive, 若connection 模式为close,则服务器处理完一个请求之后会主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则当服务器处理完一个请求之后,该连接会保持一段时间,在该时间内可以继续接收请求;
当浏览器发送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
进一步阅读
服务器接到请求后,根据请求的url和数据等,经过一系列处理,最后返回一个大字符串就是网页的内容给浏览器。一般服务器处理一个动态请求的工作过程大概分几个部分:
浏览器是边下载边解析,因此优化的方法是尽量减少浏览器的首包等待时间TTFB (TIME TO FIRST BYTE) ,
优化点:
- 服务端优化,通过各级缓存(页面缓存/数据缓存/数据库缓存/模板缓存/)及服务器多IDC部署等方法,减少服务端的数据处理时间;
- 采用bigPipe方案,flush buffer early 不必等所有数据都拿到再拼页面,有一点就输出,早点刷新缓冲区,减少浏览器的首包时间TTFB (TIME TO FIRST BYTE) ,让
浏览器尽早的进行解析和渲染。
传统页面展现
bigpipe页面展现
再服务器端配置Last-Modified 和Etag,和设置缓存时间,让浏览器端尽量利用本地缓存;当缓存过期时,如果请求的资源没有修改,通过304响应,返回很少的响应头而不用返回全部的页面内容到浏览器。
- 304 Not Modified 浏览器还是利用老的缓存数据
- 200 Ok 新的内容
返回的数据越少,传输的越快
优化点:
- 按需加载和返回页面需要的资源,优先返回首屏依赖的资源,剩余的资源按需加载
- 资源懒加载,例如图片,依赖的模块等,滚动加载剩余
- 异步刷新,不需要返回全部内容,只返回变化的部分,ajax等
- 返回的内容进行压缩HTML/CSS/js/img compres gzip
优化点:
- 按需加载和返回首屏需要的资源,服务端一开始只返回首屏依赖的资源,剩余的资源按需加载
- 首屏依赖的css和js除了类库,进行页面内联
- 页面的小图标进行base64编码之后,页面内联
- 首屏最好不要放js资源,js资源都放在首屏的下面进行加载
通过CDN服务,把资源部署到更接近用户的物理机房,
- 减少浏览器建立连接的时间
- 减少数据传输的链路长度
- 同时CDN会对数据进行缓存
- CDN会根据用户所在的运营商,在DNS解析时返回访问最快的机房IP
通过CDN服务,可以加快浏览器的获取数据的时间
针对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 的三个主要处理步骤分别是:
解析(parse),转换(transform),生成(generate)。
Babel7 不再建议使用stage-x presets,而是根据你配置的目标环境的兼容情况来加载相应的plugins
各 stage 的插件
Babel7 配置的语法也发生变化,包的名字和插件名字都变化了
{
"presets": [//数组
"presetA", // 字符串
["presetA"], // 数组包裹的字符串
["presetA", {}]// 数组,第一项是字符串,第二项是个对象,对象里面配置各种属性
],
"plugins": [
"plugin-A",
["plugin-A"],
["plugin-A", {}]
]
}
// 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"
]
}
如果没有配置presets和plugins,babel是如何选择哪些插件进行处理?
如果没有配置presets,babel什么也不转换
babel推荐使用preset-env,属性说明
preset-env 根据配置的targets属性,就是目标浏览器,加载需要的插件;如果没配置targes属性,默认会加载ES2015,ES2016,ES2017 需要的插件;
即使配置了targets属性,babel不会根据你代码中用到了哪些新语法而加载相应的插件,而是根据你的preset配置来加载需要的插件;如果没有配置targets属性,但是package.json配置了browserslist,会把这个属性作为targets来使用。
需要在入口文件中直接添加对 @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
根据文件中使用情况,添加相应的polyfill
Babel7 如果不设置useBuiltIns属性,babel转换时不会自动插入需要的polyfill,例如promise等,因此需要自己在代码中自行引入。
当运行环境中不支持一些方法时,babel-polyfill 会给其做兼容。 但是这样做也有一个缺点,就是会污染全局变量,而且项目打包以后体积会增大很多,当不使用useBuiltIns: 'usage'属性时,会把整个依赖包也打了进去。所以并不推荐在一些方法类库中去使用。
基于以上,一方面为了开发一个lib时不污染全局变量,另一方面为了减少由于辅助代码带来的冗余代码,有了Babel-runtime
npm install --save @babel/runtime
npm install --save-dev @babel/plugin-transform-runtime
注意: babel-runtime 不会转码实例方法,比如这样的代码,
'!!!'.repeat(3);
'hello'.includes('h');
只会通过添加相应的polyfill来处理
我们可能只知道写一些脚本,然后一张好看的页面就出现在了屏幕上。但是浏览器究竟是怎样将我们的HTML,CSS和JavaScript脚本渲染成屏幕上的像素的呢?
从浏览器接收到HTML,CSS和JavaScript脚本,到将它们渲染为像素点的过程中有许多中间步骤。要优化性能,就要了解这些步骤,也就是所谓的关键渲染路径(Critical Rendering Path)。
浏览器渲染大致需要如下的步骤:
这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
CSS中“visibility:hidden”和“display:none”是不同的。前者将元素隐藏起来,但是隐藏元素仍然会在页面最
终的布局中占据相应的空间(其实就是一块空白)。然而后者会直接将元素从渲染树中删除,不仅不可见,也不属于最终布局的一部分。
最终的渲染树既包含了所有可视的内容,又包含了相应的样式信息。快要大功告成了!有了这棵渲染树,我们就能进入下一步,布局。
在此之前,我们已经计算了什么节点是可视的以及它们对应的样式是什么,但是我们还没有计算它们在当前设备中准确的位置和尺寸。这正是布局阶段要做的的工作,该阶段在英语中也被称为“回流”(reflow).
布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
在前部分中我们看到了关键的渲染途径需要同时具备DOM(Document Object Mode,文件对象模型)和CSSOM(CSS Object Model,CSS对象模型)来构造渲染树,这证明了一个重要的性能上的可能的结果:HTML和CSS都是阻止渲染的资源。
默认情况下,CSS会被当做渲染中的阻塞性资源,也就是说浏览器即使已经处理好了某些页面内容,只要CSSOM没有构建好,便不会进行渲染。浏览器会在同时具备了DOM和CSSOM时才渲染界面。
JavaScript让我们能够修改页面的每个方面:内容、样式和用户交互行为。然而,JavaScript也能阻止DOM的构建,并延迟页面的渲染。确保你的JavaScript是异步的,并且从关键渲染路径中消除任何不必要的JavaScript,以提供最佳性能。
脚本是在它被插入到文本中的位置处执行的。当HTML解析器遇到一个脚本标签时,它会暂停对构建DOM的处理,并让出控制权给JavaScript引擎。一旦JavaScript引擎完成了运行,浏览器再从它离开的地方重新开始,并继续进行DOM构建。
执行内联脚本会阻止DOM的构建,这也会延迟初始化渲染。
假如浏览器在我们运行脚本时还没有完成CSSOM的下载和创建会怎么样?答案很简单,并且对于性能不会很好:浏览器会延迟脚本的执行,直到它完成了CSSOM的下载和构建,当我们在等待时,DOM的构建也被阻止了!
脚本在文本中的位置是很关键的
当遇到script标签时DOM的构建会被暂停,直到脚本完成了执行。
JavaScript可以查询和修改DOM和CSSDOM
直到CSSOM准备好了,JavaScript才会执行
在使用外部的JavaScript文件的情况下,浏览器将不得不暂停来等待从磁盘、缓存或远程服务器上获取这个脚本,这会给关键路径的渲染增加数十到数千毫秒的延迟。
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计算出来的样式。
描述了与浏览器进行交互的方法和接口,可以对浏览器窗口进行访问和操作,譬如可以弹出新的窗口,改变状态栏中的文本,对Cookie的支持,IE还扩展了BOM,加入了ActiveXObject类,可以通过js脚本实例化ActiveX对象等等)。
Window对象:
是整个BOM的核心,所有对象和集合都以某种方式回接到window对象。
Document对象:
实际上是window对象的属性。这个对象的独特之处是唯一一个既属于BOM又属于DOM的对象。从BOM角度看,document对象由一系列集合构成,这些集合可以访问文档的各个部分。
Location对象:
它是window对象和document对象的属性。Location对象表示载入窗口的URL,此外它还可以解析URI.
Navigator对象:
Navigator包含大量Web浏览器相关的信息。各种浏览器支持该对象的属性和方法不尽相同。
Screen对象:
通过其可以获取用户屏幕相关的信息.
全局的方法和属性
(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);
浏览器本身支持,与获取文档相关的协议
第三方应用或插件支持的协议
<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代码,并且继承了调用的当前域。有些情况会对后面的内容处理两次,如果代码正确的话,会把后面的代码当成html解析,覆盖掉原来的html代码,所以很多javascript:void+ 一段代码:
<iframe src='javascript:"hello"'> </iframe>
<!-- 仅一行代码,打造一个在线编辑器 -->
data:text/html, <html contenteditable>
<!-- 图片采用datauri协议,减少请求 -->
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEBLAEsAAD...">
about:blank这个URL可以用来被创建DOM对象,例如:
<iframe src="about:blank" name="test"></iframe>
<script>
frames["test"].document.body.innerHTML = "<h1>Hi!</h1>";
</script>
在浏览器中,创建一个about:blank页面,它继承的域为创建它的页面的域。例如,点击一个链接,提交一个表单,创建一个新窗口,但是当用户手动输入about:或者书签中打开的话,他的域是一个特殊的域,任何其他的页面都不可以访问。
查看源代码协议
浏览器通过隔离来自不同源的文件之间的访问来提供各种安全保障。
同源策略一开始是为了管理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/ | 端口不匹配 | 可以访问 |
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请求严格遵守同源策略,非同源不可以请求。
跨域请求解决方案
http响应头:
HTTP/1.1 200 OK
Access-Control-Allow-Origin:http://another-domain.com
iframe
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
数据在网络上转发
服务器端处理请求并返回结果
浏览器接收和解析返回的内容
接收内容
解析内容和绘制
计算每个Element的位置,布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
通过调用操作系统Native GUI的API绘制,将渲染树转化为屏幕上的每个像素点
浏览器的UI更新和js的执行共用同一个线程UI Thread,同一时间要么进行UI更新,要么执行js代码。他们通过共享同一个任务队列UI Queue来实现这一机制。
对浏览器解析和渲染页面的优化,主要通过以下几个方面:
首先需要了解浏览器的解析和渲染的过程
主要分以下几个步骤:
根据http响应返回的数据根据页面编码进行解码,而浏览器判断编码主要是依据以下方法:
解析返回的内容,一个字符一个字符解析,边下载边解析
优化点:
- 通过HTTP头信息或meta标签尽早指定正确的字符编码,省去浏览器判断字符编码的时间
解析过程遇到css,js等资源,就会发起请求,下载css,js,..,但是浏览器同一个域名并行下载个数有限制
优化点:
- js/css 多域名存放,增加并行下载的个数,注意与cdn查询成本从和浏览器处理性能之间的平衡
进一步阅读
字节(Bytes) -> 字符(characters) -> 标记(tokens) -> 节点(nodes) -> 对象模型
优化点:
- 去除HTML/CSS/js/中的空白字符,省去浏览器解析空格的时间
- 删除无用的html标签,精简html结构,尽量减少不必要的dom的深度
进一步阅读
提高CSS解析的效率,主要通过几个方面:
- 避免通配符选择器
- 删除多余的修饰语
- 避免使用后代选择器,特别是那些指定多余祖先的
- 使用class选择器代替后代选择器
- 在IE中避免对非链接元素应用:hover
- 避免CSS expressions
进一步阅读
浏览器只有同时具备DOM(Document Object Mode,文档对象模型)和CSSOM(CSS Object Model,CSS对象模型)才能来构造渲染树。
为了创建渲染树,浏览器大致需要如下的步骤:
谷歌 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)。布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
最后阶段,已经知道了哪些节点是可视的、它们的样式和它们的几何外观,我们终于能够将这些信息渲染为屏幕上每个真实的像素点了。这个阶段称为“绘制”,或者“格栅化”(rasterizing)。一旦布局阶段完成,浏览器便开始绘制阶段,将渲染树转化为屏幕上的每个像素点
通过调用操作系统Native GUI的API绘制每一个像素点,更新显存,给显示器发送信号,显示器根据得到的信号进行显示。
如果浏览器刷新频率为60hz,那么定时器的时间小于1/60s(16.666..ms)就没有意义。
优化点:
- 设置适当的定时时间,小于1/60是没意义
- 使用requestAnimationFrame(rAF)来代替定时器动画
- 利用GPU加速
进一步阅读
Accelerated Rendering in Chrome
Jank Busting for Better Rendering Performance
Leaner, Meaner, Faster Animations with requestAnimationFrame
GPU Accelerated Compositing in Chrome
以下这些会引起reflow & repaint:
改变文字大小
应用新的样式或者修改任何影响元素外观的属性,修改class/style
激活伪类,如:hover
DOM元素的添加、修改(内容)、删除( Reflow + Repaint),添加新的页面内容,例如输入框输入文字等
读取元素的某些属性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE))
当一个元素的外观被改变,但没有改变布局的情况下发生,如改变visibility、outline、前景色,scroll等,会引起repain
Resize浏览器窗口、滚动页面,
没有指定的图片尺寸,或者如果指定的尺寸不符合图片的实际尺寸,一旦图片下载,浏览器将需要reflows和重新绘制页面
在页面中间通过style或外链加载样式,也会导致reflow
不指定页面的编码,浏览器开始渲染页面之后发现指定的编码设定与其设定或默认值不同,都会导致重新解析文档并重绘页面。如果编码的变化影响到了外部资源(例如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
在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比较小,那么这个元素(不管是不是应用了硬件加速样式)也会被放到复合层中。最可怕的是,浏览器有可能给复合层之后的所有相对或绝对定位的元素都创建一个复合层来渲染,
事件是javascript和HTML交互基础, 任何文档或者浏览器窗口发生的交互, 都要通过绑定事件进行交互;
事件响应是异步处理的, 当某个事件触发时,不一定马上会得到响应,而是异步处理的,响应的时间是不一定的。
<!-- 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>
选项 | 标准事件模型 | 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 | 无 |
单个元素添加多个事件处理程序,执行的顺序 | 执行顺序按照它们添加的顺序执行 | 执行顺序按照添加的顺序相反的顺序执行 |
支持冒泡和捕获
绑定方法
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;
}
}
};
解绑方法
事件的名称参数
事件hanlde中对事件对象的获取
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
阻止冒泡的方法
function handle(event){
var event = event || window.event;
if(event.stopPropagation){
event.stopPropagation();
}else
{
event.cancelBubble= true;
}
//...
}
阻止默认行为的方法
function handle(event){
var event = event || window.event;
if(event.preventDefault){
event.preventDefault();
}else{
event.returnValue = false;
}
//...
}
事件target
function handle(event){
var event = event || window.event;
var target = event.target || event.srcElement;
//...
}
事件的relatedTarget
function handle(event){
var event = event || window.event;
var relatedTarget = event.relatedTarget || event.fromElement/*toElement*/;
//...
}
因为事件有冒泡机制,所有子节点的事件都会顺着父级节点跑回去,所以我们可以通过监听父级节点来实现监听子节点的功能,这就是事件代理。
使用事件代理主要有两个优势:
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和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 |
一个典型的鼠标拖动操作,依次触发的事件依次是
如果中间鼠标移动,将会触发多个mousemove事件。
mousedown/mouseup不一定都会触发
当按下鼠标进入目标或按下鼠标移出目标区域
mouseover和mouseout的冒泡问题
适当的用mouseenter和mouseleave替代mouseover和mouseout
//绑定
$(ele).on( events [, selector ] [, data ], handler );
//触发事件
$(elementA).trigger( events );
在js中this关键字永远都指向当前正在调用该函数(方法)的所有者。
在js中的this是运行时绑定的,不是编写时绑定,跟函数的作用域链只与函数的编写的位置有关是完全相反的,this指代的对象是函数执行时的context,跟函数的声明位置没有任何关系,而这个context随函数调用的方法的不同而不同。
例如:
var o1 = {
name:'jack',
getName:function () {
console.log(this.name); // this 指的是o1这个对象
}
};
o1.getName();
例如:
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();
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);
例如:
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);
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
});
例如:
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种情况,例如
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
*/
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对象
<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>
$('.classname').each(function(){
console.log(this);// #<HTMLXXXElement>,not jquery object
var $this = $(this);
});
$('.classname').click(function(){
console.log(this);//event #<HTMLXXXElement>,not jquery object
var $this = $(this);
});
$.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参数
}
});
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参数
}
})
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的值的不同
*/
例如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
性能优化,是一个很笼统的概念,抛开具体的时代背景和应用场景谈性能优化,都是耍流氓。
理论上来说,我们指定的技术方案,编写的代码都应该是质量完备、性能良好,运行的又快又好的,但是有时候因为人员的素质问题,项目的紧急问题等原因导致没有时间和人力等各种条件做一个完美的方案,而且过早的优化是万恶之源。但是一旦有时间和人力,就应该时刻准备着进行优化。
性能优化不能是眉毛胡子一把抓,而应该是逐步有序的进行,先抓主要的问题,再处理次要的问题,才能快速见效,因此性能优化之前首先要找到性能的瓶颈在哪里,然后有针对的进行优化。
同时性能优化需要具体问题具体分析,要针对具体的应用场景和上下文来进行优化。随着硬件和网速以及设备的升级,10年前的优化方法有可能现在已经不再适用;针对pc端有效的优化措施,拿到移动端有的也不再适用;别的大厂的优化方法和措施拿到自己的公司也不一定能贯彻和执行的起来,因此一切的优化措施都要因地制宜,有针对的进行实施。
虽然过去总结的很多优化措施现在有可能是不适合的,但是把web加载过程的各个环节进行分析,审视每一个步骤和环节的时间和资源成本,通过各种方法降低web加载过程每一个环节需要的时间,这些方法就是性能优化的方法,雅虎的前端优化的14条军规就是这些优化方法的子集而已。因此性能优化不是背背别人总结的要点,性能优化的方法也不是古老的神秘秘诀,是每一个人都可以总结和发现的。
本地缓存查找
向服务器发出请求url
数据在网络上转发
服务器端处理请求并返回结果
浏览器接收内容
页面加载之后,用户通过鼠标,键盘,手touch等操作与浏览器发生交互,交互过程浏览器可能会重新进行布局和重绘,同时交互过程可能会涉及到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+)
__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
上述例子的原型关系图如下:
当访问一个对象的属性或方法时,先会查找对象本身是否存在该属性或方法,如果不存在,然后查找对象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
;否则如果中间某个对象上找到了一个属性,就返回该属性值。
当读取一个对象的属性时,会涉及到原型链的查找,如果是对一个对象设置一个属性呢?会影响原型链上同名的值吗?
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
如果你给一个属性赋值,你通常只能修改对象自身的属性值;
如果自身属性已经存在了,则改变这个属性的值,
否则,如果自身还没有这个属性,大部分情况会在对象上新建该属性,但不是一定,原型链上的已经定义的同名属性会有一定的影响,见下面的流程图所示。
详细参考:
因为构造函数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 遍历对象的所有属性时和查找对象的属性一样也会遍历整个原型链,任何可以通过原型链访问到并且可枚举的属性(enumerable)都会被枚举,因此原型链很长时,也会影响for in的性能。
使用in操作符来检查属性是否在对象中存在时,也会查找对象的原型链(无论属性是否可枚举),因此也会影响in操作符的性能。
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更新被冻结:
例子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
“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放入任务队列期间是否有其他立即执行的事件加入到队列中。
例子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>
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);
};
})();
timed-array-processing-in-javascript
高级浏览器支持web worker,每一个web worker新起一个线程,通过加载的一个js文件来初始化,不受单线程执行的限制,但是,有很多限制:
他们通过postMessage/onmessage 来进行发送和接收消息进行交互。
example
对于动画效果我们推荐是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>
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会从当前执行的上下文的上一级作用域去查找,同理,如果在上一级作用域找不到,会继续往上层作用域去查找,直到找到顶层作用域,即global上下文,如果找不到,则会报错Uncaught ReferenceError: xxx is not defined
。
活动上下文
。栈—ECStack
。全局上下文(global context)
,堆栈顶部是当前(活动的)执行上下文
。//在全局上下文执行函数
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(){...}
};
js函数有一个特殊的属性 [[Scope]],这是一个私有属性,只有JS引擎可以访问,FireFox的几个引擎(SpiderMonkey和Rhino)提供了__parent__
来访问它,它有几个特性:
创建函数的上下文的变量对象
(可能是全局对象,也可能是对应于一个函数的执行上下文的活跃对象);通过该属性,我们可以窥探当前的执行上下文的作用域链的内部情况,例如上面的例子可以通过
bar.__parent__ === VO(foo functionContext)
bar.__parent__.__parent__ === VO(globalContext)
在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初始化的过程是依次按照形参->函数声明->变量声明的顺序进行,具体如下:
AO={
this,
arguments,
key1:val1,
…
}
// key: 形式参数的名称,
// value:如果没有对应传递实际参数,那么值就是undefined
AO={
…, // 前面已经初始化的此处省略显示
funname1: function-object1,
…,
}
注意:
function fun_name(){ …}
这样的标准函数声明/定义,不包括:// 变量声明,把一个匿名函数赋值给该变量
var fun_name = function(){…}
// 同上,只不过函数声明是具名而不是匿名
var fun_name = function fun_name(){…}
// 立即执行的函数表达式
(funciton(){…}())
(function())()
AO={
…, // 前面已经初始化的此处省略显示
var_name1: undefined,
…,
}
注意:
var foo = a;
,不带var声明的变量 foo = a
不属于这种情形;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或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构造器创建的时候,新创建的函数[[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的上下文有相同的作用域链,举例:
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方案,满足某种屏幕宽度时设置某些元素的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 方案也是最流行的响应式布局方案,优缺点如下:
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;
}
}
/* ... */
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
});
在页面的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;
}
有多种方式可以实现在保持dom元素不变,改变css的设置来变换元素的布局。
dom结构
<div class="wrapper a">
<div class="a1">
御林军VS延边队鹿死谁手?
</div>
<div class="a2">
御林军VS延边队鹿死谁手?
</div>
<div class="a3">
御林军VS延边队鹿死谁手?
</div>
</div>
.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被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;
但是只能在任意位置独占一行,只能在所有元素的上面独占一行,有一定的局限性。
.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元素之间的垂直方向的对齐。
.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;
}
}
}
总结:这种方法如果只有独占一行或者并排显示这两种逻辑还可以,如果要实现个别独占一行,有的又想并排显示处理特别麻烦,不推荐使用。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.