浏览器组成
通常我们认为浏览器主要由七部分组成:用户界面、网络、js引擎、渲染引擎、UI后端、js解释器和持久化数据存储。用户界面包括浏览器中可见的地址输入框,浏览器前进返回按钮等功能选项。浏览器引擎可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,是浏览器各个部分通信的核心。浏览器渲染引擎的功能是解析DOM文档和CSS规则并将内容排版到浏览器中显示有样式的页面,处理兼容问题时我们常说的浏览器内核主要指的就是渲染引擎。网络模块则是浏览器开启网络线程发送请求或下载资源文件的模块,例如DOM树解析过程中请求静态资源就是通过网络模块发起的。UI后端则用于绘制基本的浏览器窗口内控件,比如组合选择框、按钮等。js解释器则是浏览器解释和执行js脚本的部分,例如V8引擎。浏览器数据持久化则涉及cookie、localStorage等一些客户端存储技术。
渲染引擎
解析HTML构建DOM树时渲染引擎会先将HTML标签解析成多个DOM元素对象节点组成的且具有节点父子关系的DOM树结构,然后根据DOM树结构的每个节点顺序提取计算使用的CSS规则并重新计算DOM树结构的样式数据,生成一个带样式描述的DOM渲染树对象(还未应用样式)。DOM渲染树生成结束之后,进入渲染树的布局阶段,即根据每个渲染树的布局,即根据每个渲染树节点在页面中的大小和位置,将节点固定到页面的对应位置上,这个阶段主要是元素的布局属性生效。接下来就是绘制阶段,将渲染树节点的背景、颜色、文本等样式信息应用到每个节点上,这个阶段主要是元素的内部显示样式(例如color、background、text-shadow等属性)生效,最终完成整个DOM在页面上的绘制显示。
这里我们要关注的是渲染树的布局阶段和绘制阶段。页面生成后,如果页面位置发生变化,就要从布局阶段开始渲染,也就是页面重排。所以页面重排有一定会进行后续重绘,如果页面元素只是显示样式改变而布局不变,那么页面内容改变将从绘制阶段开始,也称为页面重绘。因此我们要尽可能避免页面的重排,并减少页面元素的重绘。关于减少页面重排可以参考我另一篇博文GPU加速
CSS解析
CSS解析和HTML解析类似,首先也要通过词法解析生成CSS分析树,而且使用特定的CSS文本语法来实现。当每个DOM节点提取CSS样式完成时,用于页面布局和绘制的DOM树便形成了。在一个已经形成的DOM渲染树中,节点的CSS规则可以通过document.defaultView.getComputedStyle(element, null)
方法来获取查看。
浏览器数据持久化存储
这里说的数据持久化存储主要是针对浏览器的,所以我们统称为浏览器缓存(Browser Caching)。浏览器缓存是浏览器端用于在本地保存数据并进行快速读取以避免重复资源请求的传输机制的统称。有效的缓存可以避免重复的网络资源请求并让浏览器快速响应用户操作,提高加载速度
- HTTP文件缓存。HTTP文件缓存是基于HTTP协议的文件级缓存机制。在文件重复请求的情况下,浏览器可以根据HTTP响应的协议头信息判断是否从服务器请求文件还是本地读取文件。这里可以参考我另一篇博文快速掌握前端的HTTP基础(1)。
- localStorage是H5的一种本地缓存方案。目前主要用于浏览器保存体积较大的数据。尽管单个域名下localStorage的大小是有限制的,但是可以用iframe的方式采用多个域名来突破单个页面下的localStorage存储数据的最大限制。
- Cookie。一条cookie记录主要是由键、值、域和过期时间和大小组成,一般用于保存用户的认证信息。不同的域的Cookie信息如果想实现共享需要设置Cookie的path和domain来实现。Session Cookie一般不保存在硬盘上而是保存在内存里,持久型Cookie一般会设置过期时间,浏览器会将其保存到硬盘上。关于Cookie可以参考我另一篇博文cookie的操作
接下来聊一聊前端中的协议。
HTTP2
HTTP2相对于之前的HTTP协议有以下优点:
- HTTP2采用完全二进制的格式来传输数据,而非HTTP1.x的默认文本格式。而二进制在网络中传输的基本单位为帧(Frame,一个帧可以理解为具有固定格式和长度的二进制数据包),每个帧包含几个固定部分内容:类型Type、长度Length、标记Flags等,多个帧的传输在网络中形成了帧的传输网络流。关于这部分内容可以参考另一篇博文HTTP 2.0带来哪些新特性
- HTTP2支持使用TCP多路复用的方式来降低网络请求连接建立和关闭的开销,多个请求可以通过一个TCP连接来并发完成(不再需要雪碧图)
- HTTP2支持传输流的优先级和流量控制机制。HTTP2中每个文件传输流都有自己的传输优先级,并可以通过服务器来动态改变。服务器会保证优先级高的文件先传输。在未来的浏览器端渲染中,服务器端即可以优先传输CSS文件保证页面的渲染。(不再需要我们在代码中继续把CSS文件写在最上方)
- 支持服务器推送。服务器端能够在特定条件下把资源主动推送给客户端。就像浏览器端的资源预加载一样,例如资源推送可以在HTML文档下载之前让HTML的静态文件先进行加载。
目前支持HTTP2协议传输的浏览器依然很少,但HTTP2的时代终会到来。
请求劫持与HTTPS
网络请求劫持目前主要分为两种:DNS劫持与HTTP劫持。DNS劫持通常是指攻击者劫持了DNS服务器,取得某域名的解析记录控制权,进而修改该域名的解析结果。HTTP劫持是指在用户浏览器与访问母的服务器之间的网络传输通道中从网关或防火墙上监视特定数据信息,在正常的数据包中插入攻击者的数据包(中间人攻击),通过使用HTTPS可以防范这一攻击方式,关于HTTPS可以参考我另一篇博文HTTPS 为什么更安全
REST
REST是网络应用软件之间的架构关系的一套约定的相互调用的规则。如果一个网络应用软件的设计是按照REST定义的,我们就可以认为它使用的交互调用的方法设计遵循RESTful规范。例如对于书籍book的记录管理接口,有增、删、改、查操作,于是我们用path/appendBook,path/getBook等。接着如果调整了需求,我们使用path/appendBook1,path/getBook2…这样几次之后项目就会无法维护,而使用RESTful规范重新设计接口之后,一切都变得清晰自然。
与Native交互
移动互联网兴起后,移动端Native开发(我们现在一般讲移动端原生应用的开发称为移动端Native开发)盛行。随之而来就是H5的出现,它允许开发者在移动设备快速开发网页端应用,也可以将Web页面嵌入到Native应用中。Hybrid App结合了Native App和Web App的优势,在牺牲一部分性能的前提下,适应了更多的移动应用开发场景。
在H5中调用Native程序一般有两种比较通用的方法。
一个是通过URI请求。其主要原理是Native应用可在移动端系统中注册一个Scheme协议的URI,这个URI可以在系统的任意地方授权访问来调起一段原生方法或者原生的界面。
let iframe = document.createElement('iframe');
iframe.setAttribute('style', 'display: none');
document.body.appendChild(iframe);
iframe.setAttribute('src', 'myApp://className/method?args');
一般Native应用会提前向移动端系统注册Scheme协议,之后前端脚本发送URI请求的时候,WebView将会获取URI资源的地址交给Native App。Native App将请求转发给系统,系统就会通过URI地址执行Scheme协议定义的Native操作。
另一个方法是通过addJavascriptInterface方法将Native的一个对象方法注入到页面中调用,供js调用。具体接口这里就不给出了。
如果Native需要主动调用HTML页面中的js方法,应该怎么做?首先需要js在页面全局中声明相应的方法,在安卓中的接口是loadUrl。
AMP
流动网页提速(Accelerated Mobile Pages,AMP)是google推行的一个提升页面资源载入效率的HTML提议规范。基本思路有两点:使用严格受限的高效HTML标签以及使用静态网页缓存技术来提高网络访问静态资源的性能和用户体验。
浏览器同个域名下的最大并行下载线程个数是有限的,所以为了增大资源下载并行数,我们常常将HTML、js、CSS、图片分域存放。分域也可以将静态资源请求进行服务器端的负载均衡。
AMP标签的实现原理可以理解为采用自定义的快加载标签元素来代替慢加载标签元素。如果要在实际项目中推行也比较简单,按照AMP标签规范去开发业务组件就可以了。
Shadow DOM
Shadow DOM是HTML的一个规范,它允许浏览器开发者封装自己的HTML标签、CSS样式和特定的js,同时也可以让开发人员创建类似< video>
这样的自定义一级标签。一个< video>
标签中其实有很多内容,隐藏的Shadow-root里面的内容就是视频播放器控制组件的结构所在处。目前Shadow DOM还未普及。
ES5
ES5中新增的对象方法比较常使用的是keys和difineProperty。而ES5中数组新增了很多方法,比如indexOf、every和reduce等,关于数组可以参考我另一篇博文js数组方法分析。ES5中函数新增了bind,字符串对象中新增了trim,Date对象中新增了now和toJSON方法。
ES6
ES6中有一个亮点功能解构赋值,它解决了编码赋值的编码冗余和模块按需导出的问题,解构赋值主要分为数组解构和对象解构。
let [a, b, c] = [11, 22];
let {one, two, three} = {two: 2, three: 3, one:1};
[a, b] = [b, a]; // 交换变量a、b的值
console.log(c); // undefined
这里数组解构是严格按照数组下标依次对应顺序赋值的,如果赋值的常量个数不够,则对应下标的变量默认为undefined,如果常量个数超出,则多余的会被舍弃。
SVG动画
SVG原生支持一些动画效果,通过组合可以生成较复杂的动画,不需要js参与控制。SVG动画由SVG元素内部的元素属性控制,通常通过< set>、< animate>、< animateColor>、< animateTransform>、< animateMotion>这几个元素来实现。
CSS3 transition
CSS3出现后,增加了两种实现动画的方式:transition和animation。transition并不能实现独立的动画,只能在某个标签元素样式或状态改变时进行平滑的动画效果过渡。
canvas动画
canvas本身就是在js中调用canvas接口进行绘图,因而也可以用js实现动画。
requestAnimiationFrame
requestAnimiationFrame是前端表现层实现动画的另一种API实现,它的原理和setTimeout及setInterval类似,但是时间更准,可以将懂画每一步的操作方法传入到requestAnimiationFrame中,在每一次执行完后进行异步回调来连续触发动画效果。关于这个API的使用可以参考我另一篇博文js实现帧动画
window.requestAnimiationFrame = window.requestAnimationFrame ||
//chrome
window.webkitRequestAnimationFrame ||
//firefox
window.mozRequestAnimationFrame ||
//opera
window.oRequestAnimationFrame ||
//没有这个方法用setTimeout代替
function(callback){
return window.setTimeout(callback, callback.interval || DEFAULT_INTERVAL);
};
let element = document.getElementById('box');
let left = 0;
requestAnimiationFrame(step);
//持续改变元素位置
function step() {
if(left < window.innerWidth - 200){
left += 1;
element.style.marginLeft = left + 'px';
requestAnimiationFrame(step);
}
}
下一篇博文 现代前端技术解析(2)会详细聊一聊前端交互框架