BOM模型

385 阅读11分钟

概述

script元素

工作原理

网页加载流程

  • 浏览器一边下载HTML网页,一边开始解析

  • 解析中若发现<script>标签,就暂停解析,把网页渲染的控制权交给js引擎

  • <script>标签引用了外部脚本,就下载该脚本再执行

  • js引擎执行完毕后,把控制权交还渲染引擎,渲染引擎恢复往下解析HTML网页

阻塞:如果外部脚本加载时间长,浏览器会一直等待,造成网页长时间失去响应,呈现假死状态。

<script src="a.js"></script>
<script src="b.js"></script>
<!-- 上述两个脚本下载是同时的,运行顺序则是按从上到下执行,保证了脚本之间的互相依赖关系 -->

注意:解析执行CSS时,也会产生阻塞。

对于来自同一个域名的资源(脚本、样式表、图片文件),浏览器一般有限制,同时最多下载6~20个资源。即最多同时打开的TCP连接有限制,防止对服务器造成太大压力。

所以通常把静态文件放在不同的域名下,以加快下载速度。

defer属性

延迟脚本的执行,等到DOM加载生成后,再执行脚本。解决了脚本文件下载阻塞网页渲染的问题。

<script src="a.js" defer></script>

运行流程

  1. 浏览器开始解析HTML网页
  2. 过程中发现带有defer属性的script标签
  3. 继续往下解析HTML网页,同时并行下载script标签所加载的外部脚本
  4. 浏览器完成解析网页,再回头执行下载完的脚本文件

async属性

使用另一个进程去下载脚本,不阻塞渲染。

注意:有别于defer,无法保证脚本的执行顺序,遵循先下载完的先执行。

<script src="a.js" async></script>

运行流程

  1. 浏览器解析HTML网页
  2. 过程中发现带有async属性的script标签
  3. 浏览器继续往下解析同时,并行下载script标签中的外部脚本
  4. 脚本下载完成,浏览器暂停解析HTML网页,执行下载的脚本
  5. 脚本执行完毕,浏览器恢复解析HTML网页

上述两者的使用取舍

  1. defer首先完成网页的解析再执行脚本文件,而async当脚本下载完毕就会去执行
  2. defer中的脚本文件遵循自上而下执行顺序,async遵循下载脚本文件完成先后顺序去执行
  3. 两者都不会阻塞页面

浏览器的组成

核心:渲染引擎(内核)和js解释器(js引擎)

渲染引擎

将网页代码渲染为用户视觉可以感知的平面文档。

流程

  1. 解析代码:HTML解析为DOMCSS解析为CSSOM
  2. 对象合成:将DOMCSSOM合成一棵render tree
  3. 布局:计算出渲染树的布局
  4. 绘制:将渲染树绘制到屏幕上

注意:以上四步并非严格按顺序执行,往往第一步还没完成,第二、三步就开始了。

重流和重绘

  • 渲染树转换为网页布局,称为布局流。

  • 布局显示到页面的过程,称为绘制。

特点:都具有阻塞效应,并且会耗费很多时间和计算资源。

注意

  1. 页面生成后,脚本操作和样式表操作、用户互动等,都会触发重流和重绘

  2. 重流(DOM)和重绘(CSS)不一定并行。重流必然导致重绘,重绘不一定需要重流。

  3. 改变元素颜色,只会重绘,不会重流

var foo = document.getElementById('foobar');
foo.style.color = 'blue';
foo.style.marginTop = '30px';
// 浏览器会累积DOM变动,一次性执行,所以上述只会导致一次重绘

优化技巧

  • 使用documentFragment操作DOM

  • 动画使用absolutefixed定位,减少对其他元素的影响

  • 只在必要时才显示隐藏元素

  • 使用window.requestAnimationFrame(),把代码推迟到下一次重流时执行,而不是立即要求页面重流

  • 使用虚拟DOM

js引擎

读取网页中js代码并处理运行。

window对象

window.screenX window.screenY

分别返回浏览器窗口左上角相对于当前屏幕左上角的水平距离和垂直距离像素,只读。

window.scrollX window.scrollY

返回页面的水平(垂直)滚动距离像素,只读。

组件属性

  • window.locationbar:地址栏对象
  • window.menubar:菜单栏对象
  • window.scrollbar:窗口滚动条对象
  • window.toolbar:工具栏对象

