文章目录
数据类型
- 值类型(基本类型)
string(字符串)、number(数字)、boolean(布尔)、null(空)、undefined(未定义)、symbol(ES6引入的一种新的原始数据类型,表示独一无二的值) - 引用数据类型
object(对象)、array(数组)、function(函数)
== 和===,!=和!==,typeof和instanceof,null和undefined
运算符 | 描述 |
---|---|
== | 等于 |
=== | 绝对等于(值和类型均相等) |
!= | 不等于 |
!== | 不绝对等于(值和类型有一个不相等,或两个都不相等) |
- ===比较规则
① 如果类型不同,就一定不相等。
② 如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN())
③ 如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
④ 如果两个值都是true,或都是false,那么相等。
⑤ 如果两个值都引用同一个对象或函数,那么相等,否则不相等。
⑥ 如果两个值都是null,或都是undefined,那么相等。
- ==比较规则
① 如果两个值类型相同,进行 === 比较。
② 如果两个值类型不同,他们可能相等。根据下面规则进行类型转换再比较:
a) 如果一个是null,一个是undefined,那么相等。
b) 如果一个是字符串,一个是数值,则把字符串转换成数值再进行比较。
c) 如果任一值是true ,则把它转换成1再比较;如果任一值是false,则把它转换成0再比较。
d) 如果一个是对象,另一个是数值或字符串,则把对象转换成基础类型的值再比较。对象转换成基础类型,利用它的toString或者valueOf方法。js核心内置类,会尝试valueOf先于toString(Date例外,Date利用的是toString转换);非js核心的对象,比较麻烦,另说。
e) 任何其他组合,都不相等。
运算符 | 描述 |
---|---|
typeof | 用来判断数据类型,返回值(为字符串)有string、boolean、number、function、object、undefined |
instanceof | 用来检测该对象是不是另一对象的实例(在实例对象的原型链上是否存在构造函数的prototype属性) |
- 用typeof检测Array、Date、null,返回值为object;用typeof检测NaN,返回值为number;用typeof检测未定义变量,返回值为undefined。
注:typeof无法区分Array和Date。constructor属性返回对象的构造函数,可用来区分Array和Date。 - instanceof语法:
object instanceof constructor
null和undefined的区别
null和undefined值相等,但类型不等。
typeof undefined // undefined
typeof null // object
null === undefined // false
null == undefined // true
类型转换
Number()转换为数字,String()转换为字符串,Boolean()转换为布尔值。
原始值 | 转换为数字 | 转换为字符串 | 转换为布尔值 |
---|---|---|---|
false | 0 | “false” | false |
true | 1 | “true” | true |
0 | 0 | “0” | false |
1 | 1 | “1” | true |
"0" | 0 | “0” | true |
"000" | 0 | “000” | true |
"1" | 1 | “1” | true |
NaN | NaN | “NaN” | false |
Infinity | Infinity | “Infinity” | true |
-Infinity | -Infinity | “-Infinity” | true |
"" | 0 | “” | false |
"20" | 20 | “20” | true |
"Runoob" | NaN | “Runoob” | true |
[ ] | 0 | “” | true |
[20] | 20 | “20” | true |
[10,20] | NaN | “10,20” | true |
[“Runoob”] | NaN | “Runoob” | true |
[“Runoob”,“Google”] | NaN | “Runoob,Google” | true |
function(){} | NaN | “function(){}” | true |
{ } | NaN** | “[object Object]” | true |
null | 0 | “null” | false |
undefined | NaN | “undefined” | false |
常用内置对象
Object(自定义对象)、Array(数组对象)、Boolean(布尔对象)、Number(数字对象)、String(字符串对象)、Date(日期对象)、RegExp(正则表达式对象)、Function(函数对象)、Math(数学对象)、Global(全局对象)、Error(错误对象)
常用数组操作
方法 | 描述 |
---|---|
map() | 遍历数组,通过指定函数处理数组的每个元素,返回处理后的新数组。不对空数组进行检测,不改变原数组。 |
forEach() | 数组的每个元素都执行一次回调函数。无法break,可以用try/catch中throw new Error来停止。对空数组不执行回调函数。 |
filter() | 过滤,返回所有符合条件元素的数组。不对空数组进行检测,不改变原数组。 |
some() | 检测数组中是否有元素符合条件。有一个符合则返回true,剩下的不再检测;都不符合则返回false。不对空数组进行检测,不改变原数组。 |
every() | 检测数组中是否所有元素都符合条件。有一个不符合则返回false,剩下的不再检测;都符合则返回true。不对空数组进行检测,不改变原数组。 |
join() | 把数组的所有元素放入一个字符串,通过指定分隔符分隔。 |
push() | 在数组末尾添加一个或多个元素, 返回新的长度。改变原数组。 |
pop() | 删除数组的最后一个元素,返回删除的元素。改变原数组。 |
unshift() | 在数组开头添加一个或多个元素, 返回新的长度。改变原数组。 |
shift() | 删除数组的第一个元素,返回删除的元素。改变原数组。 |
sort() | 对数组元素排序。改变原数组。 |
reverse() | 反转数组元素顺序。改变原数组。 |
concat() | 连接两个或多个数组,返回被连接数组的副本。浅拷贝。不改变原数组。 |
copyWithin(target, start, end) | 将下标为start(含)到end(不含)的元素覆盖到下标target处。若参数为负数,表示倒数。改变原数组。 |
slice(start, end) | 返回下标为start(含)到end(不含)的元素组成的新数组。不改变原数组。 |
splice(start, number, value…) | 从下标为start的元素开始,删除number个元素,添加元素value…,返回删除的元素组成的数组。改变原数组。 |
indexOf(value, fromIndex) | 从下标为fromIndex开始向后查找元素value,返回该元素在数组中第一次出现时的下标,若未找到则返回-1。 |
lastIndexOf(value, fromIndex) | 从下标为fromIndex开始向前查找元素value,返回该元素在数组中最后一次出现时的下标,若未找到则返回-1。 |
reduce(fn(prev, cur)) | 从左往右两两执行,prev为上次回调函数的返回值,cur为当前元素(从第二项开始),最终返回数组元素计算成的一个值。对空数组不执行回调函数。 |
reduceRight(fn(prev, cur)) | 从右往左两两执行,prev为上次回调函数的返回值,cur为当前值(从倒数第二项开始),最终返回数组元素计算成的一个值。对空数组不执行回调函数。 |
闭包
- 什么是闭包
闭包指有权访问另一个函数作用域中的变量的函数。
函数A里面包含了函数B,而函数B里面使用了函数A的变量,那么函数B被称为闭包。 - 作用
① 可以读取函数内部变量。
② 能够访问函数定义时所在的词法作用域(阻止其被回收)。
③ 私有化变量。 - 缺点
会导致函数中的变量一直保存在内存中,内存消耗很大,过多的闭包可能造成内存泄漏。 - 经典问题
for(var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
上述代码结果为依次输出3 3 3。若想依次输出0 1 2,可采取下述两种方法:
//方法一:使用let声明变量。
//每个let和代码块结合起来形成块级作用域,当setTimeout()打印时,会寻找最近的块级作用域中的i。
for(let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
//方法二:使用立即执行函数。
//每一次for循环都立即执行setTimeout()打印。
for(let i = 0; i < 3; i++) {
(function(i){
setTimeout(function() {
console.log(i);
}, 1000);
})(i)
}
作用域、作用域链、变量生命周期、变量提升
- 作用域
作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。- 全局作用域
在程序的任何地方都能被访问。window对象的内置属性都拥有全局作用域。 - 函数作用域
在固定的代码片段内才能被访问。
- 全局作用域
注:ES6新增块级作用域,见后文var、let、const。
- 作用域链
一般情况下,变量取值到创建这个变量的函数的作用域中去查;但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域。在这个查找过程中形成的链条就叫做作用域链。 - 变量生命周期
变量在函数内声明,其为局部变量,具有函数作用域;变量在函数外声明,其为全局变量,具有全局作用域。变量生命周期在它声明时初始化,局部变量在函数执行完毕后销毁,全局变量在页面关闭后销毁。 - 变量提升
用var进行变量的声明将被提升到函数的最顶部。变量可以在使用后声明,也就是变量可以先使用再声明。注:只有声明的变量会提升,初始化的不会。
下述两个示例结果相同:
x = 5; // 变量x设置为5
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x; // 在元素中显示x,结果为5
var x; // 声明x
var x; // 声明x
x = 5; // 变量x设置为5
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x; // 在元素中显示x,结果为5
下述两个示例结果不同:
var x = 5; // 初始化x
var y = 7; // 初始化y
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y; // 在元素中显示x y,结果为5 7
var x = 5; // 初始化x
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y; // 在元素中显示x y,结果为5 undefined
var y = 7; // 初始化y
// 上述代码类似于下述代码,变量声明(var y)提升,但初始化(y=7)不提升
var x = 5; // 初始化x
var y; // 声明y
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x + " " + y; // 在元素中显示x y,结果为5 undefined
y = 7; // 变量y设置为7
原型和原型链
- 原型
对象中固有的__proto__属性,该属性指向对象的prototype原型属性。
instance.constructor.prototype = instance.__proto__ - 原型链
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去。这个查找过程中形成的链条就叫做原型链。原型链的尽头一般来说都是Object.prototype。 - 特点
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
this关键字、new关键字
- this关键字指向的对象
- 函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。
- 方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。
- 构造函数调用,this指向这个用new新创建的对象。
- 在事件中,this指向触发这个事件的对象。
- apply()、call()和bind()调用模式,这三个方法都可以将this引用到任何对象。
- new关键字创建对象过程
- 首先创建了一个新的空对象。
- 设置原型,将对象的原型设置为函数的prototype对象。
- 让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)。
- 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。
call、apply、bind方法
- 三者异同
- call、apply、bind三者都是用来改变函数的this对象的指向的。
- call、apply、bind三者第一个参数都是this要指向的对象,也就是想指定的上下文。
- call、apply、bind三者都可以利用后续参数传参。
- call、apply是立即调用,bind是返回对应函数,便于稍后调用。
- 对于call、apply二者而言,作用完全一样,只是接受参数的方式不太一样。call需要把参数按顺序传递进去,而apply则是把参数放在数组里。
- call方法
语法:call([thisObj[, arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明:call方法可以用来代替另一个对象调用一个方法。call方法可将一个函数的对象上下文从初始的上下文改变为由thisObj指定的新对象。
thisObj的取值有以下4种情况:
① 不传,或者传null、undefined, 函数中的this指向window对象
② 传递另一个函数的函数名,函数中的this指向这个函数的引用
③ 传递字符串、数值或布尔类型等基础类型,函数中的this指向其对应的包装对象,如String、Number、Boolean等。
④ 传递一个对象,函数中的this指向这个对象。 - apply方法
语法:apply([thisObj[, argArray]])
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:如果argArray不是一个有效的数组或者不是arguments对象,那么将导致一个TypeError。如果没有提供argArray和thisObj任何一个参数,那么Global对象将被用作thisObj,并且无法被传递任何参数。 - bind方法
bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入bind()方法的第一个参数作为this,传入 bind()方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
注:在JavaScript中,多次bind()是无效的。原因是bind()的实现,相当于使用函数在内部包了一个call/apply,第二次bind()相当于再包住第一次bind(),故第二次以后的bind()是无法生效的。 - 如何选择使用call、apply还是bind方法
当希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用bind方法;当希望改变上下文环境之后立即执行的时候,使用call/apply方法。
此外,对于call和apply的选用如下:明确知道参数数量时使用call,参数数量不确定时使用apply(把参数push进数组传递进去,函数内部可以通过arguments对象(一个对应于传递给函数的参数的类数组对象)来遍历所有参数)。
事件冒泡和事件捕获
- 事件
事件是文档和浏览器窗口中发生的特定的交互瞬间。 - 事件流
事件流描述的是从页面中接受事件的顺序。
微软(IE)和网景(Netscape)开发团队提出了两个截然相反的事件流概念,IE的事件流是事件冒泡流(event bubbling),而Netscape的事件流是事件捕获流(event capturing)。- 事件冒泡
事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。 - 事件捕获
事件按照从最不精确的对象(document对象)到最精确的对象的顺序触发(也可以在窗口级别捕获事件,不过必须由开发人员特别指定)。
- 事件冒泡
- DOM事件流
包含3个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。首先发生的事件捕获为截获事件提供机会,然后是实际的目标接收事件,最后一个阶段是事件冒泡阶段,可以在这个阶段对事件做出响应。
AJAX
AJAX (Asynchronous JavaScript and XML)即异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页。
- 使用AJAX的过程
① 创建XMLHttpRequest对象。
② 向服务器发送请求,使用open(method, url, async)方法,规定请求的方法 (GET/POST)、url、是否异步 (true异步,false同步)。使用send()方法将请求发送到服务器。
③ 每当readyState改变时,就会触发onreadystatechange事件。当readyState为4且status为200时,表示响应就绪,返回responseText或responseXML。
④ 获取返回的数据,使用JavaScript和DOM实现局部刷新。 - 优点
① 异步通信,不打断用户的操作,局部刷新。
② 将部分后端工作交给前端,减少服务器的负担。
③ 界面和应用相分离,也就是数据与呈现相分离。 - 缺点
① 不支持浏览器的back按钮,破坏了Back和History等浏览器机制。
② 存在安全问题,暴露了与服务器交互的细节。
③ 对搜索引擎的支持比较弱。 - GET和POST的选择
与 POST 相比,GET 更简单也更快,并且在大部分情况下都能用。然而,在以下情况中,请使用 POST 请求:
① 不愿使用缓存文件(更新服务器上的文件或数据库)。
② 向服务器发送大量数据(POST没有数据量限制)。
③ 发送包含未知字符的用户输入时,POST比GET更稳定也更可靠。
EventLoop(事件循环)、Macro Task(宏任务)、Micro Task(微任务)
JS是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列。
同步任务和异步任务分别进入不同的执行环境,同步任务进入主线程,即主执行栈,异步任务进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复就是Event Loop(事件循环)。
异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列执行。宏任务主要包括setTimeout、setInterval、setImmediate (Node.js环境),微任务主要包括Promise.then、MutationObserver、process.nextTick (Node.js环境)。
console.log('start'); //直接执行
setTimeout(function() { //进宏任务队列
console.log('timeout1');
}, 10);
new Promise(resolve => {
console.log('promise'); //直接执行
resolve(); //直接执行
setTimeout(() => console.log('timeout2'), 10); //进宏任务队列
}).then(function() { //进微任务队列
console.log('then')
})
console.log('end'); //直接执行
//控制台输出结果
//start
//promise
//end
//then
//timeout1
//timeout2
ES6
var、let、const
ES2015(ES6)新增加了两个重要的JavaScript关键字:let和const。let声明的变量只在let命令所在的代码块内有效。const声明一个只读的常量,一旦声明,常量的值就不能改变。
在ES6之前,JavaScript只有两种作用域:全局作用域和函数作用域。ES6新增了块级作用域的概念,在代码块内才能访问,块级作用域由{ }包裹。使用let、const关键字来实现块级作用域。
- var、let、const的区别
- var声明变量可以重复声明,let不可以重复声明。
- var声明变量不限于块级作用域,let声明变量和const声明常量具有块级作用域。
- var不存在暂时性死区,let和const存在暂时性死区。(即var存在变量提升,let不存在变量提升,const不能在声明前使用)
- var与window相映射,let和const不与window相映射。
解构赋值
- 数组模型的解构
//基本
let [a, b, c] = [1, 2, 3]; //a=1, b=2, c=3
//可嵌套
let [a, [[b], c]] = [1, [[2], 3]]; //a=1, b=2, c=3
//可忽略
let [a, ,b] = [1, 2, 3]; //a=1, b=3
//不完全解构
let [a, b] = [1, 2, 3]; //a=1, b=2
let [a = 1, b] = []; //a=1, b=undefined
//剩余运算符
let [a, ...b] = [1, 2, 3]; //a=1, b=[2, 3]
//解构的目标为字符串等可遍历对象
let [a, b, c, d, e] = 'hello'; //a='h',b='e',c='l',d='l',e='o'
//解构默认值(匹配结果是undefined时触发默认值作为返回结果)
let [a = 3, b = a] = []; //a=3,b=3
let [a = 3, b = a] = [1]; //a=1,b=1
let [a = 3, b = a] = [1, 2]; //a=1,b=2
- 对象模型的解构
//基本
let {a, b} = {a: 'aaaa', b: 'bbbb'}; //a='aaaa',b='bbbb'
let {c: d} = {c: 'dddd'}; //d='dddd'
//可嵌套
let obj = {p: ['aaaa', {b: 'bbbb'}]};
let {p: [a, {b}]} = obj; //a='aaaa',b='bbbb'
//可忽略
let obj = {p: ['aaaa', {b: 'bbbb'}]};
let {p: [a, { }]} = obj; //a='aaaa'
//不完全解构
let obj = {p: [{a: 'aaaa'}]};
let {p: [{a}, b] } = obj; //a='aaaa',b=undefined
//剩余运算符
let {a, ...rest} = {a: 1, b: 2, c: 3}; //a=1,rest={b:2, c:3}
//声明变量
let a;
(a = {a: 'aaaa'}); //g='aaaa'
//解构默认值
let {a = 1, b = 2} = {a: 3}; //a=3,b=2
let {a: aa = 1, b: bb = 2} = {a: 3}; //aa=3,bb=2;
forEach、for … in、for … of
- forEach
对数组的每一个元素执行一次提供的函数。 - for … in
循环获取键值对的键,常用来遍历对象或json,也可以遍历数组。 - for … of
ES6中新增的语法。循环获取键值对的值,可以用来遍历数组或对象,但遍历对象需要先使用Object.keys()或Object.values()获取对象的键或值集合后,再使用for … of。
Map和Set
- Map对象保存键值对。任何值(对象或者原始值)都可以作为一个键或一个值。类似集合。
- Set对象允许存储任何类型的唯一值,无论是原始值或者是对象引用。类似数组。
箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法为:
参数 => 函数体
- 语法规则
- 当箭头函数没有参数或者有多个参数,要用()括起来。
- 当箭头函数函数体有多行语句,要用{}包裹起来表示代码块,当只有一行语句并且需要返回结果时,可以省略{},结果会自动返回。
- 当箭头函数要返回对象的时候,为了区分于代码块,要用()将对象包裹起来。
var f = v => v;
f(1); //1
///
var f = (a, b) => a + b;
f(1, 2); //3
///
var f = (a, b) => {
let result = a + b;
return result;
}
f(1, 2); //3
///
var f = (id, name) => ({id: id, name: name});
f(1, 2); //{id: 1, name: 2}
- 注意事项
- 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值。箭头函数体中的this对象,是定义函数时的对象,而不是调用函数时的对象。
- 箭头函数不绑定arguments,取而代之用rest参数解决。
- 箭头函数是匿名函数,不能作为构造函数,不能使用new命令。
- 箭头函数不能使用yield命令,因此不能用作Generator函数。
Promise
ECMAscript 6原生提供了 Promise对象,目的是更加优雅地书写复杂的异步任务。Promise对象代表了未来将要发生的事件,用来传递异步操作的消息。
- 特点
① 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:
pending:初始状态,不是成功或失败状态。
fulfilled:意味着操作成功完成。
rejected:意味着操作失败。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
② 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。 - 优点
① 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
② 提供统一的接口,使得控制异步操作更加容易。 - 缺点
① 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
② 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
③ 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 - 创建
var promise = new Promise(function (resolve,reject) {
if (操作成功) {
resolve(value);
} else {
reject(error);
}
});
promise.then(function (value) { // 此处value的值是上面调用resolve()方法传入的值
// success
},function (value) {
// failure
});
Promise类有 .then() .catch() 和 .finally() 三个方法,这三个方法的参数都是一个函数,.then()可以将参数中的函数添加到当前Promise的正常执行序列,.catch()则是设定Promise的异常处理序列,.finally()是在Promise执行的最后一定会执行的序列。 .then()传入的函数会按顺序依次执行,有任何异常都会直接跳到catch序列:
new Promise(function (resolve, reject) {
console.log(1111);
resolve(2222);
}).then(function (value) {
console.log(value);
return 3333;
}).then(function (value) {
console.log(value);
throw "An error";
}).catch(function (err) {
console.log(err);
}).finally(function () {
console.log("End");
});
//控制台输出结果:
//1111
//2222
//3333
//An error
//End
resolve()中可以放置一个参数用于向下一个then传递一个值,then中的函数也可以返回一个值传递给then。但是,如果then中返回的是一个Promise对象,那么下一个then将相当于对这个返回的Promise进行操作。reject()参数中一般会传递一个异常给之后的catch函数用于处理异常。
注:
① resolve和reject的作用域只有起始函数,不包括then以及其他序列。
② resolve和reject并不能够使起始函数停止运行,别忘了return。
- 常见问题
Q: then、catch和finally序列能否顺序颠倒?
A: 可以,效果完全一样。但不建议这样做,最好按then-catch-finally的顺序编写程序。
Q: 除了then块以外,其它两种块能否多次使用?
A: 可以,finally与then一样会按顺序执行,但是catch块只会执行第一个,除非catch块里有异常。所以最好只安排一个catch和finally块。
Q: then块如何中断?
A: then块默认会向下顺序执行,return是不能中断的,可以通过throw来跳转至catch实现中断。
Q: 什么时候适合用Promise而不是传统回调函数?
A: 当需要多次顺序执行异步操作的时候,例如,如果想通过异步方法先后检测用户名和密码,需要先异步检测用户名,然后再异步检测密码的情况下就很适合Promise。
Q: 什么时候我们需要再写一个then而不是在当前的then接着编程?
A: 当你又需要调用一个异步任务的时候。 - Promise.all方法和Promise.race方法
Promise.all方法和Promise.race方法都用于将多个Promise实例包装成一个新的Promise实例。
var p = Promise.all([p1,p2,p3]);
var p = Promise.race([p1,p2,p3]);
上述示例中,Promise.all方法和Promise.race方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例。(Promise.all方法和Promise.race方法的参数不一定是数组,但是必须具有iterator接口,且返回的每个成员都是Promise实例。)
对于Promise.all方法,p的状态由p1、p2、p3 决定,分成两种情况。
① 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
② 只要p1、p2、p3之中有一个状态变为rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
对于Promise.race方法,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
- Promise.resove方法和Promise.reject方法
如果Promise.resolve方法的参数,不是具有then方法的对象(又称 thenable对象),则返回一个新的Promise对象,且它的状态为fulfilled。
如果Promise.resolve方法的参数是一个Promise对象的实例,则会被原封不动地返回。
Promise.reject方法也会返回一个新的Promise实例,该实例的状态为rejected。
var p = Promise.resolve('Hello');
p.then(function (s){
console.log(s)
});
// Hello
var p = Promise.reject('Error');
p.then(null, function (s){
console.log(s)
});
// Error
async和await
异步函数(async function)是 ECMAScript 2017 (ECMA-262) 标准的规范,几乎被所有浏览器所支持,除了Internet Explorer。
异步函数async function中可以使用await指令(await只能放在async函数里),await指令后必须跟着一个Promise(return new Promise的函数),异步函数会在这个Promise运行中暂停,直到其运行结束再继续运行。异步函数实际上原理与 Promise 原生 API 的机制是一模一样的,只不过更便于程序员阅读。
- 处理异常的机制将用try-catch块实现,把await和成功后的操作放在try里,失败的放在catch里:
async function asyncFunc() {
try {
await new Promise(function (resolve, reject) {
throw "Some error"; // 或者 reject("Some error")
});
} catch (err) {
console.log(err);
}
}
// Some error
- 如果 Promise 有一个正常的返回值,await 语句也会返回它:
async function asyncFunc() {
let value = await new Promise(
function (resolve, reject) {
resolve("Return value");
}
);
console.log(value);
}
// Return value
- async函数会返回一个promise,并且Promise对象的状态是fulfilled。如果没有在async函数里写return,那么Promise对象resove的值是undefined;如果写了return,那么Promise对象resolve的值是return的值。