1. 字符串
- ASCII字符可以以
\#XX
形式的十六进制表示,例如:
'\x41' // 等同于A
- 还可以用
\u####
表示一个Unicode字符:
'\u4e2d\u6587' // 等同于'中文'
- 由于多行字符串用
\n
写起来比较费事,所以最新的ES6标准新增了一种多行字符串的表示方法,* ... *
表示:
alert(`多行
字符串
测试`);
2. 数组
- 多维数组
举例,构建二维数组,规模为mxn
,值全部初始化为initial
3. 对象
- JS中有一点很奇特,就是JavaScript规定,访问一个对象中不存在的属性不报错,而是返回
undefined
。
4. Map和Set
JavaScript的默认对象表示方式{}
可以视为其他语言中的Map
或Dictionary
的数据结构,即一组键值对。
但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。
为了解决这个问题,最新的ES6规范引入了新的数据类型Map
。
4.1 Map
Map
是一组键值对的结构,具有极快的查找速度。
举个例子,假设要根据同学的名字查找对应的成绩,如果用Array
实现,需要两个Array
:
var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];
给定一个名字,要查找对应的成绩,就先要在names中找到对应的位置,再从scores取出对应的成绩,Array越长,耗时越长。
如果用Map实现,只需要一个“名字”-“成绩”的对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。用JavaScript写一个Map如下:
var m = new Map([['Michael', 95], ['Bob', 75],
['Tracy', 85]]);
m.get('Michael'); // 95
通过上面的例子,可以看出Map
其实看成一个二维数组,Map
中的每一个元素都是一个一维数组,有一个key
和 与之对应的一个value
。
- Map的具体操作
初始化Map
需要一个二维数组,或者直接初始化一个空Map
。
由于一个key
只能对应一个value
,所以,多次对一个key
放入value
,后面的值会把前面的值冲掉:
4.2 Set
Set
和Map
类似,也是一组key
的集合,但不存储value
。由于key
不能重复,所以,在Set
中,没有重复的key
。
要创建一个Set
,需要提供一个Array
作为输入,或者直接创建一个空Set
:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重复元素在Set
中自动被过滤:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
通过add(key)
方法可以添加元素到Set中,可以重复添加,但不会有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
通过delete(key)
方法可以删除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
5. iterable
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。
为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型
具有iterable
类型的集合可以通过新的for ... of
循环来遍历。for ... of
循环是ES6引入的新的语法。
- iterable的使用
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍历Array
alert(x);
}
for (var x of s) { // 遍历Set
alert(x);
}
for (var x of m) { // 遍历Map
alert(x[0] + '=' + x[1]);
}
-
for...in
和for...of
的区别
for ... in
循环由于历史遗留问题,它遍历的实际上是对象的属性名称。一个Array
数组实际上也是一个对象,它的每个元素的索引被视为一个属性。
当我们手动给Array
对象添加了额外的属性后,for ... in
循环将带来意想不到的意外效果:
上面的例子中,for ... in
循环将把name
包括在内,但Array
的length
属性却不包括在内。
for ... of
循环则完全修复了这些问题,它只循环集合本身的元素:
-
iterable
内置的forEach
方法
与for...of
方法相比,forEach
方法更好。它接收一个函数,每次迭代就自动回调该函数。
1.Array
的forEach
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向当前元素的值
// index: 指向当前索引
// array: 指向Array对象本身
alert(element);
});
2. Set
与Array
类似,但Set
没有索引,因此回调函数最多两个参数:
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, set) {
console.log(element); // 'A','B','C'
});
3. Map
的回调函数参数依次为value
、key
和map
本身
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value); // 'x','y','z'
});
6. 函数
6.1 函数的定义与调用
arguments
JavaScript还有一个免费赠送的关键字arguments
,它只在函数内部起作用,和this
关键字一样,并且永远指向当前函数的调用者传入的所有参数。arguments
类似Array
,但它不是一个Array
。
利用arguments
,你可以获得调用者传入的所有参数。也就是说,即使函数不定义任何参数,还是可以拿到参数的值。
实际上arguments
最常用于判断传入参数的个数。注意区别arguments.length
和arguments.callee.length
。前者是实际传入的参数的个数,后者是定义函数时,定义的形参的个数。-
rest参数
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments
来获取所有参数:
为了获取除了已定义参数a
、b
之外的参数,我们不得不用arguments
,并且循环要从索引2开始以便排除前两个参数,这种写法很别扭,只是为了获得额外的rest
参数,有没有更好的方法?
Duang!!!,ES6标准引入了rest
参数,上面的函数可以改写为:
注意:现在浏览器大都还不支持...rest
的写法。 小心你的return语句
JavaScript引擎有一个在行末自动添加分号的机制,这可能让你栽到return语句的一个大坑:
function foo() {
return
{ name: 'foo' };
}
console.log(foo()); // undefined
---> 相当于下面:
function foo() {
return; // 自动添加了分号,相当于return undefined;
{ name: 'foo' }; // 这行语句已经没法执行到了
}
所以正确的多行写法是:
function foo() {
return { // 这里不会自动加分号,因为{表示语句尚未结束
name: 'foo'
};
}
6.2 变量的作用域
- 变量提升
变量的声明会提升,但是赋值不会被提升。
'use strict';
function foo() {
var x = 'Hello, ' + y; // 'Hello, undefined'
alert(x);
var y = 'Bob';
}
foo();
- 命名空间
全局变量会绑定到window
上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
把自己的代码全部放入唯一的名字空间MYAPP
中,会大大减少全局变量冲突的可能。
许多著名的JavaScript库都是这么干的:jQuery
,YUI
,underscore
等等。
- 局部作用域
由于JavaScript的变量作用域实际上是函数内部,是没有块级作用域这个概念的,我们在for循环等语句块中是无法定义具有局部作用域的变量的。
为了解决块级作用域,ES6引入了新的关键字let
,用let
替代var
可以申明一个块级作用域的变量。
- 常量
由于var
和let
申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”。
ES6标准引入了新的关键字const
来定义常量,const
与let
都具有块级作用域。
'use strict';
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
6.3 generator
generator
(生成器)是ES6标准引入的新的数据类型。一个generator
看上去像一个函数,但可以返回多次。
先看一个generator
的栗子:
再举个栗子,
fibnacci
数列:直接调用一个
generator
和调用函数不一样,fib(5)
仅仅是创建了一个generator
对象,还没有去执行它。调用
generator
对象有两个方法,一是不断地调用generator
对象的next()
方法,如上面的栗子所示。
generator
对象的next( )
方法
next( )
方法会执行generator
的代码,然后,每次遇到yield x
;就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator
是否已经执行结束了。如果done
为true
,则value
就是return
的返回值。
当执行到done
为true
时,这个generator
对象就已经全部执行完毕,不要再继续调用next()
了。使用
for...of
遍历generator
对象
第二个方法是直接用for ... of
循环迭代generator
对象,这种方式不需要我们自己判断done
。需要注意,使用for...of
方法遍历不到return
返回的值。generator
的用处
因为generator
可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数。利用这一点,写一个generator
就可以实现需要用面向对象才能实现的功能。
generator
还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。例如Ajax,举个栗子:
像上面的例子中,回调越多,为了保证代码的执行顺序,代码嵌套的层级越多,代码越难看。
有了generator
的美好时代,用AJAX时可以这么写:
看上去是同步的代码,实际执行是异步的。
7. 标准对象
7.1 Date
- 获取时区
Date
对象表示的时间总是按浏览器所在时区显示的,不过我们既可以显示本地时间,也可以显示调整后的UTC时间:
那么在JavaScript中如何进行时区转换呢?实际上,只要我们传递的是一个number
类型的时间戳,我们就不用关心时区转换。任何浏览器都可以把一个时间戳正确转换为本地时间。 - 时间戳
时间戳是个什么东西?时间戳是一个自增的整数,它表示从1970年1月1日零时整的GMT时区开始的那一刻,到现在的毫秒数。假设浏览器所在电脑的时间是准确的,那么世界上无论哪个时区的电脑,它们此刻产生的时间戳数字都是一样的,所以,时间戳可以精确地表示一个时刻,并且与时区无关。
所以,我们只需要传递时间戳,或者把时间戳从数据库里读出来,再让JavaScript自动转换为当地时间就可以了。
要获取当前时间戳,可以用:
7.2 正则表达式
- 贪婪匹配
需要特别指出的是,正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
由于\d+
采用贪婪匹配,直接把后面的0全部匹配了,结果0*
只能匹配空字符串了。
必须让\d+
采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?
就可以让\d+
采用非贪婪匹配。
注意:上面两个例子中使用了^
和$
,这样可以在全局上进行一次匹配。 - 全局匹配
全局匹配类似搜索,因此不能使用/^...$/
,那样只会最多匹配一次。
8. 面向对象编程
8.1 原型继承
在传统的基于Class的语言如Java、C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass。
由于这类语言严格区分类和实例,继承实际上是类型的扩展。但是JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型。
比较新颖的继承方法的实现:
9. 浏览器
由于JavaScript的出现就是为了能在浏览器中运行,所以,浏览器自然JavaScript开发者必须要关注的。
目前主流的浏览器分这么几种:
-
IE 6~11
:国内用得最多的IE浏览器,历来对W3C标准支持差。从IE10
开始支持ES6标准; -
Chrome
:Google
出品的基于Webkit
内核浏览器,内置了非常强悍的JavaScript引擎——V8
。由于Chrome
一经安装就时刻保持自升级,所以不用管它的版本,最新版早就支持ES6了; -
Sarafi
:Apple
的Mac系统自带的基于Webkit
内核的浏览器,从OS X
10.7 Lion自带的6.1版本开始支持ES6,目前最新的OS X
10.10 Yosemite自带的Sarafi版本是8.x,早已支持ES6; -
Firefox
:Mozilla
自己研制的Gecko
内核和JavaScript引擎OdinMonkey
。早起的Firefox
按版本发布,后来终于聪明地学习Chrome的做法进行自升级,时刻保持最新; -
移动设备上目前
iOS
和Android
两大阵营分别主要使用Apple
的Safari
和Google
的Chrome
,由于两者都是Webkit
核心,结果HTML5
首先在手机上全面普及(桌面绝对是Microsoft拖了后腿),对JavaScript的标准支持也很好,最新版本均支持ES6。
其他浏览器如Opera
等由于市场份额太小就被自动忽略了。
另外还要注意识别各种国产浏览器,如某某安全浏览器,某某旋风浏览器,它们只是做了一个壳,其核心调用的是IE,也有号称同时支持IE和Webkit的“双核”浏览器。
不同的浏览器对JavaScript支持的差异主要是,有些API的接口不一样,比如AJAX,File接口。对于ES6标准,不同的浏览器对各个特性支持也不一样。
在编写JavaScript的时候,就要充分考虑到浏览器的差异,尽量让同一份JavaScript代码能运行在不同的浏览器上。
JavaScript可以获取浏览器提供的很多对象,并进行操作: - window
window
对象不但充当全局作用域,而且表示浏览器窗口。
window
对象有innerWidth
和innerHeight
属性,可以获取浏览器窗口的内部宽度和高度。内部宽高是指除去菜单栏、工具栏、边框等占位元素后,用于显示网页的净宽高。
注意:IE <= 8 不兼容这两个属性的。 - navigator
navigator
对象表示浏览器的信息,最常用的属性包括:
navigator.appName:浏览器名称;
navigator.appVersion:浏览器版本;
navigator.language:浏览器设置的语言;
navigator.platform:操作系统类型;
navigator.userAgent:浏览器设定的User-Agent字符串。
请注意,navigator
的信息可以很容易地被用户修改,所以JavaScript读取的值不一定是正确的,尽量不要使用navigator.userAgent
提供的用户代理字符串去判断客户端浏览器。
正确的方法是充分利用JavaScript对不存在属性返回undefined
的特性,直接用短路运算符||
计算:
var width = window.innerWidth || document.body.clientWidth;
- screen
screen
对象表示屏幕的信息,常用的属性有:
screen.width:屏幕宽度,以像素为单位;
screen.height:屏幕高度,以像素为单位;
screen.colorDepth:返回颜色位数,如8、16、24;
-
location
location
对象表示当前页面的URL
信息。例如,一个完整的URL
:
可以用location.href
获取。要获得URL
各个部分的值,可以这么写:
要加载一个新页面,可以调用location.assign()
,使用location.href
也是可以的。如果要重新加载当前页面,调用location.reload()
方法非常方便。 document
document
对象表示当前页面。由于HTML
在浏览器中以DOM
形式表示为树形结构,document
对象就是整个DOM
数的根节点。
document
对象还有一个cookie
属性,可以获取当前页面的Cookie
。
聊聊Cookie
:
Cookie
是由服务器发送的key-value标示符。因为HTTP协议是无状态的,但是服务器要区分到底是哪个用户发过来的请求,就可以用Cookie来区分。当一个用户成功登录后,服务器发送一个Cookie
给浏览器,例如user=ABC123XYZ
(加密的字符串)...,此后,浏览器访问该网站时,会在请求头附上这个Cookie
,服务器根据Cookie
即可区分出用户。
为了防止XSS
盗取Cookie
中重要的信息,服务器在设置Cookie
时可以使用httpOnly
,设定了httpOnly
的Cookie
将不能被JavaScript读取。这一类Cookie
称为HTTP专有cookie,HTTP专有cookie可以从浏览器或者服务器设置,但是只能从服务器端读取,因此JavaScript无法获取HTTP专有cookie的值。
为了确保安全,服务器端在设置Cookie时,应该始终坚持使用httpOnly。history
history
对象保存了浏览器的历史记录,JavaScript可以调用history
对象的back()
或forward ()
,相当于用户点击了浏览器的“后退”或“前进”按钮。
这个对象属于历史遗留对象,对于现代Web页面来说,由于大量使用AJAX
和页面交互,简单粗暴地调用history.back()
可能会让用户感到非常愤怒。
新手开始设计Web页面时喜欢在登录页登录成功时调用history.back()
,试图回到登录前的页面。这是一种错误的方法。
任何情况,你都不应该使用history
这个对象了。
10. DOM操作
- element.insertBefore(
newNode
,referenceNode
); - element.insertAdjacentHTML(
positionString
,insertHTML
);
positionString:
beforeBegin afterBegin beforeEnd afterEnd
11. 表单操作
-
form
元素的submit
事件的事件监听函数中,return true
告诉来告诉浏览器继续提交;return false
表示浏览器将不会提交表单,这种情况通常对应用户输入有误,提示用户错误信息后终止提交form。 -
没有
name
属性的<input>
的数据不会被提交。
12. 操作文件
在HTML表单中,可以上传文件的唯一控件就是<input type="file">
。
尤其需要注意:
当一个表单包含
<input type="file">
时,表单的enctype
必须指定为multipart/form-data
,method
必须指定为post
,浏览器才能正确编码并以multipart/form-data
格式发送表单的数据。
出于安全考虑,浏览器只允许用户点击<input type="file">
来选择本地文件,用JavaScript对<input type="file">
的value
赋值是没有任何效果的。当用户选择了上传某个文件后,JavaScript也无法获得该文件的真实路径:
通常,上传的文件都由后台服务器处理,JavaScript可以在提交表单时对文件扩展名做检查,以便防止用户上传无效格式的文件。
-
File API
由于JavaScript对用户上传的文件操作非常有限,尤其是无法读取文件内容,使得很多需要操作文件的网页不得不用Flash这样的第三方插件来实现。
随着HTML5
的普及,新增的File API
允许JavaScript读取文件内容,获得更多的文件信息。
HTML5
的File API
提供了File
和FileReader
两个主要对象,可以获得文件信息并读取文件。
13. Ajax
-
兼容模式生成XHR对象
- 原生Ajax写法步骤
当创建了XMLHttpRequest
对象后,要先设置onreadystatechange
的回调函数。在回调函数中,通常我们只需通过readyState === 4
判断请求是否完成,如果已完成,再根据status === 200
(也可以写成(status >= 200 && status < 300) || status === 304
)判断是否是一个成功的响应。
XMLHttpRequest
对象的open()
方法有3个参数,第一个参数指定是GET
还是POST
,第二个参数指定URL
地址,第三个参数指定是否使用异步,默认是true
,所以不用写。
注意,千万不要把第三个参数指定为false
,否则浏览器将停止响应,直到AJAX请求完成。如果这个请求耗时10秒,那么10秒内你会发现浏览器处于“假死”状态。
最后调用send()
方法才真正发送请求。GET
请求不需要参数,POST
请求需要把body
部分以字符串或者FormData
对象传进去。 - 安全限制
默认情况下,JavaScript在发送AJAX请求时,URL的域名必须和当前页面完全一致。这就是浏览器的同源策略造成的。
完全一致的意思是,域名要相同(www.example.com和example.com不同),协议要相同(http和https不同),端口号要相同(默认是:80端口,它和:8080就不同)。有的浏览器口子松一点,允许端口不同,大多数浏览器都会严格遵守这个限制。
那是不是用JavaScript无法请求外域(就是其他网站)的URL了呢?方法还是有的,大概有这么几种:
1. 通过Flash插件发送HTTP请求,这种方式可以绕过浏览器的安全限制,但必须安装Flash,并且跟Flash交互。不过Flash用起来麻烦,而且现在用得也越来越少了。
2. 通过在同源域名下架设一个代理服务器来转发,JavaScript负责把请求发送到代理服务器。
代理服务器再把结果返回,这样就遵守了浏览器的同源策略。这种方式麻烦之处在于需要服务器端额外做开发。
3.** 称为JSONP
,它有个限制,只能用GET
请求,并且要求返回JavaScript
**。这种方式跨域实际上是利用了浏览器允许跨域引用JavaScript
资源。
以163的股票查询URL为例,对于URL:http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice,你将得到如下返回:
refreshPrice({"0000001":{"code": "0000001", ... });
因此我们需要首先在页面中准备好回调函数:
function refreshPrice(data) {
var p = document.getElementById('test-jsonp');
p.innerHTML = '当前价格:' +
data['0000001'].name +': ' +
data['0000001'].price + ';' +
data['1399001'].name + ': ' +
data['1399001'].price;
}
最后用getPrice( )
函数触发,就完成了跨域加载数据:
4. CORS
如果浏览器支持
HTML5
,那么就可以一劳永逸地使用新的跨域策略:CORS
了。CORS
全称Cross-Origin Resource Sharing
,是HTML5
规范定义的如何跨域访问资源。了解CORS前,我们先搞明白概念:
Origin
表示本域,也就是浏览器当前页面的域。当JavaScript向外域(如sina.com)发起请求后,浏览器收到响应后,首先检查Access-Control-Allow-Origin
是否包含本域,如果是,则此次跨域请求成功,如果不是,则请求失败,JavaScript将无法获取到响应的任何数据。可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确
Access-Control-Allow-Origin
,决定权始终在对方手中。上面这种跨域请求,称之为“简单请求”。简单请求包括
GET
、HEAD
和POST
(POST
的Content-Type
类型仅限application/x-www-form-urlencoded
、multipart/form-data
和text/plain
),并且不能出现任何自定义头(例如,X-Custom: 12345
),通常能满足90%的需求。在引用外域资源时,除了JavaScript和CSS外,都要验证CORS。例如,当你引用了某个第三方CDN上的字体文件时:
对于
PUT
、DELETE
以及其他类型如application/json
的POST
请求,在发送AJAX
请求之前,浏览器会先发送一个OPTIONS
请求(称为preflighted
请求)到这个URL上,询问目标服务器是否接受:
OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://bar.com
Access-Control-Request-Method: POST
服务器必须响应并明确指出允许的Method:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400
浏览器确认服务器响应的Access-Control-Allow-Methods
头确实包含将要发送的AJAX
请求的Method,才会继续发送AJAX,否则,抛出一个错误。
由于以POST
、PUT
方式传送JSON格式的数据在REST中很常见,所以要跨域正确处理POST
和PUT
请求,服务器端必须正确响应OPTIONS
请求。
浏览器对CORS
的实现情况:
- IE8中引入了XDR类型
XDR与XHR有一些不同之处:
cookie不会随请求发送,也不会随响应返回;
只能设置请求头部信息中的Content-Type字段;
不能访问响应头部信息;
只支持GET和POST方法;
所有的XDR请求都是异步执行的,请求返回后,会触发load事件,但只能访问响应的原始文本(responseText),不能访问status,并且,只要响应有效就会触发load事件。如果失败(包括响应中缺少Access-Control-Allow-Origin头部)就会触发error事件。
为了支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式。
- 其他浏览器对CORS的支持
其他支持HTML5的浏览器,都通过XHR对象实现对CORS的原生支持。要请求位于另一个域中的资源,使用标准的XHR对象并在open( )方法中传入绝对URL即可。
跨域的XHR对象可以访问status属性,而且还支持同步请求。不过,跨域XHR对象也会有一些限制:
不同使用setRequestHeader( )设置自定义头部;
不能发送和接收cookie;
调用getAllResponseHeader( )方法总会返回空字符串;
注意以下内容,XDR和跨域XHR有如下共同属性/方法:
abort( ):用于停止正在进行的请求
onerror:用于替代onreadystatechange检测错误
onload:用于替代onreadystatechange检测成功
responseText:用于取得响应内容
send( ):用于发送请求
跨浏览器的CORS
14. promise
在JavaScript的世界中,所有代码都是单线程执行的。
由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步操作会在将来的某个时间点触发一个函数调用,AJAX就是典型的异步操作。
这种链式写法的好处在于,先统一执行AJAX逻辑,不关心如何处理结果,然后,根据结果是成功还是失败,在将来的某个时候调success函数或fail函数。
古人云:“君子一诺千金”,这种“承诺将来会执行”的对象在JavaScript中称为Promise对象。
Promise有各种开源实现,在ES6中被统一规范,由浏览器直接支持。