浏览器与新技术
这是一篇很长的文章,能够算上一本小书了,有精力的很是建议阅读。css
常见的浏览器内核有哪些?
浏览器/RunTime | 内核(渲染引擎) | JavaScript 引擎 |
---|---|---|
Chrome | Blink(28~) Webkit(Chrome 27) |
V8 |
FireFox | Gecko | SpiderMonkey |
Safari | Webkit | JavaScriptCore |
Edge | EdgeHTML | Chakra(for JavaScript) |
IE | Trident | Chakra(for JScript) |
PhantomJS | Webkit | JavaScriptCore |
Node.js | - | V8 |
浏览器的主要组成部分是什么?
- 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其余显示的各个部分都属于用户界面。
- 浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
- 呈现引擎 - 负责显示请求的内容。若是请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- 网络 - 用于网络调用,好比 HTTP 请求。其接口与平台无关,并为全部平台提供底层实现。
- 用户界面后端 - 用于绘制基本的窗口小部件,好比组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操做系统的用户界面方法。
- JavaScript 解释器。用于解析和执行 JavaScript 代码。
- 数据存储。这是持久层。浏览器须要在硬盘上保存各类数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(可是轻便)的浏览器内数据库。
图:浏览器的主要组件。html
值得注意的是,和大多数浏览器不一样,Chrome 浏览器的每一个标签页都分别对应一个呈现引擎实例。每一个标签页都是一个独立的进程。前端
浏览器是如何渲染UI的?
- 浏览器获取HTML文件,而后对文件进行解析,造成DOM Tree
- 与此同时,进行CSS解析,生成Style Rules
- 接着将DOM Tree与Style Rules合成为 Render Tree
- 接着进入布局(Layout)阶段,也就是为每一个节点分配一个应出如今屏幕上的确切坐标
- 随后调用GPU进行绘制(Paint),遍历Render Tree的节点,并将元素呈现出来
浏览器如何解析css选择器?
浏览器会『从右往左』解析CSS选择器。html5
咱们知道DOM Tree与Style Rules合成为 Render Tree,其实是须要将Style Rules附着到DOM Tree上,所以须要根据选择器提供的信息对DOM Tree进行遍历,才能将样式附着到对应的DOM元素上。java
如下这段css为例node
.mod-nav h3 span {font-size: 16px;} 复制代码
咱们对应的DOM Tree 以下nginx
若从左向右的匹配,过程是:git
- 从 .mod-nav 开始,遍历子节点 header 和子节点 div
- 而后各自向子节点遍历。在右侧 div 的分支中
- 最后遍历到叶子节点 a ,发现不符合规则,须要回溯到 ul 节点,再遍历下一个 li-a,一颗DOM树的节点动不动上千,这种效率很低。
若是从右至左的匹配:github
- 先找到全部的最右节点 span,对于每个 span,向上寻找节点 h3
- 由 h3再向上寻找 class=mod-nav 的节点
- 最后找到根元素 html 则结束这个分支的遍历。
后者匹配性能更好,是由于从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点);而从左向右的匹配规则的性能都浪费在了失败的查找上面。web
DOM Tree是如何构建的?
- 转码: 浏览器将接收到的二进制数据按照指定编码格式转化为HTML字符串
- 生成Tokens: 以后开始parser,浏览器会将HTML字符串解析成Tokens
- 构建Nodes: 对Node添加特定的属性,经过指针肯定 Node 的父、子、兄弟关系和所属 treeScope
- 生成DOM Tree: 经过node包含的指针肯定的关系构建出DOM Tree
浏览器重绘与重排的区别?
- 重排: 部分渲染树(或者整个渲染树)须要从新分析而且节点尺寸须要从新计算,表现为从新生成布局,从新排列元素
- 重绘: 因为节点的几何属性发生改变或者因为样式发生改变,例如改变元素背景色时,屏幕上的部份内容须要更新,表现为某些元素的外观被改变
单单改变元素的外观,确定不会引发网页从新生成布局,但当浏览器完成重排以后,将会从新绘制受到这次重排影响的部分
重排和重绘代价是高昂的,它们会破坏用户体验,而且让UI展现很是迟缓,而相比之下重排的性能影响更大,在二者没法避免的状况下,通常咱们宁肯选择代价更小的重绘。
『重绘』不必定会出现『重排』,『重排』必然会出现『重绘』。
如何触发重排和重绘?
任何改变用来构建渲染树的信息都会致使一次重排或重绘:
- 添加、删除、更新DOM节点
- 经过display: none隐藏一个DOM节点-触发重排和重绘
- 经过visibility: hidden隐藏一个DOM节点-只触发重绘,由于没有几何变化
- 移动或者给页面中的DOM节点添加动画
- 添加一个样式表,调整样式属性
- 用户行为,例如调整窗口大小,改变字号,或者滚动。
如何避免重绘或者重排?
集中改变样式
咱们每每经过改变class的方式来集中改变样式
// 判断是不是黑色系样式 const theme = isDark ? 'dark' : 'light' // 根据判断来设置不一样的class ele.setAttribute('className', theme) 复制代码
使用DocumentFragment
咱们能够经过createDocumentFragment建立一个游离于DOM树以外的节点,而后在此节点上批量操做,最后插入DOM树中,所以只触发一次重排
var fragment = document.createDocumentFragment(); for (let i = 0;i<10;i++){ let node = document.createElement("p"); node.innerHTML = i; fragment.appendChild(node); } document.body.appendChild(fragment); 复制代码
提高为合成层
将元素提高为合成层有如下优势:
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当须要 repaint 时,只须要 repaint 自己,不会影响到其余的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
提高合成层的最好方式是使用 CSS 的 will-change 属性:
#target { will-change: transform; } 复制代码
前端如何实现即时通信?
短轮询
短轮询的原理很简单,每隔一段时间客户端就发出一个请求,去获取服务器最新的数据,必定程度上模拟实现了即时通信。
- 优势:兼容性强,实现很是简单
- 缺点:延迟性高,很是消耗请求资源,影响性能
comet
comet有两种主要实现手段,一种是基于 AJAX 的长轮询(long-polling)方式,另外一种是基于 Iframe 及 htmlfile 的流(streaming)方式,一般被叫作长链接。
长轮询优缺点:
- 优势:兼容性好,资源浪费较小
- 缺点:服务器hold链接会消耗资源,返回数据顺序无保证,难于管理维护
长链接优缺点:
- 优势:兼容性好,消息即时到达,不发无用请求
- 缺点:服务器维护长链接消耗资源
SSE
SSE(Server-Sent Event,服务端推送事件)是一种容许服务端向客户端推送新数据的HTML5技术。
- 优势:基于HTTP而生,所以不须要太多改造就能使用,使用方便,而websocket很是复杂,必须借助成熟的库或框架
- 缺点:基于文本传输效率没有websocket高,不是严格的双向通讯,客户端向服务端发送请求没法复用以前的链接,须要从新发出独立的请求
Websocket
Websocket是一个全新的、独立的协议,基于TCP协议,与http协议兼容、却不会融入http协议,仅仅做为html5的一部分,其做用就是在服务器和客户端之间创建实时的双向通讯。
- 优势:真正意义上的实时双向通讯,性能好,低延迟
- 缺点:独立与http的协议,所以须要额外的项目改造,使用复杂度高,必须引入成熟的库,没法兼容低版本浏览器
Web Worker
后面性能优化部分会用到,先作了解
Web Worker 的做用,就是为 JavaScript 创造多线程环境,容许主线程建立 Worker 线程,将一些任务分配给后者运行
Service workers
后面性能优化部分会用到,先作了解
Service workers 本质上充当Web应用程序与浏览器之间的代理服务器,也能够在网络可用时做为浏览器和网络间的代理,建立有效的离线体验。
什么是浏览器同源策略?
同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。
同源是指"协议+域名+端口"三者相同,即使两个不一样的域名指向同一个ip地址,也非同源。
下表给出了相对http://store.company.com/dir/page.html同源检测的示例:
浏览器中的大部份内容都是受同源策略限制的,可是如下三个标签能够不受限制:
<img src=XXX>
<link href=XXX>
<script src=XXX>
如何实现跨域?
跨域是个比较古老的命题了,历史上跨域的实现手段有不少,咱们如今主要介绍三种比较主流的跨域方案,其他的方案咱们就不深刻讨论了,由于使用场景不多,也不必记这么多奇技淫巧。
最经典的跨域方案jsonp
jsonp本质上是一个Hack,它利用<script>
标签不受同源策略限制的特性进行跨域操做。
jsonp优势:
- 实现简单
- 兼容性很是好
jsonp的缺点:
- 只支持get请求(由于
<script>
标签只能get) - 有安全性问题,容易遭受xss攻击
- 须要服务端配合jsonp进行必定程度的改造
jsonp的实现:
function JSONP({ url, params, callbackKey, callback }) { // 在参数里制定 callback 的名字 params = params || {} params[callbackKey] = 'jsonpCallback' // 预留 callback window.jsonpCallback = callback // 拼接参数字符串 const paramKeys = Object.keys(params) const paramString = paramKeys .map(key => `${key}=${params[key]}`) .join('&') // 插入 DOM 元素 const script = document.createElement('script') script.setAttribute('src', `${url}?${paramString}`) document.body.appendChild(script) } JSONP({ url: 'http://s.weibo.com/ajax/jsonp/suggestion', params: { key: 'test', }, callbackKey: '_cb', callback(result) { console.log(result.data) } }) 复制代码
最流行的跨域方案cors
cors是目前主流的跨域解决方案,跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不一样源服务器上的指定的资源。当一个资源从与该资源自己所在的服务器不一样的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
若是你用express,能够这样在后端设置
//CORS middleware var allowCrossDomain = function(req, res, next) { res.header('Access-Control-Allow-Origin', 'http://example.com'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); res.header('Access-Control-Allow-Headers', 'Content-Type'); next(); } //... app.configure(function() { app.use(express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({ secret: 'cool beans' })); app.use(express.methodOverride()); app.use(allowCrossDomain); app.use(app.router); app.use(express.static(__dirname + '/public')); }); 复制代码
在生产环境中建议用成熟的开源中间件解决问题。
最方便的跨域方案Nginx
nginx是一款极其强大的web服务器,其优势就是轻量级、启动快、高并发。
如今的新项目中nginx几乎是首选,咱们用node或者java开发的服务一般都须要通过nginx的反向代理。
反向代理的原理很简单,即全部客户端的请求都必须先通过nginx的处理,nginx做为代理服务器再讲请求转发给node或者java服务,这样就规避了同源策略。
#进程, 可更具cpu数量调整
worker_processes 1;
events {
#链接数
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#链接超时时间,服务器会在这个时间事后关闭链接。
keepalive_timeout 10;
# gizp压缩
gzip on;
# 直接请求nginx也是会报跨域错误的这里设置容许跨域
# 若是代理地址已经容许跨域则不须要这些, 不然报错(虽然这样nginx跨域就没意义了)
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
# srever模块配置是http模块中的一个子模块,用来定义一个虚拟访问主机
server {
listen 80;
server_name localhost;
# 根路径指到index.html
location / {
root html;
index index.html index.htm;
}
# localhost/api 的请求会被转发到192.168.0.103:8080
location /api {
rewrite ^/b/(.*)$ /$1 break; # 去除本地接口/api前缀, 不然会出现404
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://192.168.0.103:8080; # 转发地址
}
# 重定向错误页面到/50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
复制代码
其它跨域方案
- HTML5 XMLHttpRequest 有一个API,postMessage()方法容许来自不一样源的脚本采用异步方式进行有限的通讯,能够实现跨文本档、多窗口、跨域消息传递。
- WebSocket 是一种双向通讯协议,在创建链接以后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据,链接创建好了以后 client 与 server 之间的双向通讯就与 HTTP 无关了,所以能够跨域。
- window.name + iframe:window.name属性值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值,咱们能够利用这个特色进行跨域。
- location.hash + iframe:a.html欲与c.html跨域相互通讯,经过中间页b.html来实现。 三个页面,不一样域之间利用iframe的location.hash传值,相同域之间直接js访问来通讯。
- document.domain + iframe: 该方式只能用于二级域名相同的状况下,好比 a.test.com 和 b.test.com 适用于该方式,咱们只须要给页面添加 document.domain ='test.com' 表示二级域名都相同就能够实现跨域,两个页面都经过js强制设置document.domain为基础主域,就实现了同域。
1.若是以为这篇文章还不错,来个分享、点赞吧,让更多的人也看到