全局对象属性

  • window.document:指向document对象
  • window.location:获取当前窗口的URL信息
  • window.navigator:获取环境信息
  • window.history:表示浏览器的浏览历史
  • window.localStorage:指向本地储存的localStorage数据
  • window.sessionStorage:指向本地储存的sessionStorage数据
  • window.screen:表示屏幕信息

Screen对象

  • Screen.height:浏览器窗口所在的屏幕高度,一般是常值

  • Screen.width:浏览器窗口所在的屏幕宽度

  • Screen.availHeight:浏览器窗口可用的屏幕高度

  • Screen.availWidth:浏览器窗口可用的屏幕宽度

// 根据屏幕宽度变化,将用户导向不同网页
if ((screen.width <= 800) && (screen.height <= 600)) {
  window.location.replace('small.html');
}else {
  window.location.replace('wide.html');
}

Navigator对象

Navigator.geolocation

返回用户地理位置信息

  • Geolocation.getCurrentPosition() 用户当前位置
  • Geolocation.watchPosition() 监听用户位置变化
  • Geolocation.clearWatch() 取消watchPosition()方法指定的监听函数

window对象方法

window.alert() window.prompt() window.confirm()

window.open() window.close() window.stop()

var popup = window.open(
  'somepage.html',
  'DefinitionsWindows', // 新打开窗口的名字
  'height=200,width=200,location=no,status=yes,resizable=yes,scrollbars=yes'
);

window.moveTo() window.moveBy()

  • 移动浏览器窗口到指定位置
  • 将浏览器窗口移动到一个相对位置
// 窗口左上角距离屏幕左上角的水平和垂直距离
window.moveTo(100, 200)
// 窗口左上角向右移动的水平距离和向下移动的垂直距离
window.moveBy(25, 50)

window.scrollTo() window.scrollBy()

  • 将文档滚动到指定位置
  • 将网页滚动指定距离
// 水平向右,垂直向下滚动的像素
window.scrollTo(x-coord, y-coord)
// 将网页向下滚动一屏
window.scrollBy(0, window.innerHeight)

window.focus() window.blur()

事件

onload事件

window.onload = function() {
    // ...
};

History对象

保存了当前窗口访问过的所有页面网址。

window.history.length // 3
// 后退到前一个网址 == 前进-1
history.back()
history.go(-1)

属性

  • History.length 当前窗口访问过的网址数量
  • History.state History堆栈最上层的状态值

方法

History.back() History.forward() History.go()

用于在历史之中移动:

  • History.back() 移动到上一个网址
  • History.forward() 移动到下一个网址
  • History.go() 接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址

History.pushState()

用于在历史中添加一条记录。

window.history.pushState(state, title, url)
  • state:一个与添加记录相关联的状态对象,用于popstate事件
  • title:新页面的标题
  • url:新的网址
var stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2', './2.html');

添加新记录后,浏览器地址栏立刻显示example.com/2.html,不会跳到2.html,甚至不会检查2.html是否存在,它只是成为浏览历史中的一条最新记录。

总结pushState()不会触发页面刷新,只是导致History发生变化,地址栏会有反应。

Cookie

  • 服务器保存在浏览器的一小段文本信息,大小一般不能超过4KB

  • 浏览器每次向服务器发出请求,会自动附上这段信息

用途

  1. 用来分辨两个请求是否来自同一个浏览器
  2. 用来保存一些状态信息(对话管理、保存登录、购物车记录的信息)
  3. 个性化:保存用户的偏好
  4. 追踪:记录分析用户行为

组成

  • 名字
  • 值(数据)
  • 到期时间
  • 所属域名
  • 生效的路径

举例:用户访问网址www.example.com,服务器在当前浏览器上写入一个Cookie,其包含了www.example.com域名以及根路径/。意味着,这个Cookie 对该域名的根路径和它的所有子路径都有效。如果路径设为/forums,那么这个Cookie 只在访问www.example.com/forums及其子路径时才有效。浏览器一旦访问这个路径,就会附上这段 Cookie发送给服务器。

浏览器可以设置不接受Cookie,也可设置不向服务器发送 Cookie

document.cookie属性返回当前网页的Cookie

CookieHTTP协议

CookieHTTP协议生成,也主要供HTTP协议使用。

Cookie生成

服务器如果希望在浏览器保存Cookie,就要在HTTP 回应的头信息里,放置一个Set-Cookie字段。

Set-Cookie:foo=bar

Cookie发送

浏览器向服务器发送HTTP请求时,每个请求都会带上相应的Cookie

