浏览器对象模型
浏览器渲染与布局
在网页的加载开始到结束这期间,浏览器需要建立三棵树:
DOM
俗称节点树,它是浏览器根据HTML结构经过各种处理(特别是容错处理)生成的树,它代表了整个网页的结构。
CSSOM
样式规则树,它是浏览器分析网页对应的CSS文件生成的树,它跟网页的HTML结构没有任何关系,仅仅是CSS文件的树型结构。
要特别注意的是浏览器发现需要下载一个CSS文件时,它会马上停止渲染页面,并且在新的样式规则树未被建立前都不会再进行渲染或者更新页面,也就是CSS会阻塞页面的渲染。因此,必须尽可能精简CSS文件内容以及将CSS文件的link标签放到页面头部。Render tree
渲染树,浏览器在DOM树和CSSOM树生成之后融合成为真正用于渲染的树,它的结构跟DOM树一致,但是同时又根据CSSOM树添加了相关的样式信息,浏览器会根据渲染树在页面区域画图,最终形成网页界面。
浏览器对象模型的构成
在设计页面交互时,我们需要面对三组API:
JavaScript Core
属于ECMAScript定义的JavaScript标准对象、函数。像Array、String、Number之类的对象都是JavaScript标准对象,这部份内容主要是提供了基础编程能力。
文档对象模型
W3C针对XML定义的标准DOM API,主要功能是针对DOM节点进行操作。DOM API的很多实现跟XML DOM其实是一致的,事实上它们也应该是一致的,仅仅因为HTML是一个应用非常广泛并且不是一个严格的XML文档,在实现上会针对HTML的特点做了一些扩展。
浏览器对象模型
浏览器宿主环境提供给JavaScript调用的API,主要是允许JavaScript通过这些API操作浏览器相关的功能,大家称其为BOM。
BOM虽然没有标准,每个浏览器基本上可以自己搞一套,事实上在现代浏览器中已经形成了一套事实标准,各种浏览器之间差距也并不大,也很少出现同样的功能在这个浏览器能实现但在另一个浏览器没有实现的情况。
DOM是BOM的一部份,BOM的顶级对象是window,而DOM则是document。
节点的关系
- 文档对象模型从结构上看是一棵树,树上每个叶子都是一个节点,也就是说它们都拥有节点的标准方法与属性,如右图所示,Node是所有节点类型的基类。
- 文档节点在HTML DOM中的实现是HTMLDocument,这也是跟标准XML DOM不同的地方,它其实是Document的子类。
- 属性(Attribute)在XML DOM中的定义也是一个节点,它的类名为Attr。
- CharacterData分为文本节点(Text)以及注释节点(Comment),它实际上并没有直接实例存在文档中,而是跟Document类一样作为纯粹的父类存在。
- 元素是接触最多的节点类型,HTML文档中所有格式的标签都是一个元素,跟其它几种节点一样,它不仅拥有Node提供的各类方法与属性,还拥有Element、HTMLElement提供的各类方法与属性。
实际上,HTMLElement在DOM中也是作为一个纯粹的基类,由于每种元素具体的表现不同,它们都拥有自己的一个类,比如BODY标签是HTMLBodyElement。
注:
- 无法使用new Node()创建一个节点,根据XML
DOM的标准,每个节点都被限定为某个文档所属,因此只能通过document.createElement之类的API创建。 - 节点都能通过ownerDocument属性访问到它所属的Document对象。
- childNodes会遍历到一个节点的所有子节点包括TextNode,因此请小心!你也可以使用children属性来得到所有的子元素。
DOM事件流
当一个DOM元素在产生一个事件的时候,将会在根节点以及该DOM元素之间传递,这个过程被称为事件流。
常见的事件流分为三种:
捕获型事件流(Capturing)
最早由Netscape Navigator实现,当事件发生时,浏览器会从根节点一层一层往事件发生的元素传递,沿途节点对应的事件回调函数会依次被触发。
冒泡型事件流(Bubbling)
由IE浏览器实现,跟捕获型事件流相反,浏览器会从事件发生的元素一层一层传递到根节点。
标准事件流(W3C Event)
W3C规定的标准事件流相当于捕获型事件流和冒泡型事件流的结合,当事件发生时,首先会开始捕获阶段,事件从根节点传递到事件发生的元素,然后再进入冒泡阶段重新传递回根节点。
现阶段,除了IE浏览器低版本,各主流浏览器基本都支持标准事件流。
事件的绑定
绑定事件分为三种方法,以click事件为例分别是:
1.HTML元素的onclick属性
<a href=“javascript:void(0);” onclick=“alert(1234);”>这是元素</a>
2.DOM对象的onclick属性(JavaScript)
document.getElementById(“mylink”).onclick = function(ev) {}
3.addEventListener/removeEventListener(JavaScript)
document.getElementById(“mylink”).addEventListener(“click”, function(ev){}, false);
需要注意的是:
- 第一种方式直接将JavaScript代码写在HTML代码中,从界面与逻辑分享的角度来看,不可取,应该弃用。
- 第二种方式是直接为DOM对象的属性赋值,它只能绑定一个事件回调函数,很容易不经意间将之前绑定过的回调函数覆盖,应该弃用。
第三种方式是应该采用的方式,使用中需要注意几点:
- 第一个参数不带on,直接写click即可
第二个参数除了允许是一个函数外,它还可以是一个实现了handleEvent方法的对象
document.body.addEventListener(‘click’, {
handleEvent: function() {
alert(“1234”);
}
}, false);第三个参数允许设置事件发生时,应该是捕获阶段还是冒泡阶段调用回调函数
- 允许为同一个事件绑定多个事件回调函数,从标准上来看并没有规定多个事件回调函数的执行顺序,也就是它们理论上是无序的,但事实上表现出来是有序的,至于是先绑定先回调还是先绑定后回调要看浏览器的具体实现(基本上是先绑定先回调)
数据存储
cookie
http协议规定了http请求是无状态的,但在http应用(即网页)中经常需要追踪用户的使用习惯、登录状态,cookie作为http协议的一部份,允许服务器端或者客户端往客户端写入小量的明文文本内容,该内容根据写入时的要求可以保存在用户的硬盘上或者内存中。JavaScript可以使用document.cookie读写cookie。在同一个域下,cookie的个数是有限制的,比如IE浏览器限制为50个,并且总长度也有限制,不应该超过4kb。
服务器通过http报文头写cookie的格式是:Set-Cookie: name=value[; expires=date][; domain=domain][; path=path][; secure][; httpOnly]
expires
cookie的过期时间,它是一个GMT时间,格式是: Mon, 27 Jun 2016 16:45:31 GMT。使用JavaScript可以用new Date().getGMTString()来获取当前的GMT时间。
如果不设置过期时间,cookie将会是会话级的,当浏览器关闭时即被删除。如果想删除某个已经存在的cookie,也可以为它设置已经过期的时间。domain
cookie所属的域名必须是当前域名的一部份,比如当前域名是abc.myhome.com,那么默认的domain是abc.myhome.com,但也可以设置为myhome.com。浏览器会匹配该domain值是否为当前域名的尾部,如果是则发送该cookie。
path
跟domain类似,浏览器会匹配当前页面的路径头部是否包含path值,如果包含则会发送该cookie。
secure
如果set-cookie包含secure字段,则仅会在当前协议是https的情况下才会发送cookie
httpOnly
这是IE6 SP1是为了防止XSS攻击而引入的选项,现在主流浏览器都支持该选项。如果存在该字段,浏览器将会对JavaScript屏蔽该cookie的访问权限。某些移动端如果当前域下有一个cookie被设置为httpOnly,则所有cookie包括非httpOnly的cookie都无法通过document.cookie访问到。
注意,httpOnly对于服务器端来说完全没区别。
storage
在本地用户数据缓存方面,IE浏览器曾经流行过userdata,但从IE8以上版本开始支持HTML5的storage,其它浏览器在这方面支持也非常好。Storage允许前端工程师使用脚本在本地存储一些键值对类型的数据,根据W3C的建议,基本上所有浏览器都限制Storage的总大小不得超过5MB。
Storage分成两种:
- SessionStorage 会话级storage,当用户关闭浏览器时它即会消失
- LocalStorage 只要用户不清除,存储的数据将永久保存在本地
需要注意的是:
- storage的键和值都是字符串,在序列化与反序列化时要特别注意
- storage的数据不会在http请求中被传输
- 写入数据的时候,最好考虑一下它什么时候应该被删除
- app中的webview,经过测试对onstorage事件的支持不佳,无法跨webview监听onstorage
- 除去sessionStorage和localStorage,其实ServiceWork
API还定义了一种叫CacheStorage的缓存CacheStorage在chrome中有实现,可以用caches来访问到它的实例,它的作用是缓存Response对象。
Application Cache
除了针对用户的Storage之外,针对离线应用浏览器还实现了离线缓存机制,网页设计者可以为HTML元素增加一个名为manifest的属性,它的值是离线应用缓存的配置文件。
浏览器将会根据配置文件的设置将当前页依赖的各种资源缓存到本地,当用户断开网络时这些缓存起来的资源将会像网络正常使用时一样正常访问,同时浏览器还会根据manifest文件的变化做自动更新。
<!DOCTYPE HTML>
<html manifest=”index.appcache">
…
</html>
manifest的文件内容分为三部份:
CACHE MANIFEST
每行罗列的文件地址都会在首次加载后被缓存在本地
NETWORK
要求浏览器不对这里罗列的文件地址做缓存,它们每次访问都应该是从服务器下载
FALLBACK
规定了当某个目录下的网页无法访问时的回退页
indexedDB
Storage允许用户保存键值对的数据,由于离线应用的需求越来越多,工程师们需要一款能够在客户端存储更为复杂并且支持复杂的查询条件的数据库。早期Chrome实现了Web Database(至今依然可用,但不建议使用),但W3C提出了新的标准并且被各大浏览器实现的indexedDB。
如Mozilla开发者社区所言,indexedDB希望大家如此使用它:
- 打开数据库并且开始一个事务。
- 创建一个 object store。
- 构建一个请求来执行一些数据库操作,像增加或提取数据等。
- 通过监听正确类型的 DOM 事件以等待操作完成。
- 在操作结果上进行一些操作(可以在 request 对象中找到)
需要特别注意的是:
- indexedDB的任何操作都是基于事务的
- indexedDB跟其它数据库不同,它允许你直接添加一个JSON对象,而不是编写SQL语句
- indexedDB支持索引,当然你需要自己去创建它
参考资料:
https://developer.mozilla.org/zh-CN/docs/Web/API/IndexedDB_API/Using_IndexedDB
数据通讯
HTTP请求基本格式
HTTP请求包含两大部份,分别是请求体(request)以及响应体(response)。请求体是由客户端(浏览器)向服务器端发出的请求信息,而响应体则是服务器向客户端返回的响应数据。
请求的方式经常接触到的有GET以及POST,二者区别在于有没有报文主体。由于GET方式没有报文主体,因此服务器在解析GET请求会更快速。
请求体跟响应体都分别包含三部份:
请求行
请求行也就是请求的第一行,请求体与响应体二者的请求行略有不同,除了标明了HTTP协议版本以及请求方式(GET/POST)之外 ,请求体会注明请求的URL地址,而响应体则标明此次请求的结果。
报文头
报文头用键值对的方式标明了一些附加数据,比如User-Agent表示客户端的类型,Content-Type表示请求主体的内容格式。右图中采用的是查询字符串的方式,也就是Form表单提交的数据格式。
报文主体
并不是所有请求都会有报文主体的,报文主体跟报文头之间会空出一行。POST方式提交的数据将会在这里传输。响应体的报文主体也就是返回的数据,比如网页的内容。
响应代码的含义:
- 1xx:指示信息–表示请求已接收,继续处理
- 2xx:成功–表示请求已被成功接收、理解、接受
- 3xx:重定向–要完成请求必须进行更进一步的操作
- 4xx:客户端错误–请求有语法错误或请求无法实现
- 5xx:服务器端错误–服务器未能实现合法的请求
注:关于详细的HTTP协议请查阅《HTTP协议1.1白皮书》,即RFC2616。
常用的Web数据交换格式
JSON
现今在Web应用中应用最广泛的数据交换格式,它是ECMAScript表示对象的字面量语法的子集,对于JavaScript工程师来说它并不陌生,仅仅是一种更严格的对象定义方式。在各种用于Web的后端语言中基本上也都提供了对JSON数据的支持。
根据JSON的定义,大致可以总结为以下四条规则(其实就是变量的字面量表示法):
- 并列的数据(数组元素)以逗号分隔
- 键值对使用冒号分隔
- 数组使用中括号表示
- 对象使用大括号表示
XML
XML被设计用来传输和存储数据,HTML就是XML的一个子集,各种语言也基本上提供了对XML语言的支持。尽管由于XML相对JSON来说不够简洁,并且在序列化以及反序列化不如JSON方便(毕竟JSON本身就是ECMAScript语法的一部份)而在Web应用方面被JSON取代,但依然有大量的数据接口采用XML格式。
YAML
一种非常简洁的数据交换格式,甚至比JSON更加简洁,但由于浏览器并没有内置组件支持以及语法上远远不如JSON对JavaScript工程师的亲和力,它更多的是用在其它脚本语言,比如Python、Perl等。
GitHub有一个名为js-yaml的项目建议关注一下。
XMLHttpRequest
XMLHttpRequest提供了对同域接口的请求功能,并且如果远程服务器允许还能够跨域请求,右侧示例是最简单的请求代码。需要注意的是:旧版本IE浏览器需要通过ActiveXObject来建立XMLHttpRequest对象。
使用XMLHttpRequest对象,我们需要关注readyState以及status两个属性。
readyState
该属性保存了当前XMLHttpRequest组件正处于什么阶段,它的取值为5个,分别是:
- 0 组件已经建立,但尚未开始调用open准备请求
- 1 已经调用open函数开始为请求做准备,当调用send发送请求的过程它的值也为1
- 2 已经发送完请求,并且接收到全部响应数据
- 3 正在解析响应数据,Content-Type的值将会决定数据应该被如何处理
- 4 数据已经解析完毕,应用程序可以使用这些数据了因此,如果在open函数调用后绑定onreadystatechange事件,直到数据准备就绪,该事件会被回调3次,这也是为什么要判断readyState必须为4的原因。
status
该属性代表服务器返回的Http状态码,正常情况下应该是200,如果为0则表示被中断或者网络连接不通。
该值与Http协议定义的状态码完全一致。
注意:
- 当请求方式为POST并且数据格式为查询字符串,那么我们还需要调用setRequestHeader将请求体的内容格式设置为application/x-www-form-urlencoded。
- 所有发送以及接收的数据,按照惯例应该皆为UTF-8格式。
- GET方式可能会有缓存,如果不希望从缓存中读数据,可以为URL追加时间戳。
同源策略与跨域
同源策略
同源策略是由Netscape提出来的一种广泛应用至今的安全策略,它规定必须符合域名、端口、协议完全相同的页面之间才能够互相访问对方的数据,包括BOM、后端接口等,所有支持JavaScript的浏览器都遵守同源策略,这也是出现跨域问题的原因。
跨域
由于同源策略的限制,不同源的网页之间无法互相访问DOM节点、后端接口,这就是所谓的跨域问题。有许多方法可以解决跨域问题,但都是基于两个站点的页面是互相信任并且建立起通讯协议的情况考虑。
跨域问题一般体现在两方面,包括:
两个非同源的页面之间的数据传递
页面关系又分为不同窗口的两个页面以及A页面嵌套B页面两种情况A域下的页面需要请求B域下的数据接口
跨域页面数据通讯
同主域嵌套页面
修改domain为主域,比如a.domain.com修改为domain.com,即可互相访问浏览器对象模型。
不同主域嵌套页面
修改子页面的iframe的hash值
不同主域的页面
HTML5提供了postMessage/sendMessage等API允许从一个域发送消息到指定的域,另一个域下的网页可以通过onmessage监听消息并且做出处理。
JSONP
在跨域请求数据方面如果服务器支持可以通过XMLHttpRequest2.0规范直接进行跨域通讯,否则还可以通过请求发起方的后端程序做接口中转,另外JSONP协议也是当前应用最广泛的跨域通讯方式。
JSONP协议建立在网页允许跨域加载JavaScript脚本文件的前提下,具体步骤是:
- 请求发起方在页面添加一个script标签,地址是另一个域的接口地址
- 请求发起方通过接口地址的URL查询字符串传递参数,包括约定好的回调函数名称(比如callback)
- 响应方收到请求并且处理数据,生成一段调用回调函数的JavaScript代码并返回
- script标签加载到JavaScript代码后自动执行回调函数,将返回的数据传递给回调函数作为参数
比如:
- http://www.domain.com/a.html页面添加一个标签
<script src=“http://test.domain.com/b.php?callback=fun1&id=1”></script>
- http://test.domain.com/b.php接收请求并生成JavaScript代码返回。
fun({
code: 0,
data: 1234
});
数据推送
Http协议的连接类型
http1.0默认会使用短连接,浏览器每次向服务器发起一个请求都会全新建立一个连接,在请求服务结束后连接就会断开。
http1.1默认使用长连接,如图中所示,当第一个请求服务结束后双方并没有断开连接,而是利用该连接继续执行下一次请求,直到超时才会断开连接。
需要注意的是:同一个域名下浏览器会建立连接池并且会复用它们。
推送技术
尽管长连接能够让连接得到极大程度的复用,减少了建立连接的消耗,但Http请求本质上是由客户端主动向服务器端发起服务请求,无法完美地实现服务器向客户端推送数据的需求,而这种需求在在线IM之类需要服务器推送数据的场景中是非常重要的。
http轮询
客户端每隔一段时间就向服务器发送一次http请求获取最新的数据
flash socket
利用flash组件跟服务器建立一个socket连接并且长时间保持,并且提供API给JavaScript调用。
websocket
HTML5专门为推送技术实现的一套推送技术,使用了类Http的实现,完美解决推送问题。