JavaScript-浏览器
浏览器对象
window
window
对象不但充当全局作用域,而且表示浏览器窗口
1 |
|
innerWidth
/innerHeight
:浏览器窗口的内部宽度和高度- 内部宽高是指,去掉菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高.
- 对应的有
outerWidth
/outerHeight
,是浏览器整个窗口的宽高
navigator
navigator
对象表示浏览器信息,常用属性包括:
navigator.appName
: 浏览器名称navigator.appVersion
: 版本navigator.language
: 语言navigator.platform
: 操作系统类型navigator.userAgent
: 浏览器设定的User-Agent
字符串
1 |
|
注意:navigator
的值很容易被用户修改,所以JS读取出来的值不一定正确.另外很多初学者喜欢针对不同的浏览器写不同的代码,喜欢用if
判断浏览器版本:
1 |
|
这样做既不能保证数据准确,也难以维护.正确
的做法是:
1 |
|
screen
表示屏幕的信息
screen.width
screen.height
screen.colorDepth
: 颜色位数,比如8,16,24
location
表示当前页面的URL信息,获取URL的个个部分
1 |
|
还有两个常用方法
location.reload()
: 重新加载当前页面
localtion.assign()
:加载一个新页面(当前页跳转)
1 |
|
document
这个对象表示当前页面.
由于HTML在浏览器中以DOM
形式表示为树状结构,document
对象就是整个DOM树的根节点
.
DOM
(document object model 文档对象模型)DOM是一个编程接口,它将HTML或XML文档表示为一个树形结构,其中每个节点代表文档的一部分.
这样可以让开发者通过编程方式来访问和修改文档的内容,结构和样式.
比如:
1
2
3
4
5
6
7
8
9
<html>
<head>
<title>我的页面</title>
</head>
<body>
<h1>欢迎</h1>
<p>这是一个段落</p>
</body>
</html>在DOM中就会表示为:
1
2
3
4
5
6
7
8
9
10document
└── html
├── head
│ └── title
│ └── "我的页面"
└── body
├── h1
│ └── "欢迎"
└── p
└── "这是一个段落"
比如document
的title
属性是从HTML的<title>xxx</title>
读取的,但是可以动态改变
1 |
|
要查找DOM树的某个节点,需要从document
对象开始查找,最常用的查找的是根据ID
和Tag Name
.比如有如下html
1 |
|
1 |
|
document.cookie
该属性可以获取当前页面的cookie
.cookie是由服务器发送的K-V标识符.因为HTTP是无状态的,但是服务器要区分是哪个用户发过来的请求,就可以用cookie.当一个用户成功登录后,服务器发送一个cookie给浏览器,例如user=asfdf(加密的字符串)
,此后浏览器访问该网站时就会在请求头附上这个cookie,服务器根据cookie区别用户.另外cookie还可用于存储网站一些设置,比如语言等.
cookie
和session
cookie session 存储位置 存储在客户端(浏览器) 存储在服务端 安全性 容易被窃取和篡改 比较安全,因为数据在服务器上 存储容量 通常限制4kb 取决于服务端配置,一般可以存储更多数据 生命周期 客户端可以设置过期时间 以下情况会过期:
- 用户关闭浏览器
- 服务器端设置的过期时间到达
- 用户主动退出登录一般
- 敏感数据用Session
- 用户偏好设置用Cookie
1 |
|
由于JavaScript可以获取到cookie,而用户的登录信息通常也会存在cookie,这会产生安全隐患.因为HTML页面可以引入第三方JS,也就是第三方可以通过JS获取用户的cookie.为了避免这种情况,服务器端应该将cookie设置为httpOnly
.这样JS便不能获取cookie.
history
这个对象保存了浏览器的历史记录
,通过它的back()
和fowward()
方法,相当于用户点击”后退”或”前进”.
新手喜欢在登录页登录成功后调用history.back()
返回登录前页面,这是一个错误
的做法.
对使用AJAX动态加载的页面,如果希望页面更新时同时更新history
对象,应当使用history.pushState()
1 |
|
操作DOM
- 更新: 更新DOM节点的内容,相当于更新了DOM节点所表示HTML的内容
- 遍历: 遍历该DOM节点下的子节点,以便进一步操作
- 添加: 在该DOM节点下新增一个子节点,相当于动态增加了一个HTML节点
- 删除: 将该节点从HTML删除,包括它的子节点也一并删除
遍历
document.getElementById()
: 由于ID在HTML文档中是唯一
的,所以此方法可以定位唯一的DOM节点document.getElementByTagName()
/document.getElementByClassName()
总是返回一组
DOM节点,可以先定位父节点,再慢慢定位
1 |
|
querySelector()
/querySelectorAll()
: 需要了解selector语法
1 |
|
实际上DOM的节点有多种类型:
- Element: 最常见的节点类型,代表html的各种标签
- Comment: 就是注释
- CDATA_SECTION: 主要用于XML,用于存储包含特殊字符的文本无需转义
- Document: 就是整个HTML文档,也是DOM树的根节点
DOM节点称为
Node
,而我们上面操作的都是Element
类型的Node
更新
修改DOM节点一般使用:
innerHtml
: 可以设置HTML标签
1 |
|
innerText
/textContent
: 会对字符串自动编码,从而无法设置任何HTML标签
1 |
|
两者区别在于读取属性时,innerText
不返回隐藏元素的文本,而textContent
返回所有文本.
style
: 对应所有CSS,可以通过这个属性获取或设置CSS
1 |
|
插入
如果获取到的DOM节点是空的,比如<div></div>
,直接使用innerHtml = '<span>child</span>'
,就相当于插入了一个新的DOM节点.
但如果节点本身不为空,有两个办法插入:
appendChild
,把一个节点添加到父节点的最后一个子节点1
2
3
4
5
6
7<!-- 有这么一段html -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>1
2
3
4let
js = document.getElementById('js'),
list = document.getElementById('list')
list.appendChild(js);1
2
3
4
5
6
7<!-- HTML 会变成这样 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>如果要插入的节点
已经存在
当前的文档中,这个节点会从原来的位置删除
,再插入.(id唯一
)从零创建再插入:
1
2
3
4
5
6let
list = document.getElementById('list'),
haskell = document.createElement('p'); // 创建element类型的dom
haskell.id = 'haskell'; // 给dom添加id
haskell.innerText = 'Haskell'; // 给dom添加文本
list.appendChild(haskell); // 插入动态添加DOM节点可以实现很多功能,比如
1
2
3
4let d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }'; // 构建一个新的CSS元素
document.getElementsByTag('head')[0].appendChild(d) // 插入上面动态创建了一个
<style>
节点,然后把它添加到<head>
节点末尾,动态给document添加了新的CSS.inserBefore
: 把子节点插入到指定位置,parentElemnet.insertBefore(newElement, referenceElement)
,新的子节点会插入到referenceElement
之前1
2
3
4
5
6
7let
list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);所以使用
inserBefore
的关键在于拿到准确的参考节点,很多时候需要循环一个父节点的所有子节点1
2
3
4
5
6let
i, c,
list = document.getElementById('list');
for (i=0; i<list.children.length; i++) {
c = list.children[i]; // 拿到第i个子节点
}一个例子
有如下的html
1
2
3
4
5
6
7
8<!-- HTML结构 -->
<ol id="test-list">
<li class="lang">Scheme</li>
<li class="lang">JavaScript</li>
<li class="lang">Python</li>
<li class="lang">Ruby</li>
<li class="lang">Haskell</li>
</ol>需要把它们按字符串重新排序:
1
2
3
4
5
6
7
8
9
10
11
12let list = document.getElementById('test-list');
let lis = list.children;
let arr = [];
let m = new Map();
for(let e=0; e<lis.length; e++) {
arr.push(lis[e].innerText);
m.set(lis[e].innerText, lis[e]);
}
arr.sort()
for (let a of arr) {
list.appendChild(m.get(a)); // 如果该element已存在,则删除原有的再插入
}
删除
首先获取该节点本身
以及它的父节点
,然后调用父节点的removeChild
把自己删掉
1 |
|
注意:chidlren
是一个只读属性
,并且在它子节点变化时会实时更新
1 |
|
1 |
|
第二次remove报错是因为第一次remove后,children
实时变化,索引[1]已经不存在
表单操作
表单本身也是DOM树.
表单的输入框,下拉框可以接收用户输入,所以JS可以获得用户输入的内容,或者对一个输入框设置新的内容.
HTML表单的输入控件主要有以下几种:
- 文本框,对应的
<input type="text">
,用于输入文本; - 口令框,对应的
<input type="password">
,用于输入口令; - 单选框,对应的
<input type="radio">
,用于选择一项; - 复选框,对应的
<input type="checkbox">
,用于选择多项; - 下拉框,对应的
<select>
,用于选择一项; - 隐藏文本,对应的
<input type="hidden">
,用户不可见,但表单提交时会把隐藏文本发送到服务器。
获取值
对于text
,input
,password
,hidden
以及select
1 |
|
但对于单选和复选,value
返回的是HTML的预设值,也就是所有可选择项.要用checked
判断是否用户已选择
1 |
|
设置值
text
、password
、hidden
以及select
,直接设置value
就可以
单选或复选就把checked
设置为true
或false
1 |
|
HTML5控件
HTML5新增大量标准控件,常用的如date
,datetime
,datetime-local
,color
等,它们都是用<input>
标签
不支持HTML5的浏览器无法识别新的控件,会把他们当作type='text'
来显示.支持的将获得格式化的字符串
.例如,type='date'
类型的input
的value
将保证一个有效的YYYY-MM-DD
格式的日期,或者空字符串.
提交表单
JS可以以两种方式处理表单的提交.
通过
<form>
元素的submit()
方法提交一个表单,例如响应一个<button>
的click
事件,在js代码中提交1
2
3
4
5
6
7
8
9
10
11
12
13
14
15< form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSumbmitForm()">
Submit
</button>
</form>
<script>
function doSubmitForm() {
let form = document.getElementById('test-form');
// 这里可以修改form的input
// 提交form
form.submit()
}
</script>这种方式的缺点是扰乱了浏览器对form的正常提交.正常来说浏览器默认点击
<button type='submit'>
时提交表单,或者用户在最后一个输入框按回车键.因此就有了第二种方法响应
<form>
本身的onsubmit
事件.1
2
3
4
5
6
7
8
9
10
11
12
13
14<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
let form = document.getElementById('test-form');
// 可以在此修改form的input...
// 继续下一步:
return true; // 返回true来告诉浏览器继续提交,如果返回false,浏览器将不会继续提交,可用于检查用户输入出错的场景
}
</script>
<input type="hidden">
的妙用:
很多时候用户输入用户名和口令时,出于安全考虑不会传递明文,会把它转为MD5再传递
1 |
|
这个做法本身没什么问题,但是当用户输入了密码提交时,密码框会突然从几个*
变成32个*
(MD5有32个字符)
如果不想有这个效果:
1 |
|
文件操作
HTML表单中,可以上传文件的唯一控件就是<input type='file'>
**注意:**当一个表单包含<input type='file'>
时:
enctype
必须为multipart/form-data
method
必须为post
- 这样浏览器才能正确编码以
multipart/form-data
格式发送表单数据
出于安全考虑,浏览器只允许用户点击<input type='file'>
来选择本地文件,用JS给<input type='file'>
de value
赋值是没有任何效果的.当用户上传了某个文件后,JS也无法获得文件的真实路径.
通常上传的文件都是由后台服务器处理.JS可以在提交表单时对文件扩展名做检查,凡是用户上传无效的格式
1 |
|
File API
由于JS对用户上传的文件操作非常优先,尤其无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现.
随着HTML5的普及,新增的File API允许JS读取文件内容,获得更多信息.
HTML5的File API提供了File
和FileReader
两个主要对象,可以获得文件信息并读取文件.
下面的例子演示如何读取用户上传的图片并在一个div中显示.
1 |
|
以DataURL
形式读取到的文件是一个字符串,类似data:image/jpeg;base64,/9j/4AAQSk...(base64编码)...
,常用于设置图像.如果需要服务端处理,把字符串base64,
后面的字符发送给服务端,并用base64解码就可以得到原始文件的二进制内容.
回调
上面的代码还演示了JS的一个重要特性就是单线程执行模式
.
浏览器的JS执行引擎在执行JS代码时,总是单线程模式执行.任何时候JS都不可能同时有多于一个线程执行.
JS中的多任务是通过异步
来实现的,比如上面的
1 |
|
就会发起一个异步操作来读取文件内容.因为是异步操作
,所以我们在JS中就不知道什么时候操作结束,因此需要先设置一个回调函数.
1 |
|
当文件读取完成后,JavaScript引擎将自动调用我们设置的回调函数。执行回调函数时,文件已经读取完毕,所以我们可以在回调函数内部安全地获得文件内容。
AJAX
AJAX是一个缩写: Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求.
如果仔细观察一个Form的提交,你就会发现,一旦用户点击“Submit”按钮,表单开始提交,浏览器就会刷新页面,然后在新页面里告诉你操作是成功了还是失败了。如果不幸由于网络太慢或者其他原因,就会得到一个404页面。
这就是Web的运作原理:
一次HTTP请求对应一个页面
。如果要让用户留在当前页面中,同时发出新的HTTP请求,就必须用JavaScript发送这个新请求,接收到数据后,再用JavaScript更新页面,这样一来,用户就感觉自己仍然停留在当前页面,但是数据却可以不断地更新。
最早大规模使用AJAX的就是Gmail,Gmail的页面在首次加载后,剩下的所有数据都依赖于AJAX来更新。
用JavaScript写一个完整的AJAX代码并不复杂,但是需要注意:AJAX请求是异步执行的,也就是说,要通过
回调函数
获得响应。
现在浏览器写AJAX主要依靠XMLHttpRequest
对象,另外还提供了原生支持的Fetch API以Promise
(下面会说到)方式提供.使用Fetch API发送HTTP请求代码如下:
1 |
|
使用Fetch API 配合async写法,代码更简单.
Fetch API详细用法可以参考MDN文档
安全限制
上面的代码使用./content.html
相对路径.如果你把它改为https://baidu.com
,在运行就会报错.在chrome的浏览器控制台还可以看到错误信息.
这就是浏览器的同源策略
:
默认情况下,JS发送AJAX请求时,URL域名必须和当前页面
完全一致.
完全一致的意思是:
- 域名:
www.example.com
和example.com
是两个域名- 协议:
http
和https
是不同协议- 端口号也要相同
那如果想通过JS去请求外域
可以怎么做呢?
通过Flash插件发送HTTP请求,这种方式可以绕过安全限制,但是Flash已经淘汰了.
通过同源域名下假设代理服务器转发,JS把请求发送到代理服务器
1
'/proxy?url=https://www.sina.com.cn'
称为
JSONP
,有个限制,只能用GET
请求,且要求返回JS.因为浏览器允许跨域引用JS资源.1
2
3
4
5
6
7
8
9<html>
<head>
<script src="http://example.com/abc.js"></script>
...
</head>
<body>
...
</body>
</html>JSNOP通常以函数形式返回,例如但会一个foo
1
foo('data');
这样一来,我们如果在页面先准备好
foo()
函数,然后给页面动态加一个<script>
节点,相当于动态读取外域JS资源,然后就等着接收回调.
CORS
支持HTML5的现代浏览器有新的跨域策略可以使用: CORS
全称Cross-Origin Resource Sharing
,是HTML5规范定义的如何跨域访问资源
Origin表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查
Access-Control-Allow-Origin
是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。
1 |
|
只要返回的响应头里的Access-Control-Allow-Origin
包含本域http://my.com
或者*
,本次请求就能成功.
也就是说决定权在对方手上,就看它愿不愿意返回一个正确的Access-Control-Allow-Origin
.
目前新的浏览器都支持CORS,也就是除了JS和CSS外,所有外域资源都要验证CORS.比如你引用某个第三方CDN字体文件
1 |
|
上面的这些跨域请求,称为简单请求
,包括:
- GET
- HEAD
- POST: content-type类型限制为
application/x-www-form-urlencoded
multipart/form-data
text/plain
- 并且不能出现任何
自定义头
而对于PUT
和其他类型如application/json
的POST
请求,在发送AJAX之前,浏览器会先发送一个OPTIONS
请求(称为preflighted请求
)到这个URL上,询问目标服务器是否接收
1 |
|
服务器必须响应并明确指出明确的允许的Methods
1 |
|
浏览器确认服务器响应的Access-Control-Allow-Methods
头确实包含将要发送的AJAX请求的Method,才会继续发送AJAX,否则,抛出一个错误。
由于以POST
、PUT
方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST
和PUT
请求,服务器端必须正确响应OPTIONS
请求。
Promise
JS中,所有代码都是但线程执行,着导致JS的所有网络操作,浏览器事件,都必须是异步执行.异步执行可以用回调函数
实现.
1 |
|
setTimeout()
做了两件事:
- 注册了一个定时器
- 告诉JS引擎在1000毫秒后把callback函数放入任务队列
这也称为
定时器回调
,类似的还有事件回调
比如上面的读文件和提交表单
浏览器console输入如下:
1 |
|
AJAX异步操作: 传统XMLHttpRequest
方式定义
1 |
|
把回调函数success(request.responseText)
和fail(request.status)
写到一个AJAX操作里,这种方式已经很少使用.不好看也不利于代码复用.
前置概念
- 同步: 代码按顺序一行一行执行,上一行代码执行完毕才能执行下一行
- 异步: 代码的执行不需要等待上一个任务的完成.例如网络请求,定时器等.异步操作不会阻塞主线程执行.
JS是单线程语言,意味着它一次只能执行一个任务.为了处理耗时的异步操作而不阻塞主线程,JS使用事件循环和回调函数机制.Promise是建立在这个机制之上的更高级的异步处理方案.
基本概念
Promise有三种状态:
- Pending: 初始状态,异步操作正在进行中
- fulfilled: 异步操作成功完成
- rejected: 异步操作失败
Promise对象提供then()
和catch()
方法来处理异步操作的结果或错误.
then() 和 catch() 是 Promise 对象上用于处理异步操作结果的关键方法。它们允许你指定回调函数,分别在 Promise
完成(fulfilled)
或拒绝(rejected)
时执行
then()
接收一个或两个参数:
- 第一个参数(
onFulfilled
): 一个回调函数,当promise完成(fulfilled)时调用.这个回调函数会接收Promise的完成值作为参数- 第二个参数(
onRejected
)(可选): 一个回调函数,当Promise拒绝(rejected)时调用.这个回调函数会接收Promise的拒绝原因(通常是一个Error对象)作为参数catch() 方法是
then(null, onRejected)
的简写形式,它专门用于处理 Promise 的拒绝(rejected)状态。它接受一个回调函数作为参数,当 Promise 拒绝时调用。
resolve()
和rejected()
是JS Promise API提供的一个内部函数.当你使用new Promise()
构造函数创建一个Promise,Promise会将resolve
和reject
两个函数作为参数传递给你的执行器函数
1
2
3
new Promise((resolve, reject) => {
// ...你的异步操作代码...
});
resolve()
: 一个函数,用于将Promise状态从pending改为fulfilled(已完成).你需要在异步成功完成后调用resolve()
,并将操作的结果作为参数传递给它.例如resolve('操作成功')
或resolve({data: someData})
reject()
: 一个函数,用于将Promise的状态从pending改为rejected(已拒绝).你需要在异步操作失败时调用reject()
,并将错误原因(一般是一个Error
对象)作为参数传递给它.例如:reject(new Error("操作失败"))
.
1 |
|
链式调用
因为then()
方法返回一个新的Promise对象,因此可以实现链式调用,异步操作更加清晰和易于维护
1 |
|
asyncOperation1()
: 调用asyncOperation1()函数,这个函数返回一个Promise
,这个Promise在1秒后resolve为操作1完成
..then((result1) => asyncOperation2(result1))
: 第一个.then()方法被调用,它的回调函数(result1) => asyncOperation2(result1)
会在asyncOperation1()
返回Promise fulfilled
后执行.
result1
:asyncOperation1()
的Promise resolve值(操作1完成
)会被作为参数传递给第一个.then()
的回调函数,这里的result1
只是自定义的一个变量名,它会被自动赋值,你把它改为其他名字一样的作用.asyncOperation2(result1)
: 回调函数内部调用了asyncOperation2
函数.并将result1
(也就是”操作1完成”)作为参数传递给它.asyncOperation2
也会返回一个Promise,这个Promise在1.5秒后resolve为result1 + ', 操作2完成'
,也就是”操作1完成,操作2完成”.then((result2) => console.log(result2))
: 第二个.then()
方法被调用.它的回调函数(result2) => console.log(result2)
会在第一个.then()
返回新的Promise fulfilled后执行.
result2
:asyncOperation2()
返回的Promise resolve的值(“操作1完成,操作2完成”)会被作为参数传给第二个.then()
的回调函数,赋值给result2
,同样,名字随意.console.log(result2)
: 打印result2
的值.catch((error) => console.error(error)))
: 用于捕获Promise链中任何一个Promise出现rejected
状态的情况.
总结: 理解Promise链的关键在于理解:
.then()
方法会获取上一个Promise对象的resolve返回,并把它作为参数传递给.then()
的回调函数
- 如果上一个Promise内
reject()
了,那么.then()
的第一个回调函数就不会被执行,代码跳到链上的下一个.catch()
或者直接抛出错误(如果没有.catch()
) .then()
方法不仅是处理 Promise 完成后的结果,更是构建 Promise 链、实现异步操作顺序执行的关键。 它通过接收前一个 Promise 的结果作为输入,来实现异步操作之间的依赖关系,让后续操作建立在前面的操作结果之上
但是其实可以看到Promise链理解起来十分复杂,写起来也一样复杂,大多数情况下,要使用async/await
,这是针对Promise的语法糖,它使得异步代码看起来更像同步代码,从而大大简化代码的复杂度喝可读性.只有在一些非常特殊的情况下(例如,需要非常精细地控制 Promise 的执行顺序,或者与一些老旧的库进行交互),你才可能需要编写更复杂的 Promise 链。 而大多数情况下,应该尽量避免复杂的 Promise 链,而选择更清晰和易于维护的方法。
async
在JS中,async关键字用于声明一个异步函数.异步函数总是返回一个Promise对象,即使函数体没有显示地返回一个Promise.
特点:
- 总是返回一个Promise: 一个 async 函数无论是否显式返回一个 Promise,都会隐式地返回一个 Promise。如果 async 函数返回一个值,这个值会被
Promise.resolve()
包装成一个 fulfilled 的 Promise;如果 async 函数抛出一个异常,则返回一个 rejected 的 Promise。 await
关键字:async
关键字内部可以使用await
关键字,暂停函数的执行,知道一个Promise完成(fulfilled或rejected).这使得异步代码更容易阅读和编写,看起来更像同步代码.- 清晰的错误处理: 使用
try...catch
直接不过异步操作中可能发生的错误. - 简化异步操作:
async/await
使得处理多个异步操作变得更加简单和易于理解,避免promise链的嵌套和复杂.
1 |
|
- myAsyncFunction 是一个 async 函数。
- await 关键字用于等待 someAsyncOperation1 和 someAsyncOperation2 异步操作完成。
- try…catch 块用于处理潜在的错误。
- 函数最终返回一个 Promise。
async
函数的本质是对Promise的语法糖.它使得异步操作得编写简洁易懂,底层仍然依赖于Promise.所以你可以看到,使用
async/await
来编写异步代码更加便捷易懂,但是它返回的永远是一个Promise对象.所以依旧需要then
和catch
来处理成功的结果/可能发生的错误
错误返回
1 |
|
上面的错误处理,return null;
和throw error;
是两种截然不同的操作.
return null
只是简单地返回一个null,不会中断Promise链的执行,也不会触发
.catch()
,return null
只是改变了Promise的resolve值,但Promise的状态仍然是fulfilled
throw error;
则会将错误抛出,把promise的状态改为rejected,从而触发
.catch()
方法.这个错误也能被调用方用try...catch
来捕获.触发了throw error
会中断Promise链. 更准确地说,它会将 Promise 的状态设置为 rejected,并且会停止后续 .then() 方法中 第一个参数(onFulfilled) 回调函数的执行。 错误会沿着 Promise 链向上“冒泡”,直到遇到一个 .catch() 方法来处理它,或者最终导致程序报错(如果没有 .catch() 处理)。
下面是一个用try...catch
捕获threw error
的例子
1 |
|
从上面的例子也能看出,async
定义的异步函数可以通过另一个异步函数
(main)来调用,并使用try...catch
来捕获错误.从而避免了.then()
和.catch()
的使用.这是一种更优雅更易的方式.但是要谨记,这些都只是语法糖,底层依旧是Promise对象.
Canvas
是HTML5的一个组件,提供一个可以用来绘制2D图形的位图.它本质上是一个可以被JS代码操作的画布.你可以用JS在它上面绘制各种图形,图像和文字.
主要用途:
- 图形绘制
- 图像处理: 缩放,选装,裁剪,滤镜等
- 动画制作: 通过不停更新canvas的内容创建动画效果
- 游戏开发: 是很多2D游戏的开发基础
- 数据可视化
局限性:
- 性能: 非常复杂的图形和动画,推荐使用WebGL(一种基于canvas的3D图形库)
- DOM操作: canvas本身不是DOM元素,所以不支持DOM API来操作.
- 矢量图: 不支持矢量图,矢量图缩放不会失真.所以Canvas绘制的图形缩放会失真.SVG是处理矢量图更好的选择.