ECMAScript 6.0(如下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言能够用来编写复杂的大型应用程序,成为企业级开发语言。javascript
一个常见的问题是,ECMAScript 和 JavaScript 究竟是什么关系?html
要讲清楚这个问题,须要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,但愿这种语言可以成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的初版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。该标准从一开始就是针对 JavaScript 语言制定的,可是之因此不叫 JavaScript,有两个缘由。一是商标,Java 是 Sun 公司的商标,根据受权协议,只有 Netscape 公司能够合法地使用 JavaScript 这个名字,且 JavaScript 自己也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。所以,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。平常场合,这两个词是能够互换的。前端
这个问题能够转换一种问法,就是学完es6会给咱们的开发带来什么样便利?chrome解释javascript的引擎叫作V8,有一我的把V8引擎转移到了服务器,因而服务器端也能够写javascript,这种在服务器端运行的js语言,就是Node.js。Node.js一经问世,它优越的性能就表现了出了,不少基于nodejs的web框架也应运而生,express就是之一,随之而来的就是全栈MEAN mogoDB,Express,Vue.js,Node.js开发,javaScript愈来愈多的使用到web领域的各个角落,js能作的事情也愈来愈多。Babel是一个普遍使用的ES6转码器,能够将ES6代码转为ES5代码,从而在现有环境执行。这意味着,你能够用ES6的方式编写程序,又不用担忧现有环境是否支持。nodejs是一种开发趋势,Vue.js这种前端框架是一种开发趋势,ES6被普及使用也是趋势。目前一些前端框架都在使用ES6语法,例如Vue、React、D3等等,因此ES6也是学习好前端框架的基础。java
因为有些低版本的浏览器还不支持ES6的语法,因此在不使用框架的状况下,须要将ES6语法转换为ES5语法。node
先建立一个项目,项目中有两个文件夹,src和dist,一个html文件git
src:将编写的ES6的js文件放到此文件夹中(这里是index.js文件)程序员
dist:将经过Babel编译成的ES5的js文件放到此文件中(这里是index.js文件)es6
html:注意:将dist中编译好的文件引入到HTML文件中,而不是src中的js文件github
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./dist/index.js"></script> </head> <body> Hello ES6 </body> </html>
第一步:web
在src目录下,新建index.js文件。这个文件很简单,咱们只做一个a变量的声明,并用console.log()打印出来。
let a = 1; console.log(a);
第二步:
在项目的根目录初始化项目并生成package.json文件(能够根据本身的需求进行修改)
cnpm init -y
{ "name": "es6", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }
第三步:
安装Babel插件(将ES6语法转换为ES5)
cnpm install -g babel-cli
第四步:
固然如今还不能正常转换,还须要安装ES5所需的一个包
cnpm install --save-dev babel-preset-es2015 babel-cli ## 安装完成后,package.json会有所变化
{ "name": "es6", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1" } }
第五步:
在项目的根目录添加一个 .babelrc 文件,并添加内容
{ "presets":[ "es2015" ], "plugins": [] }
在windows系统中建立.babelrc文件的方法
方法一:根目录下,建立“.babelrc.”文件名就能够了!(先后共两个点)
方法二:cmd进入根目录,输入“type null>.babelrc”,回车便可!
第六步:
安装完成后咱们能够经过命令进行转换
babel src/index.js -o dist/index.js
第七步:
能够将命令进行简化(package.json进行配置)
"scripts": { "test": "echo "Error: no test specified" && exit 1" },
修改成:
{ "name": "es6", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "babel src/index.js -o dist/index.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "babel-cli": "^6.26.0", "babel-preset-es2015": "^6.24.1" } }
而后咱们能够经过下面命令转义代码:
npm run test
ES2015(ES6) 新增长了两个重要的 JavaScript 关键字: let 和 const。
let 声明的变量只在 let 命令所在的代码块内有效,const 声明一个只读的常量,一旦声明,常量的值就不能改变。
let命令有如下特色:
(1)代码块内有效
ES2015(ES6) 新增长了两个重要的 JavaScript 关键字: let 和 const。let 声明的变量只在 let 命令所在的代码块内有效,const 声明一个只读的常量,一旦声明,常量的值就不能改变。
{ let a = 1; var b = 2; console.log(a);//输出1 console.log(b);//输出2 } console.log(a);//报错 ReferenceError: a is not defined console.log(b);//输出2
(2)不能重复声明
let 只能声明一次 var 能够声明屡次:
let a = 1; let a = 2;//报错 Identifier 'a' has already been declared var b = 3; var b = 4; console.log(a); console.log(b);//输出4
for 循环计数器很适合用 let
for (var i = 0; i < 10; i++) { setTimeout(function(){ console.log(i); }) } // 输出十个 10 for (let j = 0; j < 10; j++) { setTimeout(function(){ console.log(j); }) } // 输出 0123456789
变量 i 是用 var 声明的,在全局范围内有效,因此全局中只有一个变量 i, 每次循环时,setTimeout 定时器里面的 i 指的是全局变量 i ,而循环里的十个 setTimeout 是在循环结束后才执行,因此此时的 i 都是 10。
变量 j 是用 let 声明的,当前的 j 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,因此 setTimeout 定时器里面的 j 实际上是不一样的变量,即最后输出 12345。(若每次循环的变量 j 都是从新声明的,如何知道前一个循环的值?这是由于 JavaScript 引擎内部会记住前一个循环的值)。
(3)不存在变量提高
let 不存在变量提高,var 会变量提高:
console.log(a); //ReferenceError: a is not defined let a = "apple"; console.log(b); //undefined var b = "banana";
变量 b 用 var 声明存在变量提高,因此当脚本开始运行的时候,b 已经存在了,可是尚未赋值,因此会输出 undefined。变量 a 用 let 声明不存在变量提高,在声明变量 a 以前,a 不存在,因此会报错。
(4)暂时性死区
只要块级做用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,再也不受外部的影响。
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
上面代码中,存在全局变量tmp,可是块级做用域内let又声明了一个局部变量tmp,致使后者绑定这个块级做用域,因此在let声明变量前,对tmp赋值会报错。
ES6 明确规定,若是区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就造成了封闭做用域。凡是在声明以前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量以前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
if (true) { // TDZ开始 tmp = 'abc'; // ReferenceError console.log(tmp); // ReferenceError let tmp; // TDZ结束 console.log(tmp); // undefined tmp = 123; console.log(tmp); // 123 }
上面代码中,在let命令声明变量tmp以前,都属于变量tmp的“死区”。
“暂时性死区”也意味着typeof再也不是一个百分之百安全的操做。
typeof x; // ReferenceError let x;
另外,下面的代码也会报错,与var的行为不一样。
// 不报错 var x = x; // 报错 let x = x; // ReferenceError: x is not defined
上面代码报错,也是由于暂时性死区。使用let声明变量时,只要变量在尚未声明完成前使用,就会报错。上面这行就属于这个状况,在变量x的声明语句尚未执行完成前,就去取x的值,致使报错”x 未定义“。
ES6 规定暂时性死区和let、const语句不出现变量提高,主要是为了减小运行时错误,防止在变量声明前就使用这个变量,从而致使意料以外的行为。这样的错误在 ES5 是很常见的,如今有了这种规定,避免此类错误就很容易了。
总之,暂时性死区的本质就是,只要一进入当前做用域,所要使用的变量就已经存在了,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。
const 声明一个只读变量,声明以后不容许改变。意味着,一旦声明必须初始化,不然会报错。
基本用法:
const PI = "3.1415926"; PI // 3.1415926 const MY_AGE; // 报错 SyntaxError: Missing initializer in const declaration
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须当即初始化,不能留到之后赋值。
const foo; // 报错 SyntaxError: Missing initializer in const declaration
上面代码表示,对于const来讲,只声明不赋值,就会报错。const的做用域与let命令相同:只在声明所在的块级做用域内有效。
if (true) { const MAX = 5; } MAX // Uncaught ReferenceError: MAX is not defined
const命令声明的常量也是不提高,一样存在暂时性死区,只能在声明的位置后面使用。
if (true) { console.log(MAX); // ReferenceError const MAX = 5; }
上面代码在常量MAX声明以前就调用,结果报错。const声明的常量,也与let同样不可重复声明。
var message = "Hello!"; let age = 25; // 如下两行都会报错 const message = "Goodbye!"; const age = 30;
暂时性死区:
var PI = "a"; if(true){ console.log(PI); //报错 ReferenceError: PI is not defined const PI = "3.1415926"; }
ES6 明确规定,代码块内若是存在 let 或者 const,代码块会对这些命令声明的变量从块的开始就造成一个封闭做用域。代码块内,在声明变量 PI 以前使用它会报错。
注意要点
const 如何作到变量在声明初始化以后不容许改变的?其实 const 其实保证的不是变量的值不变,而是保证变量指向的内存地址所保存的数据不容许改动。此时,你可能已经想到,简单类型和复合类型保存值的方式是不一样的。是的,对于简单类型(数值 number、字符串 string 、布尔值 boolean),值就保存在变量指向的那个内存地址,所以 const 声明的简单类型变量等同于常量。而复杂类型(对象 object,数组 array,函数 function),变量指向的内存地址实际上是保存了一个指向实际数据的指针,因此 const 只能保证指针是固定的,至于指针指向的数据结构变不变就没法控制了,因此使用 const 声明复杂类型对象时要慎重。
解构赋值是对赋值运算符的扩展。
它是一种针对数组或者对象进行模式匹配,而后对其中的变量进行赋值。在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
ES6 容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
在解构中,有下面两部分参与:
解构的源,解构赋值表达式的右边部分;
解构目标,解构赋值表达式的左边部分;
在ES5中,为变量赋值只能直接指定变量的值:
let a = 1; let b = 2;
在ES6中,变量赋值容许写成:
let [a,b,c] = [1,2,3]; console.log(a); // 输出1 console.log(b); // 输出2 console.log(c); // 输出3
面代码表示,能够从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
基本用法
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 = 1, b] = []; // a = 1, b = undefined
若是解构不成功,变量的值就等于undefined。
let [foo] = []; let [bar, foo] = [1];
以上两种状况都属于解构不成功,foo的值都会等于undefined。
另外一种状况是不彻底解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种状况下,解构依然能够成功。
let [x, y] = [1, 2, 3]; x // 1 y // 2 let [a, [b], d] = [1, [2, 3], 4]; a // 1 b // 2 d // 4
上面两个例子,都属于不彻底解构,可是能够成功。
若是等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错。
// 报错 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
上面的语句都会报错,由于等号右边的值,要么转为对象之后不具有 Iterator 接口(前五个表达式),要么自己就不具有 Iterator 接口(最后一个表达式)。
剩余运算符
let [a, ...b] = [1, 2, 3]; //a = 1 //b = [2, 3]
字符串
在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。可遍历对象即实现 Iterator 接口的数据。
let [a, b, c, d, e] = 'hello'; // a = 'h' // b = 'e' // c = 'l' // d = 'l' // e = 'o'
解构默认值
解构赋值容许指定默认值。
let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值做为返回结果。
let [a = 2] = [undefined]; // a = 2
注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。因此,只有当一个数组成员严格等于undefined,默认值才会生效。
let [x = 1] = [undefined]; x // 1 let [x = 1] = [null]; x // null
上面代码中,若是一个数组成员是null,默认值就不会生效,由于null不严格等于undefined。
若是默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
function f() { console.log('aaa'); } let [x = f()] = [1];
上面代码中,由于x能取到值,因此函数f根本不会执行。上面的代码其实等价于下面的代码。
let x; if ([1][0] === undefined) { x = f(); } else { x = [1][0]; }
默认值能够引用解构赋值的其余变量,但该变量必须已经声明。
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, b = 1] = []; // ReferenceError: y is not defined
上述代码解释:
- a 与 b 匹配结果为 undefined ,触发默认值:a = 3; b = a =3;
- a 正常解构赋值,匹配结果:a = 1,b 匹配结果 undefined ,触发默认值:b = a =1;
- a 与 b 正常解构赋值,匹配结果:a = 1,b = 2;
- 上面最后一个表达式之因此会报错,是由于x用y作默认值时,y尚未声明。
(1)基本用法
解构不只能够用于数组,还能够用于对象。
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb"
对象的解构与数组有一个重要的不一样。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb" let { baz } = { foo: 'aaa', bar: 'bbb' }; baz // undefined
上面代码的第一个例子,等号左边的两个变量的次序,与等号右边两个同名属性的次序不一致,可是对取值彻底没有影响。第二个例子的变量没有对应的同名属性,致使取不到值,最后等于undefined。
若是解构失败,变量的值等于undefined。
let {foo} = {bar: 'baz'}; foo // undefined
上面代码中,等号右边的对象没有foo属性,因此变量foo取不到值,因此等于undefined。
对象的解构赋值,能够很方便地将现有对象的方法,赋值到某个变量。
// 例一 let { log, sin, cos } = Math; // 例二 const { log } = console; log('hello') // hello
上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便不少。例二将console.log赋值到log变量。
若是变量名与属性名不一致,必须写成下面这样。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; f // 'hello' l // 'world'
这实际上说明,对象的解构赋值是下面形式的简写。
let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };
也就是说,对象的解构赋值的内部机制,是先找到同名属性,而后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" foo // error: foo is not defined
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
(2)嵌套对象的解构赋值
与数组同样,解构也能够用于嵌套结构的对象。
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p: [x, { y }] } = obj; x // "Hello" y // "World"
注意,这时p是模式,不是变量,所以不会被赋值。若是p也要做为变量赋值,能够写成下面这样。
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p, p: [x, { y }] } = obj; x // "Hello" y // "World" p // ["Hello", {y: "World"}]
下面是另外一个例子。
(1)若是要将一个已经声明的变量用于解构赋值,必须很是当心。
// 错误的写法 let x; {x} = {x: 1}; // SyntaxError: syntax error
上面代码的写法会报错,由于 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法 let x; ({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就能够正确执行。关于圆括号与解构赋值的关系,参见下文。
(2)解构赋值容许等号左边的模式之中,不放置任何变量名。所以,能够写出很是古怪的赋值表达式。
({} = [true, false]); ({} = 'abc'); ({} = []);
上面的表达式虽然毫无心义,可是语法是合法的,能够执行。
(3)因为数组本质是特殊的对象,所以能够对数组进行对象属性的解构。
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”。
变量的解构赋值用途不少。
(1)交换变量的值
let x = 1; let y = 2; [x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不只简洁,并且易读,语义很是清晰。
(2)从函数返回多个值
函数只能返回一个值,若是要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就很是方便。
// 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
(3)函数参数的定义
解构赋值能够方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
(4)提取JSON数据
解构赋值对提取 JSON 对象中的数据,尤为有用。
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
上面代码能够快速提取 JSON 数据的值。
(5)函数参数的默认值
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config } = {}) { // ... do stuff };
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || ‘default foo’;这样的语句。
(6)遍历Map结构
任何部署了 Iterator 接口的对象,均可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就很是方便。
const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); } // first is hello // second is world
若是只想获取键名,或者只想获取键值,能够写成下面这样。
// 获取键名 for (let [key] of map) { // ... } // 获取键值 for (let [,value] of map) { // ... }
(7)输入模块的指定方法
加载模块时,每每须要指定输入哪些方法。解构赋值使得输入语句很是清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
传统的 JavaScript 语言,输出模板一般是这样写的(下面使用了 jQuery 的方法)。
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
上面这种写法至关繁琐不方便,ES6 引入了模板字符串解决这个问题。
$('#result').append(` There are <b>${basket.count}</b> items in your basket, <em>${basket.onSale}</em> are on sale! `);
模板字符串(template string)是加强版的字符串,用反引号(`)标识。它能够看成普通字符串使用,也能够用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串 `In JavaScript '\n' is a line-feed.` // 多行字符串 `In JavaScript this is not legal.` console.log(`string text line 1 string text line 2`); // 字符串中嵌入变量 let name = "Bob", time = "today"; `Hello ${name}, how are you ${time}?`
上面代码中的模板字符串,都是用反引号表示。
转义符号
若是在模板字符串中须要使用反引号,则前面要用反斜杠转义。
let greeting = `\`Yo\` World!`;
多行字符串
若是使用模板字符串表示多行字符串,全部的空格和缩进都会被保留在输出之中。
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `);
上面代码中,全部模板字符串的空格和换行,都是被保留的,好比 <ul>
标签前面会有一个换行。若是你不想要这个换行,可使用 trim
方法消除它。
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
插入变量
模板字符串中嵌入变量,须要将变量名写在${}之中。
function authorize(user, action) { if (!user.hasPrivilege(action)) { throw new Error( // 传统写法为 // 'User ' // + user.name // + ' is not authorized to do ' // + action // + '.' `User ${user.name} is not authorized to do ${action}.`); } }
插入表达式
大括号内部能够放入任意的 JavaScript 表达式,能够进行运算,以及引用对象属性。
let x = 1; let y = 2; `${x} + ${y} = ${x + y}` // "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}` // "3"
调用函数
模板字符串之中还能调用函数。
function fn() { return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar
若是大括号中的值不是字符串,将按照通常的规则转为字符串。好比,大括号中是一个对象,将默认调用对象的 toString
方法。
若是模板字符串中的变量没有声明,将报错。
// 变量place没有声明 let msg = `Hello, ${place}`; // 报错
因为模板字符串的大括号内部,就是执行 JavaScript 代码,所以若是大括号内部是一个字符串,将会原样输出。
`Hello ${'World'}` // "Hello World"
注意要点
模板字符串中的换行和空格都是会被保留的
innerHtml = `<ul> <li>menu</li> <li>mine</li> </ul> `; console.log(innerHtml); // 输出 <ul> <li>menu</li> <li>mine</li> </ul>
(1)子串的识别
ES6 以前判断字符串是否包含子串,用 indexOf 方法,ES6 新增了子串的识别方法。
以上三个方法均可以接受两个参数,须要搜索的字符串,和可选的搜索起始位置索引。
let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
上面代码表示,使用第二个参数 n
时, endsWith
的行为与其余两个方法有所不一样。它针对前 n
个字符,而其余两个方法针对从第 n
个位置直到字符串结束。
注意点:
(2)字符串重复
repeat():返回新的字符串,表示将字符串重复指定次数返回。
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // ""
参数若是是小数,会被向下取整。
'na'.repeat(2.9) // "nana"
若是 repeat
的参数是负数或者 Infinity
,会报错。
'na'.repeat(Infinity) // RangeError 'na'.repeat(-1) // RangeError
可是,若是参数是 0 到-1 之间的小数,则等同于 0,这是由于会先进行取整运算。0 到-1 之间的小数,取整之后等于 -0
, repeat
视同为 0。
'na'.repeat(-0.9) // ""
参数 NaN
等同于 0。
'na'.repeat(NaN) // ""
若是 repeat
的参数是字符串,则会先转换成数字。
'na'.repeat('na') // "" 'na'.repeat('3') // "nanana"
(3)字符串补全
ES2017 引入了字符串补全长度的功能。若是某个字符串不够指定长度,会在头部或尾部补全。
以上两个方法接受两个参数,第一个参数是指定生成的字符串的最小长度,第二个参数是用来补全的字符串。若是没有指定第二个参数,默认用空格填充。
console.log("h".padStart(5,"o")); // "ooooh" console.log("h".padEnd(5,"o")); // "hoooo" console.log("h".padStart(5)); // " h" console.log('x'.padStart(5, 'ab')); // 'ababx' console.log('x'.padStart(4, 'ab')); // 'abax' console.log('x'.padEnd(5, 'ab')); // 'xabab' console.log('x'.padEnd(4, 'ab')); // 'xaba'
上面代码中, padStart()
和 padEnd()
一共接受两个参数,第一个参数是字符串补全生效的最大长度,第二个参数是用来补全的字符串。
若是指定的长度小于或者等于原字符串的长度,则返回原字符串:
console.log("hello".padStart(5,"A")); // "hello"
若是原字符串加上补全字符串长度大于指定长度,则截去超出位数的补全字符串:
console.log("hello".padEnd(10,",world!")); // "hello,worl"
若是省略第二个参数,默认使用空格补全长度。
console.log('x'.padStart(4)); // ' x' console.log('x'.padEnd(4)); // 'x '
padStart()
的常见用途是为数值补全指定位数。下面代码生成 10 位的数值字符串。
console.log('1'.padStart(10, '0')); // "0000000001" console.log('12'.padStart(10, '0')); // "0000000012" console.log('123456'.padStart(10, '0')); // "0000123456"
另外一个用途是提示字符串格式。
console.log('12'.padStart(10, 'YYYY-MM-DD')); // "YYYY-MM-12" console.log('09-12'.padStart(10, 'YYYY-MM-DD')); // "YYYY-09-12"
(4)消除空格
ES6对字符串实例新增了 trimStart()
和 trimEnd()
这两个方法。它们的行为与 trim()
一致,trimStart()
消除字符串头部的空格,trimEnd()
消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。
const s = ' abc '; s.trim() // "abc" s.trimStart() // "abc " s.trimEnd() // " abc"
上面代码中,trimStart()
只消除头部的空格,保留尾部的空格。trimEnd()
也是相似行为。
除了空格键,这两个方法对字符串头部(或尾部)的 tab 键、换行符等不可见的空白符号也有效。
浏览器还部署了额外的两个方法,trimLeft()
是 trimStart()
的别名,trimRight()
是 trimEnd()
的别名。
(1)默认值
ES6 以前,不能直接为函数的参数指定默认值,只能采用变通的方法。
function log(x, y) { y = y || 'World'; console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello World
上面代码检查函数log
的参数y
有没有赋值,若是没有,则指定默认值为World
。这种写法的缺点在于,若是参数y
赋值了,可是对应的布尔值为false
,则该赋值不起做用。就像上面代码的最后一行,参数y
等于空字符,结果被改成默认值。
为了不这个问题,一般须要先判断一下参数y
是否被赋值,若是没有,再等于默认值。
if (typeof y === 'undefined') { y = 'World'; }
ES6 容许为函数的参数设置默认值,即直接写在参数定义的后面。
function log(x, y = 'World') { console.log(x, y); } log('Hello') // Hello World log('Hello', 'China') // Hello China log('Hello', '') // Hello
能够看到,ES6 的写法比 ES5 简洁许多,并且很是天然。下面是另外一个例子。
function Point(x = 0, y = 0) { this.x = x; this.y = y; } const p = new Point(); p // { x: 0, y: 0 }
除了简洁,ES6 的写法还有两个好处:首先,阅读代码的人,能够马上意识到哪些参数是能够省略的,不用查看函数体或文档;其次,有利于未来的代码优化,即便将来的版本在对外接口中,完全拿掉这个参数,也不会致使之前的代码没法运行。
参数变量是默认声明的,因此不能用let
或const
再次声明。
function foo(x = 5) { let x = 1; // error const x = 2; // error }
上面代码中,参数变量x
是默认声明的,在函数体中,不能用let
或const
再次声明,不然会报错。
使用参数默认值时,函数不能有同名参数。
// 不报错 function foo(x, x, y) { // ... } // 报错 function foo(x, x, y = 1) { // ... } // SyntaxError: Duplicate parameter name not allowed in this context
另外,一个容易忽略的地方是,参数默认值不是传值的,而是每次都从新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
let x = 99; function foo(p = x + 1) { console.log(p); } foo() // 100 x = 100; foo() // 101
上面代码中,参数p
的默认值是x + 1
。这时,每次调用函数foo
,都会从新计算x + 1
,而不是默认p
等于 100。
(2)不定参数
不定参数用来表示不肯定参数个数,形如,…变量名,由…加上一个具名参数标识符组成。具名参数只能放在参数组的最后,而且有且只有一个不定参数。
基本用法
function f(...values){ console.log(values.length); } f(1,2); //2 f(1,2,3,4); //4
(3)箭头函数
箭头函数提供了一种更加简洁的函数书写方式。基本语法是:
参数 => 函数体
基本用法:
var f = v => v; //等价于 var f = function(a){ return a; } f(1); //1
当箭头函数没有参数或者有多个参数,要用 () 括起来。
var f = (a,b) => a+b; f(6,2); //8
当箭头函数函数体有多行语句,用 {} 包裹起来,表示代码块,当只有一行语句,而且须要返回结果时,能够省略 {} , 结果会自动返回。
var f = (a,b) => { let result = a+b; return result; } f(6,2); // 8
当箭头函数要返回对象的时候,为了区分于代码块,要用 () 将对象包裹起来
// 报错 var f = (id,name) => {id: id, name: name}; f(6,2); // SyntaxError: Unexpected token : // 不报错 var f = (id,name) => ({id: id, name: name}); f(6,2); // {id: 6, name: 2}
注意点:没有 this、super、arguments 和 new.target 绑定。
var func = () => { // 箭头函数里面没有 this 对象, // 此时的 this 是外层的 this 对象,即 Window console.log(this) } func(55) // Window var func = () => { console.log(arguments) } func(55); // ReferenceError: arguments is not defined
箭头函数体中的 this 对象,是定义函数时的对象,而不是使用函数时的对象。
function fn(){ setTimeout(()=>{ // 定义时,this 绑定的是 fn 中的 this 对象 console.log(this.a); },0) } var a = 20; // fn 的 this 对象为 {a: 19} fn.call({a: 18}); // 18
不能够做为构造函数,也就是不能使用 new 命令,不然会报错
(1)扩展运算符
扩展运算符(spread)是三个点(...
)。它比如 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3]) // 1 2 3 console.log(1, ...[2, 3, 4], 5) // 1 2 3 4 5 [...document.querySelectorAll('div')] // [<div>, <div>, <div>]
该运算符主要用于函数调用。
function push(array, ...items) { array.push(...items); } function add(x, y) { return x + y; } const numbers = [4, 38]; add(...numbers) // 42
上面代码中,array.push(...items)
和add(...numbers)
这两行,都是函数的调用,它们都使用了扩展运算符。该运算符将一个数组,变为参数序列。
(2)扩展运算符的应用
复制数组
数组是复合的数据类型,直接复制的话,只是复制了指向底层数据结构的指针,而不是克隆一个全新的数组。
const a1 = [1, 2]; const a2 = a1; a2[0] = 2; a1 // [2, 2]
上面代码中,a2
并非a1
的克隆,而是指向同一份数据的另外一个指针。修改a2
,会直接致使a1
的变化。
ES5 只能用变通方法来复制数组。
const a1 = [1, 2]; const a2 = a1.concat(); a2[0] = 2; a1 // [1, 2]
上面代码中,a1
会返回原数组的克隆,再修改a2
就不会对a1
产生影响。
扩展运算符提供了复制数组的简便写法。
const a1 = [1, 2]; // 写法一 const a2 = [...a1]; // 写法二 const [...a2] = a1;
上面的两种写法,a2
都是a1
的克隆。
合并数组
扩展运算符提供了数组合并的新写法。
const arr1 = ['a', 'b']; const arr2 = ['c']; const arr3 = ['d', 'e']; // ES5 的合并数组 arr1.concat(arr2, arr3); // [ 'a', 'b', 'c', 'd', 'e' ] // ES6 的合并数组 [...arr1, ...arr2, ...arr3] // [ 'a', 'b', 'c', 'd', 'e' ]
不过,这两种方法都是浅拷贝,使用的时候须要注意。
const a1 = [{ foo: 1 }]; const a2 = [{ bar: 2 }]; const a3 = a1.concat(a2); const a4 = [...a1, ...a2]; a3[0] === a1[0] // true a4[0] === a1[0] // true
上面代码中,a3
和a4
是用两种不一样方法合并而成的新数组,可是它们的成员都是对原数组成员的引用,这就是浅拷贝。若是修改了原数组的成员,会同步反映到新数组。
(3)数组实例的find()和findIndex()
数组实例的find
方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,全部数组成员依次执行该回调函数,直到找出第一个返回值为true
的成员,而后返回该成员。若是没有符合条件的成员,则返回undefined
。
[1, 4, -5, 10].find((n) => n < 0) // -5
上面代码找出数组中第一个小于 0 的成员。
[1, 5, 10, 15].find(function(value, index, arr) { return value > 9; }) // 10
上面代码中,find
方法的回调函数能够接受三个参数,依次为当前的值、当前的位置和原数组。
数组实例的findIndex
方法的用法与find
方法很是相似,返回第一个符合条件的数组成员的位置,若是全部成员都不符合条件,则返回-1
。
[1, 5, 10, 15].findIndex(function(value, index, arr) { return value > 9; }) // 2
ES6 容许在大括号里面,直接写入变量和函数,做为对象的属性和方法。这样的书写更加简洁。
const foo = 'bar'; const baz = {foo}; baz // {foo: "bar"} // 等同于const baz = {foo: foo};
除了属性简写,方法也能够简写。
const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
对象的新方法
Object.assign(target, source_1, ···)
用于将源对象的全部可枚举属性复制到目标对象中。
基本用法
let target = {a: 1}; let object2 = {b: 2}; let object3 = {c: 3}; Object.assign(target,object2,object3); // 第一个参数是目标对象,后面的参数是源对象 target; // {a: 1, b: 2, c: 3}
JavaScript 语言中,生成实例对象的传统方法是经过构造函数。下面是一个例子。
function Point(x, y) { this.x = x; this.y = y; } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2);
上面这种写法跟传统的面向对象语言(好比 C++ 和 Java)差别很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,做为对象的模板。经过class
关键字,能够定义类。
基本上,ES6 的class
能够看做只是一个语法糖,它的绝大部分功能,ES5 均可以作到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class
改写,就是下面这样。
class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; } }
上面代码定义了一个“类”,能够看到里面有一个constructor
方法,这就是构造方法,而this
关键字则表明实例对象。也就是说,ES5 的构造函数Point
,对应 ES6 的Point
类的构造方法。
Point
类除了构造方法,还定义了一个toString
方法。注意,定义“类”的方法的时候,前面不须要加上function
这个关键字,直接把函数定义放进去了就能够了。另外,方法之间不须要逗号分隔,加了会报错。
constructor
方法是类的默认方法,经过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,若是没有显式定义,一个空的constructor
方法会被默认添加。
class Point { } // 等同于 class Point { constructor() {} }
上面代码中,定义了一个空的类Point
,JavaScript 引擎会自动为它添加一个空的constructor
方法。
生成类的实例的写法,与 ES5 彻底同样,也是使用new
命令。前面说过,若是忘记加上new
,像函数那样调用Class
,将会报错。
class Point { // ... } // 报错 var point = Point(2, 3); // 正确 var point = new Point(2, 3);
Class 能够经过extends
关键字实现继承,这比 ES5 的经过修改原型链实现继承,要清晰和方便不少。
class Point { } class ColorPoint extends Point { }
super关键字
super
这个关键字,既能够看成函数使用,也能够看成对象使用。在这两种状况下,它的用法彻底不一样。
第一种状况,super
做为函数调用时,表明父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
class A {} class B extends A { constructor() { super(); } }
上面代码中,子类B
的构造函数之中的super()
,表明调用父类的构造函数。这是必须的,不然 JavaScript 引擎会报错。
第二种状况,super
做为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A { p() { return 2; } } class B extends A { constructor() { super(); console.log(super.p()); // 2 } } let b = new B();
上面代码中,子类B
当中的super.p()
,就是将super
看成一个对象使用。这时,super
在普通方法之中,指向A.prototype
,因此super.p()
就至关于A.prototype.p()
。
类至关于实例的原型,全部在类中定义的方法,都会被实例继承。若是在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接经过类来调用,这就称为“静态方法”。
class Foo { static classMethod() { return 'hello'; } } Foo.classMethod() // 'hello' var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
上面代码中,Foo
类的classMethod
方法前有static
关键字,代表该方法是一个静态方法,能够直接在Foo
类上调用(Foo.classMethod()
),而不是在Foo
类的实例上调用。若是在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
静态属性指的是 Class 自己的属性,即Class.propName
,而不是定义在实例对象(this
)上的属性。
class Foo { } Foo.prop = 1; Foo.prop // 1
上面的写法为Foo
类定义了一个静态属性prop
。
目前,只有这种写法可行,由于 ES6 明确规定,Class 内部只有静态方法,没有静态属性。如今有一个提案提供了类的静态属性,写法是在实例属性的前面,加上static
关键字。
class MyClass { static myStaticProp = 42; constructor() { console.log(MyClass.myStaticProp); // 42 } }
这个新写法大大方便了静态属性的表达。
// 老写法 class Foo { // ... } Foo.prop = 1; // 新写法 class Foo { static prop = 1; }
上面代码中,老写法的静态属性定义在类的外部。整个类生成之后,再生成静态属性。这样让人很容易忽略这个静态属性,也不符合相关代码应该放在一块儿的代码组织原则。另外,新写法是显式声明(declarative),而不是赋值处理,语义更好。
ES6 提供了新的数据结构 Set。它相似于数组,可是成员的值都是惟一的,没有重复的值。
基础用法:
let mySet = new Set(); mySet.add(1); // Set(1) {1} mySet.add(5); // Set(2) {1, 5} mySet.add(5); // Set(2) {1, 5} 这里体现了值的惟一性 mySet.add("some text"); // Set(3) {1, 5, "some text"} 这里体现了类型的多样性 var o = {a: 1, b: 2}; mySet.add(o); mySet.add({a: 1, b: 2}); // Set(5) {1, 5, "some text", {…}, {…}} // 这里体现了对象之间引用不一样不恒等,即便值相同,Set 也能存储
上面代码经过add()
方法向 Set 结构加入成员,结果代表 Set 结构不会添加剧复的值。
Set
函数能够接受一个数组(或者具备 iterable 接口的其余数据结构)做为参数,用来初始化。
// 例一 const set = new Set([1, 2, 3, 4, 4]); [...set] // [1, 2, 3, 4] // 例二 const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]); items.size // 5
数据类型转换
Array与Set类型转换
// Array 转 Set var mySet = new Set(["value1", "value2", "value3"]); // 用...操做符,将 Set 转 Array var myArray = [...mySet]; //Array.from方法能够将 Set 结构转为数组。 const items = new Set([1, 2, 3, 4, 5]); const array = Array.from(items);
String与Set类型转换
// String 转 Set var mySet = new Set('hello'); // Set(4) {"h", "e", "l", "o"} // 注:Set 中 toString 方法是不能将 Set 转换成 String
Set实例的属性
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。Set实例的操做方法
Set.prototype.add(value)
:添加某个值,返回 Set 结构自己。Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set
的成员。Set.prototype.clear()
:清除全部成员,没有返回值。代码示例:
s.add(1).add(2).add(2); // 注意2被加入了两次 s.size // 2 s.has(1) // true s.has(2) // true s.has(3) // false s.delete(2); s.has(2) // false
Set实例的遍历方法
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每一个成员须要特别指出的是,Set
的遍历顺序就是插入顺序。这个特性有时很是有用,好比使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
代码示例:
keys
方法、values
方法、entries
方法返回的都是遍历器对象(详见《Iterator 对象》一章)。因为 Set 结构没有键名,只有键值(或者说键名和键值是同一个值),因此keys
方法和values
方法的行为彻底一致。
let set = new Set(['red', 'green', 'blue']); for (let item of set.keys()) { console.log(item); } // red // green // blue for (let item of set.values()) { console.log(item); } // red // green // blue for (let item of set.entries()) { console.log(item); } // ["red", "red"] // ["green", "green"] // ["blue", "blue"]
forEach()代码示例:
let set = new Set([1, 4, 9]); set.forEach((value, key) => console.log(key + ' : ' + value)) // 1 : 1 // 4 : 4 // 9 : 9
遍历的应用
(1)数组去重
var mySet = new Set([1, 2, 3, 4, 4]); [...mySet]; // [1, 2, 3, 4]
(2)并集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var union = new Set([...a, ...b]); // {1, 2, 3, 4}
(3)交集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var intersect = new Set([...a].filter(x => b.has(x))); // {2, 3}
(4)差集
var a = new Set([1, 2, 3]); var b = new Set([4, 3, 2]); var difference = new Set([...a].filter(x => !b.has(x))); // {1}
Map 对象保存键值对。任何值(对象或者原始值) 均可以做为一个键或一个值。
基本用法:
const m = new Map(); const o = {p: 'Hello World'}; m.set(o, 'content') m.get(o) // "content" m.has(o) // true m.delete(o) // true m.has(o) // false
Map中的key
key是字符串
var myMap = new Map(); var keyString = "a string"; myMap.set(keyString, "和键'a string'关联的值"); myMap.get(keyString); // "和键'a string'关联的值" myMap.get("a string"); // "和键'a string'关联的值" // 由于 keyString === 'a string'
key是对象
var myMap = new Map(); var keyObj = {}, myMap.set(keyObj, "和键 keyObj 关联的值"); myMap.get(keyObj); // "和键 keyObj 关联的值" myMap.get({}); // undefined, 由于 keyObj !== {}
key是函数
var myMap = new Map(); var keyFunc = function () {}, // 函数 myMap.set(keyFunc, "和键 keyFunc 关联的值"); myMap.get(keyFunc); // "和键 keyFunc 关联的值" myMap.get(function() {}) // undefined, 由于 keyFunc !== function () {}
Map的遍历
对 Map 进行遍历,如下两个最高级。
(1)for…of
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 将会显示两个 log。 一个是 "0 = zero" 另外一个是 "1 = one" for (var [key, value] of myMap) { console.log(key + " = " + value); } for (var [key, value] of myMap.entries()) { console.log(key + " = " + value); } /* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每一个元素的 [key, value] 数组。 */ // 将会显示两个log。 一个是 "0" 另外一个是 "1" for (var key of myMap.keys()) { console.log(key); } /* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每一个元素的键。 */ // 将会显示两个log。 一个是 "zero" 另外一个是 "one" for (var value of myMap.values()) { console.log(value); } /* 这个 values 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每一个元素的值。 */
(2)forEach()
var myMap = new Map(); myMap.set(0, "zero"); myMap.set(1, "one"); // 将会显示两个 logs。 一个是 "0 = zero" 另外一个是 "1 = one" myMap.forEach(function(value, key) { console.log(key + " = " + value); }, myMap)
是异步编程的一种解决方案。
从语法上说,Promise 是一个对象,从它能够获取异步操做的消息。
基本用法:
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操做成功 */){ resolve(value); } else { reject(error); } }); promise.then(function(value) { // success }, function(error) { // failure });
Promise
构造函数接受一个函数做为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用本身部署。
resolve
函数的做用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操做成功时调用,并将异步操做的结果,做为参数传递出去;reject
函数的做用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操做失败时调用,并将异步操做报出的错误,做为参数传递出去。
Promise
实例生成之后,能够用then
方法分别指定resolved
状态和rejected
状态的回调函数。
then
方法能够接受两个回调函数做为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不必定要提供。这两个函数都接受Promise
对象传出的值做为参数。
Promise 异步操做有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操做的结果,任何其余操做都没法改变这个状态。
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型)。
const p1 = new Promise(function(resolve,reject){ resolve('success1'); resolve('success2'); }); const p2 = new Promise(function(resolve,reject){ resolve('success3'); reject('reject'); }); p1.then(function(value){ console.log(value); // success1 }); p2.then(function(value){ console.log(value); // success3 });
状态的缺点
没法取消 Promise ,一旦新建它就会当即执行,没法中途取消。
若是不设置回调函数,Promise 内部抛出的错误,不会反应到外部。
当处于 pending 状态时,没法得知目前进展到哪个阶段(刚刚开始仍是即将完成)。
then()方法
then 方法接收两个函数做为参数,第一个参数是 Promise 执行成功时的回调,第二个参数是 Promise 执行失败时的回调,两个函数只会有一个被调用。
在 JavaScript 事件队列的当前运行完成以前,回调函数永远不会被调用。
const p = new Promise(function(resolve,reject){ resolve('success'); }); p.then(function(value){ console.log(value); }); console.log('first'); // first // success
catch()方法
Promise.prototype.catch
方法是.then(null, rejection)
或.then(undefined, rejection)
的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); });
上面代码中,getJSON
方法返回一个 Promise 对象,若是该对象状态变为resolved
,则会调用then
方法指定的回调函数;若是异步操做抛出错误,状态就会变为rejected
,就会调用catch
方法指定的回调函数,处理这个错误。另外,then
方法指定的回调函数,若是运行中抛出错误,也会被catch
方法捕获。
p.then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); // 等同于 p.then((val) => console.log('fulfilled:', val)) .then(null, (err) => console.log("rejected:", err));
all()方法
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
上面代码中,Promise.all()
方法接受一个数组做为参数,p1
、p2
、p3
都是 Promise 实例,若是不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数能够不是数组,但必须具备 Iterator 接口,且返回的每一个成员都是 Promise 实例。
p
的状态由p1
、p2
、p3
决定,分红两种状况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
下面是一个具体的例子。
// 生成一个Promise对象的数组 const promises = [2, 3, 5, 7, 11, 13].map(function (id) { return getJSON('/post/' + id + ".json"); }); Promise.all(promises).then(function (posts) { // ... }).catch(function(reason){ // ... });
上面代码中,promises
是包含 6 个 Promise 实例的数组,只有这 6 个实例的状态都变成fulfilled
,或者其中有一个变为rejected
,才会调用Promise.all
方法后面的回调函数。
ES2017 标准引入了 async 函数,使得异步操做变得更加方便。
async 函数是什么?一句话,它就是 Generator 函数的语法糖
async
函数返回一个 Promise 对象,可使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操做完成,再接着执行函数体内后面的语句。
下面是一个例子。
async function getStockPriceByName(name) { const symbol = await getStockSymbol(name); const stockPrice = await getStockPrice(symbol); return stockPrice; } getStockPriceByName('goog').then(function (result) { console.log(result); });
上面代码是一个获取股票报价的函数,函数前面的async
关键字,代表该函数内部有异步操做。调用该函数时,会当即返回一个Promise
对象。
async
函数返回一个 Promise 对象。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async function f() { return 'hello world'; } f().then(v => console.log(v)) // "hello world"
上面代码中,函数f
内部return
命令返回的值,会被then
方法回调函数接收到。
async
函数内部抛出错误,会致使返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。
async function f() { throw new Error('出错了'); } f().then( v => console.log(v), e => console.log(e) ) // Error: 出错了