把服务器早前保存在浏览器的这段信息,再发回服务器,使用HTTP头信息的Cookie字段。

// 向服务器发送一个名为foo,值为bar的cookie
Cookie: foo=bar
Cookie: name=value; name2=value2; name3=value3

服务器收到浏览器发来的Cookie时,有两点是不知道的

  • Cookie的各种属性,何时过期
  • 哪个域名设置的Cookie,到底是一级域名,还是二级域名

Cookie的属性

document.cookie

读写当前网页的Cookie

document.cookie // "foo=bar;baz=bar"
var cookies = document.cookie.split(';');

for (var i = 0; i < cookies.length; i++) {
  console.log(cookies[i]);
}
// foo=bar
// baz=bar
document.cookie = 'test1=hello';
document.cookie = 'test2=world';
document.cookie // test1=hello;test2=world

document.cookie一次可以读出全部Cookie,但是只能写入一个Cookie

删除现存Cookie的唯一方法是设置它的expires属性为一个过去的日期。

浏览器数据储存机制

window.sessionStoragewindow.localStorage

  • sessionStorage保存的数据用于浏览器的一次会话,当会话结束(窗口关闭),数据被清空
  • localStorage保存的数据长期存在,下次访问该网站的时候,网页可直接读取以前保存的数据
  • Cookie的强化版,能够使用更大的存储空间

属性和方法

window.localStorage.setItem('foo', 'a');
window.localStorage.setItem('bar', 'b');
window.localStorage.setItem('baz', 'c');
window.localStorage.length // 3

Storage.setItem()

存入数据。

window.sessionStorage.setItem('key', 'value');
window.localStorage.setItem('key', 'value');

Storage.getItem()

读取数据。

window.sessionStorage.getItem('key')
window.localStorage.getItem('key')

Storage.removeItem()

清除某个键名对应的键值。

sessionStorage.removeItem('key');
localStorage.removeItem('key');

Storage.clear()

清除所有保存的数据。

window.sessionStorage.clear()
window.localStorage.clear()

同源策略

是浏览器安全的基石,保证了用户信息安全,防止恶意网站窃取数据。

概念

A网页设置的CookieB网页不能打开,除非这两个网页同源。

同源的三个相同:

  • 协议相同
  • 域名相同
  • 端口相同

栗子:http://www.example.com/dir/page.html

  • 协议是http://
  • 域名是www.example.com
  • 端口是80(默认端口可以省略)

Cookie

是服务器写入浏览器的一小段信息,只有同源的网页才能共享。

ajax

同源规定,AJAX请求只能发给同源的网址,否则报错。

除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务)外,有三种方法规避这个限制:

  • JSONP
  • websocket
  • CORS

JSONP

特点:简单适用,老式浏览器全部支持,服务端改造小。

基本思想:网页通过动态添加一个script元素,向服务器请求JSON数据,这种做法不受同源政策限制。服务器收到请求后,将数据放在一个指定名字的回调函数中传回来。

WebSocket

是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。

该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

上面代码中,字段Origin表示该请求的请求源,即发自哪个域名。服务器根据这个字段,判断是否许可本次通信。

CORS

跨源资源分享,属于跨源AJAX 请求的根本解决方法。

  • 相比JSONP只能发GET请求,CORS允许任何类型的请求

  • 需要浏览器和服务器同时支持

  • 整个CORS通信过程都是浏览器自动完成的,不需要用户参与

  • 实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨域通信

两种请求

CORS请求分为两类:简单请求和非简单请求。

简单请求

  • 请求方法为以下三种之一:HEADGETPOST

  • HTTP的头信息不超出以下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不同时满足上述两个条件的,都属于非简单请求。

在简单请求中,浏览器直接发出CORS请求,在头信息中增加一个Origin字段。

例子:浏览器发现这次跨域AJAX请求是简单请求,自动在头信息中添加一个Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

服务器返回:

Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
  1. Access-Control-Allow-Origin

接受任意域名的请求。

  1. Access-Control-Allow-Credentials

可选,表示是否允许发送Cookie

默认情况下,Cookie不包括在CORS请求中。设为true,表示服务器明确许可,浏览器可以把 Cookie包含在请求中,一起发给服务器。

非简单请求

对服务器提出特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

  1. 预检请求

非简单请求的CORS请求会在正式通信之前,增加一次HTTP 查询请求,称为预检请求。

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

上面代码中,HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header