浏览器模型
浏览器环境
JavaScript 是浏览器的内置脚本语言。
也就是说,浏览器内置了 JavaScript 引擎,并提供了各种接口,让 JavaScript 脚本可以控制浏览器的各种功能。
代码嵌入网页
<script>
元素直接嵌入代码
<script>var x = 1 + 5; console.log(x);</script>
有一个 type 属性,来指定脚本类型,有两个值
1、text/javascript
:默认值,适用于老式浏览器
2、application/javascript
:较新的浏览器
如果取其他值,浏览器不认识,就不会执行其中的代码
<script>
标签加载外部脚本
<script src="https://www.example.com/script.js"></script>
如果脚本文件使用了非英语字符,应该注明字符的编码
<script charset="utf-8" src="https://www.example.com/script.js"></script>
为了防止攻击者篡改外部脚本,script 标签允许设置一个 integrity 属性,写入该外部脚本的 Hash 签名,用来验证脚本的一致性。
<script
src="/assets/application.js"
integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="
></script>
事件属性
网页元素的事件属性如 onclike,可以写入 JavaScript 代码
<button id="myBtn" onclick="console.log(this.id)">
点击
</button>
URL 协议
URL 支持javascript:
协议,即在 URL 的位置写入代码,使用这个 URL 的时候就会执行 JavaScript 带啊吗
<a href="javascript:console.log('Hello')">点击</a>
浏览器的地址栏也可以执行javascript:
协议。将javascript:console.log('Hello')
放入地址栏,按回车键也会执行这段代码。
浏览器组成
浏览器的核心是两部分:渲染引擎和 JavaScript 解释器(又叫 JavaScript 引擎)
渲染引擎的主要作用是,将网页代码渲染为用户视觉可以感知的平面文档
JavaScript 引起的主要作用是,读取网页中的 JavaScript 代码,对其处理后运行
window 对象
当前的浏览器窗口,是当前页面的顶层对象,即最高一层的对象,所有其他对象都是它的下属
Navigator 对象,Screen 对象
window.navigator
属性指向一个包含浏览器和系统信息的 Navigator 对象,脚本通过这个属性了解用户的环境信息。
Screen 对象表示当前窗口所在的屏幕,提供显示设备的信息。window.screen
属性指向这个对象。
Cookie
Cookie 是服务器保存在浏览器的一小段文本信息,一般大小不能超过 4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。
Cookie 是按照域名区分的,foo.com 只能读取自己放置的 Cookie,无法读取其他网站(比如 bar.com)放置的 Cookie。一般情况下,一级域名也不能读取二级域名留下的 Cookie,比如 mydomain.com 不能读取 subdomain.mydomain.com 设置的 Cookie。
但是有一个例外,设置 Cookie 的时候(不管是一级域名设置的,还是二级域名设置的),明确将 domain 属性设为一级域名,则这个域名下面的各级域名可以共享这个 Cookie。
Set-Cookie: name=value; domain=mydomain.com
区分 Cookie 时不考虑协议和端口。
XMLHttpRequest 对象
1999 年,微软公司发布 IE 浏览器 5.0 版,第一次引入新功能:允许 JavaScript 脚本向服务器发起 HTTP 请求。
这个功能当时并没有引起注意,直到 2004 年 Gmail 发布和 2005 年 Google Map 发布,才引起广泛重视。
2005 年 2 月,AJAX 这个词第一次正式提出,它是 Asynchronous JavaScript and XML
的缩写,指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档从中提取数据,再更新当前网页的对应部分,而不用刷新整个网页。
后来,AJAX 这个词就成为 JavaScript 脚本发起 HTTP 通信的代名词,也就是说,只要用脚本发起通信,就可以叫做 AJAX 通信。
W3C 也在 2006 年发布了它的国际标准。
具体来说,AJAX 包括以下几个步骤。
1、创建 XMLHttpRequest 实例
2、发出 HTTP 请求
3、接收服务器传回的数据
4、更新网页数据
概括起来,就是一句话,AJAX 通过原生的XMLHttpRequest对象发出 HTTP 请求,得到服务器返回的数据后,再进行处理
。现在,服务器返回的都是 JSON 格式的数据,XML 格式已经过时了,但是 AJAX 这个名字已经成了一个通用名词,字面含义已经消失了。
同源限制
同源:协议、域名、端口
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
限制范围
1、无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB
2、无法接触非同源网页的 DOM
3、无法向非同源地址发送 Ajax 请求(可以发送,但浏览器会拒绝响应)
Cookie
浏览器允许通过设置document.domain
共享 Cookie
A 网页的网址是http://w1.example.com/a.html
,B 网页的网址是http://w2.example.com/b.html
,那么只要设置相同的document.domain
,两个网页就可以共享 Cookie。因为浏览器通过document.domain
属性来检查是否同源。
// 两个网页都需要设置
// 因为设置document.domain的同时,会把端口重置为null
// 如果只设置一个网页的document.domain,会导致两个网址的端口不同,还是达不到同源的目的
document.domain = "example.com";
服务器也可以在设置 Cookie 的时候,指定 Cookie 的所属域名为一级域名,比如 example.com。
Set-Cookie: key=value; domain=example.com; path=/
这样的话,二级域名和三级域名不用做任何设置,都可以读取这个 Cookie。
AJAX
ajax 请求只能发送给同源的网址,否则就会报错
除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),有三种方法规避这个限制。
1、JSONP
2、WebSocket
3、CORS
JSONP
简单易用,没有兼容性问题,老式浏览器全部支持,服务端改造小
1、网页添加<script>
元素,向服务器请求一个脚本,请求的脚本网址有一个 callback 参数,用来告诉服务器,客户端的回调函数名称bar
<script src="http://api.foo.com?callback=bar"></script>
2、服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...})
)
3、客户端将服务器返回的字符串,作为代码解析,因为浏览器认为,这是<srcipt>
标签请求的脚本内容。只要客户端定义了回调函数,就能在函数体内,拿到服务器返回的 JSON 数据
WebSocket
WebSocket 是一种通信协议,使用ws://(非加密)
和wss://(加密)
作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信
浏览器发出的 WebSocket 请求的头信息示例
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
字段,表示请求的请求源,即发自哪个域名,正是因为有了 Origin 这个字段,服务器可以根据这个字段,判断是否许可本次通信,所以 WebSocket 才没有实行同源政策。
如果该域名在白名单内,服务器就会做出如下回应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
CORS
CORS:跨资源分享 Cross-Orifin Resource Sharing,是 W3C 标准,属于跨源 Ajax 请求的根本解决办法。
CORS 通信
CORS 需要浏览器和服务器同时支持,目前,所以浏览器都支持该功能
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与,浏览器一旦发现 Ajax 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感知。
因此,实现 CORS 通信的关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信
请求类型
CORS 请求分成两类:简单请求 simple request 和非简单请求 not-simple request
简单请求
满足以下两大条件,就属于简单请求。
1、请求方法是 HEAD、GET、POST 之一
2、HTTP 的头信息不超出以下几种字段,Accept
、Accept-Language
、Content-Language
、Last-Event-ID
、Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
如果 Origin 指定的源,不再许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段,就知道出错了,从而抛出错误,被 XMLHttpRequest 的 onerror 回调函数捕获。这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是 200。
如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段
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
Access-Control-Allow-Origin:该字段是必须的。要么是请求时的 Origin 字段的值,要么是一个
*
,表示接受任意域名的请求。Access-Control-Allow-Credentials:可选,是一个布尔值,表示是否允许发送 Cookie。默认 Cookie 不包括在 CORS 请求之中。设为 true,表示服务器明确许可,浏览器可以把 Cookie 包含在请求中,一起发送给服务器。
Access-Control-Expose-Headers:可选,CORS 请求时,XMLHttpRequest 对象的 getResponseHeader 方法只能拿到 6 个服务器返回的基本字段,如果想拿到其他字段,必须在这里指定
withCredentials 属性
服务器需要拿到 Cookie 时,不仅需要服务器显式指定Access-Control-Allow-Credentials
字段,还需要开发者在 Ajax 请求中打开withCredentials
属性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
如果服务器要求浏览器发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie。
非简单请求
非简单请求是那种对服务器提出特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是application/json
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为预检请求 preflight。
预检请求
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段,只有得到肯定答复,浏览器才会发出正式的 XMLHttpRequest 请求,否则就会报错。
这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器收到大量 DELETE 和 PUT 请求,这些传统的表单不可能跨源发出的请求。
预检请求用的请求方法是 OPTIONS,表示这个请求是用来询问的。
Origin
Access-Control-Request-Method:必须,列出浏览器的 CORS 请求会用到哪些 HTTP 方法
Access-Control-Request-Headers:是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。
预检请求的回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
一些字段:
Access-Control-Allow-Methods:必须,返回所有支持的方法,是为了避免多次“预检”请求
Access-Control-Allow-Headers:如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段
Access-Control-Allow-Credentials:
Access-Control-Max-Age:可选,用来指定本次预检请求的有效期,单位是秒。在此期间,不用发出另一条预检请求。
浏览器的正常请求和响应
一旦服务器通过了“预检”请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。
Storage 接口
Storage 接口用于脚本在浏览器保存数据。两个对象部署了这个接口:window.sessionStorage
和window.localStorage
。
sessionStorage
保存的数据用于浏览器的一次会话(session),当会话结束(通常是窗口关闭),数据被清空;localStorage
保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取以前保存的数据。
除了保存期限的长短不同,这两个对象的其他方面都一致。
History 对象
window.history
属性指向 History 对象,它表示当前窗口的浏览历史。
History 对象保存了当前窗口访问过的所有页面网址。
History.back()
History.forward()
History.go():接受一个整数作为参数,以当前网址为基准,移动到参数指定的网址,比如 go(1)相当于 forward(),go(-1)相当于 back()。如果参数超过实际存在的网址范围,该方法无效果;如果不指定参数,默认参数为 0,相当于刷新当前页面。
Location 对象,URL 对象,URISearchParams 对象
Location
Location 对象是浏览器提供的原生对象,提供 URL 相关的信息和操作方法。通过 window.location 和 document.location 属性,可以拿到这个对象。
URL
网页的 URL 只能包含合法的字符。合法字符分成两类。
URL 元字符:分号(;),逗号(,),斜杠(/),问号(?),冒号(:),at(@),&,等号(=),加号(+),美元符号($),井号(#)
语义字符:a-z,A-Z,0-9,连词号(-),下划线(_),点(.),感叹号(!),波浪线(~),星号(*),单引号('),圆括号(())
除了以上字符,其他字符出现在 URL 之中都必须转义,规则是根据操作系统的默认编码,将每个字节转为百分号(%)加上两个大写的十六进制字母。
JavaScript 提供四个 URL 的编码/解码方法。
- encodeURI():用于转码整个 URL,将元字符和语义字符之外的字符,都进行转义
encodeURI("http://www.example.com/q=春节");
// "http://www.example.com/q=%E6%98%A5%E8%8A%82"
- encodeURIComponent():用于转码 URL 的组成部分,会转码除了语义字符之外的所有字符,即元字符也会被转码,如果转码整个 URL 就会出错
encodeURIComponent("春节");
// "%E6%98%A5%E8%8A%82"
encodeURIComponent("http://www.example.com/q=春节");
// "http%3A%2F%2Fwww.example.com%2Fq%3D%E6%98%A5%E8%8A%82"
decodeURI()
decodeURIComponent()
URLSearchParams
URLSearchParams 对象是浏览器的原生对象,用来构造、解析和处理 URL 的查询字符串(即 URL 问号后面的部分)。会对查询字符串自动编码。
// 方法一:传入字符串
var params = new URLSearchParams("?foo=1&bar=2");
// 等同于
var params = new URLSearchParams(document.location.search);
// 方法二:传入数组
var params = new URLSearchParams([
["foo", 1],
["bar", 2],
]);
// 方法三:传入对象
var params = new URLSearchParams({ foo: 1, bar: 2 });
ArrayBuffer 对象,Blob 对象
ArrayBuffer(ES6 标准)
ArrayBuffer 对象表示一段二进制数据,用来模拟内存里面的数据。通过这个对象,JavaScript 可以读写二进制数据。这个对象可以看作内存数据的表达。
Blob
Blob 对象表示一个二进制文件的数据内容,比如一个图片文件的内容就可以通过 Blob 对象读写。它通常用来读写文件,它的名字是 Binary Large Object (二进制大型对象)的缩写。它与 ArrayBuffer 的区别在于,它用于操作二进制文件,而 ArrayBuffer 用于操作内存。
File 对象,FileList 对象,FileReader 对象
File
File 对象代表一个文件,用来读写文件信息。它继承了 Blob 对象,或者说是一种特殊的 Blob 对象,所有可以使用 Blob 对象的场合都可以使用它。
FileList
FileList 对象是一个类似数组的对象,代表一组选中的文件,每个成员都是一个 File 实例。它主要出现在两个场合。
文件控件节点(
<input type="file">
)的 files 属性,返回一个 FileList 实例。拖拉一组文件时,目标区的
DataTransfer.files
属性,返回一个 FileList 实例。
FileReader
FileReader 对象用于读取 File 对象或 Blob 对象所包含的文件内容。
浏览器原生提供一个 FileReader 构造函数,用来生成 FileReader 实例。
表单,FormData 对象
表单(<form>
)用来收集用户提交的数据,发送到服务器。
表单提供多种控件。
表单的内置验证
自动校验
表单提交的时候,浏览器允许开发者指定一些条件,它会自动验证各个表单控件的值是否符合条件。
<!-- 必填 -->
<input required>
<!-- 必须符合正则表达式 -->
<input pattern="banana|cherry">
<!-- 字符串长度必须为6个字符 -->
<input minlength="6" maxlength="6">
<!-- 数值必须在1到10之间 -->
<input type="number" min="1" max="10">
<!-- 必须填入 Email 地址 -->
<input type="email">
<!-- 必须填入 URL -->
<input type="URL">
如果一个控件通过验证,它就会匹配:valid
的 CSS 伪类,浏览器会继续进行表单提交的流程。如果没有通过验证,该控件就会匹配:invalid
的 CSS 伪类,浏览器会终止表单提交,并显示一个错误信息。
input:invalid {
border-color: red;
}
input,
input:valid {
border-color: #ccc;
}
checkValidity(),手动触发表单校验
// 触发整个表单的校验
// 返回一个布尔值,true表示通过校验,false表示没有通过校验
form.checkValidity();
willValidate 属性,布尔值,该控件是否提交时校验
validationMessage 属性,字符串,不满足校验条件时浏览器显示的文本
// HTML 代码如下
// <form><input type="text" required></form>
document.querySelector("form input").validationMessage;
// "请填写此字段。"
setCustomValidity(),定制校验失败时的报错信息
validity 属性,返回一个 ValidityState 对象,包含当前校验状态的信息
novalidate 属性,可以关闭浏览器的自动校验
<form novalidate></form>
<!-- 或者在脚本里 -->
form.noValidate = true;
<!-- 或者提交按钮(<button>或<input>元素)的formnovalidate属性 -->
<form>
<input type="submit" value="submit" formnovalidate />
</form>
enctype 属性,编码格式
表单能够用四种编码,向服务器发送数据。编码格式由表单的 enctype 属性决定
假设表单字段为:
foo:'bar',
baz:'
The first line.
The second line.
'
1、GET 方法。表单使用 get 方法发送数据,enctype 属性无效;
// 以URL查询字符串发出
?foo=bar&baz=The%20first%20line.%0AThe%20second%20line.
2、application/x-www-form-urlencoded(默认值)
如果表单用 post 方法发送数据,并省略 enctype 属性,数据以 application/x-www-form-urlencoded 格式发送
Content-Type: application/x-www-form-urlencoded
foo=bar&baz=The+first+line.%0D%0AThe+second+line.%0D%0A
3、text/plain
如果使用 post 发送数据,enctype 属性为 text/plain,那么数据将以纯文本格式发送
<form
action="register.php"
method="post"
enctype="text/plain"
onsubmit="AJAXSubmit(this); return false;"
></form>
Content-Type: text/plain foo=bar baz=The first line. The second line.
4、multipart/form-data
如果表单使用 post 方法,enctype 属性为 multipart/form-data,那么数据将以混合的格式发出。
这种格式也是文件上传的格式
<form
action="register.php"
method="post"
enctype="multipart/form-data"
onsubmit="AJAXSubmit(this); return false;"
></form>
Content-Type: multipart/form-data;
boundary=---------------------------314911788813839
-----------------------------314911788813839 Content-Disposition: form-data;
name="foo" bar -----------------------------314911788813839 Content-Disposition:
form-data; name="baz" The first line. The second line.
-----------------------------314911788813839--
FormData
表单数据以键值对的形式向服务器发送,这个过程是浏览器自动完成的。
但是有时候,希望通过脚本完成这个过程,构造或编辑表单的键值对,然后通过脚本发送给服务器。浏览器原生提供了 FormData 对象来完成这项工作。
IndexedDB API
IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。
IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
特点:
键值对储存
异步
支持事务
同源限制
储存空间大
支持二进制储存
Web Worker
JavaScript 采用的是单线程模型,也就是说,所有的任务只能在一个线程上完成,一次只能做一件事。
Web Worker,可以为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。
在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。
这样的好处就是,一些计算密集型或高延迟的任务可以交由 Worker 线程执行,主线程(通常负责 UI 交互)能够保持流畅,不会被阻塞或拖慢。
Worker 线程一旦新建成功,就会始终运行,不会被主线程上的活动(如用户点击按钮、提交表单)打断。这样有利于随时响应主线程的通信,但这也造成了 Worker 比较耗费资源,不应该过度使用,并且使用完毕,就应该关闭。
使用注意点:
同源限制
DOM 限制,无法获取主线程所在网页的 DOM 对象,也无法使用 document、window、parent 这些对象,但可以使用 navigator 和 location 对象
全局对象限制。Worker 的全局对象 WorkerGlobalScope,不同于网页的全局对象 Window,很多接口拿不到。
通信联系。Worker 线程和主线程不在同一个上下文环境,它们不能直接通信,必须通过消息完成
脚本限制。不能执行 alert 和 confirm 方法,但可以使用 XMLHttpRequest 对象发出 Ajax 请求
文件限制,Worker 线程无法读取本地文件,加载的脚本必须来自于网络