ECMAScript 即是大家熟悉的 JavaScript,简称 js,作为一个前端,如果说不熟悉 ES6 就算不上一个真正的前端。本文详细而简单地介绍了 JavaScript 的发展历程并介绍了 ECMAScript 中的里程碑版本 ES5、ES6、ES7 的常用知识
1 ES6 的发展背景 JavaScript 是大家所了解的语言名称,但是这个语言名称是商标( Oracle 公司注册的商标)。因此,JavaScript 的正式名称是 ECMAScript 。1996 年 11 月,JavaScript 的创造者网景公司将 JS 提交给国际化标准组织 ECMA(European computer manufactures association,欧洲计算机制造联合会),希望这种语言能够成为国际标准,随后 ECMA 发布了规定浏览器脚本语言的标准,即 ECMAScript。这也有利于这门语言的开放和中立。
1.1 ECMAScript 的历史
1997 年 ECMAScript 1.0 诞生。
1998 年 6 月 ECMAScript 2.0 诞生,包含一些小的更改,用于同步独立的 ISO 国际标准。
1999 年 12 月 ECMAScript 3.0 诞生,它是一个巨大的成功,在业界得到了广泛的支持,它奠定了 JS 的基本语法,被其后版本完全继承。直到今天,我们一开始学习 JS ,其实就是在学 3.0 版的语法。
2000 年的 ECMAScript 4.0 是当下 ES6 的前身,但由于这个版本太过激烈,对 ES 3 做了彻底升级,所以暂时被”和谐”了。
2009 年 12 月,ECMAScript 5.0 版正式发布。ECMA 专家组预计 ECMAScript 的第五个版本会在 2013 年中期到 2018 年作为主流的开发标准。2011 年 6 月,ES 5.1 版发布,并且成为 ISO 国际标准。
2013 年,ES6 草案冻结,不再添加新的功能,新的功能将被放到 ES7 中;2015 年 6 月, ES6 正式通过,成为国际标准。
1.2 ECMAScript 的发布周期 在 2015 年发布的 ECMAScript(ES6)新增内容很多,在 ES5 发布近 6 年(2009-11 至 2015-6)之后才将其标准化。两个发布版本之间时间跨度如此之大主要有两大原因:
比新版率先完成的特性,必须等待新版的完成才能发布。
那些需要花长时间完成的特性,也顶着很大的压力被纳入这一版本,因为如果推迟到下一版本发布意味着又要等很久,这种特性也会推迟新的发布版本。
因此,从 ECMAScript 2016(ES7)开始,版本发布变得更加频繁,每年发布一个新版本,这么一来新增内容也会更小。新版本将会包含每年截止时间之前完成的所有特性。
1.3 ECMAScript 的发布流程 每个 ECMAScript 特性的建议将会从阶段 0 开始, 然后经过下列几个成熟阶段。其中从一个阶段到下一个阶段必须经过 TC39 的批准。
stage-0 - Strawman: just an idea, possible Babel plugin.
任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有 TC39 成员可以提交。
stage-1 - Proposal: this is worth working on.
什么是 Proposal?一份新特性的正式建议文档。提案必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。
stage-2 - Draft: initial spec.
什么是 Draft?草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。
草案之后,原则上只接受增量修改。这个阶段开始实验如何实现,实现形式包括 polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如 babel)
stage-3 - Candidate: complete spec and initial browser implementations.
候选阶段,获得具体实现和用户的反馈。此后,只有在实现和使用过程中出现了重大问题才会修改。至少要在一个浏览器中实现,提供 polyfill 或者 babel 插件。
stage-4 - Finished: will be added to the next yearly release.
已经准备就绪,该特性会出现在下个版本的 ECMAScript 规范之中。
1.4 几个重要版本
es5: 09 年发布
es6(ES2015): 2015 年发布,也称之为 ECMA2015
es7(ES2016): 2016 年发布,变化不大
es8(es2017): 2017 发布
ES7 在 ES6 的基础上主要添加了两项内容:
Array.prototype.includes()方法
求幂运算符(**)
在 2017 年 1 月的 TC39 会议上,ECMAScript 2017 的最后一个功能“Shared memory and atomics”推进到第 4 阶段。这意味着它的功能集现已完成。
主要新功能:
异步函数 Async Functions(Brian Terlson)
共享内存和 Atomics(Lars T. Hansen)
次要新功能:
Object.values / Object.entries(Jordan Harband) String padding(Jordan Harband,Rick Waldron) Object.getOwnPropertyDescriptors() (Jordan Harband,Andrea Giammarchi) 函数参数列表和调用中的尾逗号(Jeff Morrison)
2 ES5 基础知识 2.1 严格模式
运行模式: 正常(混杂)模式与严格模式
应用上严格式: ‘strict mode’;
作用:
使得 Javascript 在更严格的条件下运行
消除 Javascript 语法的一些不合理、不严谨之处,减少一些怪异行为
消除代码运行的一些不安全之处,保证代码运行的安全
需要记住的几个变化
声明定义变量必须用 var
禁止自定义的函数中的 this 关键字指向全局对象
创建 eval 作用域, 更安全
"use strict" 指令只允许出现在脚本或函数的开头
严格模式的限制
不允许使用未声明的变量:
1 2 "use strict"; x = 3.14; // 报错 (x 未定义)
对象也是一个变量
1 2 "use strict"; x = {p1:10, p2:20}; // 报错 (x 未定义)
3 不允许删除变量或对象
1 2 3 "use strict"; var x = 3.14; delete x; // 报错
4 不允许删除函数
1 2 3 "use strict"; function x(p1, p2) {}; delete x; // 报错
5 不允许变量重名
1 2 "use strict"; function x(p1, p1) {}; // 报错
6 不允许使用八进制
1 2 "use strict"; var x = 010; // 报错
7 不允许使用转义字符
1 2 "use strict"; var x = \010; // 报错
8 不允许对只读属性赋值
1 2 3 4 5 "use strict"; var obj = {}; Object.defineProperty(obj, "x", {value:0, writable:false}); obj.x = 3.14; // 报错
9 不允许对一个使用 getter 方法读取的属性进行赋值
1 2 3 4 "use strict"; var obj = {get x() {return 0} }; obj.x = 3.14; // 报错
10 由于一些安全原因,在作用域 eval() 创建的变量不能被调用
1 2 3 "use strict"; eval ("var x = 2"); alert (x); // 报错
11 禁止 this 关键字指向全局对象
1 2 3 4 5 6 7 8 9 10 function f(){ return !this; } // 返回false,因为"this"指向全局对象,"!this"就是false function f(){ "use strict"; return !this; } // 返回true,因为严格模式下,this的值为undefined,所以"!this"为true。
12 使用构造函数时,如果忘了加 new,this 不再指向全局对象
1 2 3 4 5 function f(){ "use strict"; this.a = 1; }; f();// 报错,this未定义
2.2 JSON 对象
作用: 用于在 json 对象/数组与 js 对象/数组相互转换
JSON.stringify(obj/arr) js 对象(数组)转换为 json 对象(数组)
1 JSON.stringify(value[, replacer[, space]])
参数说明:
value: 必需, 要转换的 JavaScript 值(通常为对象或数组)。
replacer: 可选。用于转换结果的函数或数组。
如果 replacer 为函数,则 JSON.stringify 将调用该函数,并传入每个成员的键和值。使用返回值而不是原始值。如果此函数返回 undefined,则排除成员。根对象的键是一个空字符串:””。
如果 replacer 是一个数组,则仅转换该数组中具有键值的成员。成员的转换顺序与键在数组中的顺序一样。当 value 参数也为数组时,将忽略 replacer 数组。
space: 可选,文本添加缩进、空格和换行符,如果 space 是一个数字,则返回值文本在每个级别缩进指定数目的空格,如果 space 大于 10,则文本缩进 10 个空格。space 也可以使用非数字,如:\t。
1 2 3 4 5 var obj = { "name":"runoob", "alexa":10000, "site":"www.runoob.com"}; var myJSON = JSON.stringify(obj); //得到的结果 {"name":"runoob","alexa":10000,"site":"www.runoob.com"}
JSON.parse(json) json 对象(数组)转换为 js 对象(数组)
1 JSON.parse(text[, reviver])
参数说明:
text:必需, 一个有效的 JSON 字符串。
reviver: 可选,一个转换结果的函数, 将为对象的每个成员调用此函数。
2.3 Object 扩展 Object 构造函数为给定值创建一个对象包装器。如果给定值是 null 或 undefined,将会创建并返回一个空对象,否则,将返回一个与给定值对应类型的对象。
当以非构造函数形式被调用时,Object 等同于 new Object()。
Object.create(prototype[, descriptors]) : 创建一个新的对象
以指定对象为原型创建新的对象
指定新的属性, 并对属性进行描述
value : 指定值
writable : 标识当前属性值是否是可修改的, 默认为 true
get 方法 : 用来得到当前属性值的回调函数
set 方法 : 用来监视当前属性值变化的回调函数
Object.defineProperties(object, descriptors) : 为指定对象定义扩展多个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 var obj = {name : 'curry', age : 29} var obj1 = {}; obj1 = Object.create(obj, { sex : { value : '男', writable : true } }); obj1.sex = '女'; console.log(obj1.sex); //Object.defineProperties(object, descriptors) var obj2 = { firstName : 'curry', lastName : 'stephen' }; Object.defineProperties(obj2, { fullName : { get : function () { return this.firstName + '-' + this.lastName }, set : function (data) { var names = data.split('-'); this.firstName = names[0]; this.lastName = names[1]; } } }); console.log(obj2.fullName); obj2.firstName = 'tim'; obj2.lastName = 'duncan'; console.log(obj2.fullName); obj2.fullName = 'kobe-bryant'; console.log(obj2.fullName);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <!-- 对象本身的两个方法 * get propertyName(){} 用来得到当前属性值的回调函数 * set propertyName(){} 用来监视当前属性值变化的回调函数 --> <script type='text/javascript'> var obj = { firstName : 'kobe', lastName : 'bryant', get fullName(){ return this.firstName + ' ' + this.lastName }, set fullName(data){ var names = data.split(' '); this.firstName = names[0]; this.lastName = names[1]; } }; console.log(obj.fullName); obj.fullName = 'curry stephen'; console.log(obj.fullName); </script>
一些常用的方法
Object.assign()
通过复制一个或多个对象来创建一个新的对象。
Object.getOwnPropertyNames()
返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。
Object.is()
比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。
Object.keys()
返回一个包含所有给定对象自身可枚举属性名称的数组。
Object.values()
返回给定对象自身可枚举值的数组。
其他更多方法参见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object
2.4 Array 扩展
Array.prototype.indexOf(value) : 得到值在数组中的第一个下标
Array.prototype.lastIndexOf(value) : 得到值在数组中的最后一个下标
Array.prototype.forEach(function(item, index){}) : 遍历数组
Array.prototype.map(function(item, index){}) : 遍历数组返回一个新的数组
Array.prototype.filter(function(item, index){}) : 遍历过滤出一个子数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <script type="text/javascript"> /* 需求: 1. 输出第一个6的下标 2. 输出最后一个6的下标 3. 输出所有元素的值和下标 4. 根据arr产生一个新数组,要求每个元素都比原来大10 5. 根据arr产生一个新数组, 返回的每个元素要大于4 */ var arr = [1, 4, 6, 2, 5, 6]; console.log(arr.indexOf(6));//2 //Array.prototype.lastIndexOf(value) : 得到值在数组中的最后一个下标 console.log(arr.lastIndexOf(6));//5 //Array.prototype.forEach(function(item, index){}) : 遍历数组 arr.forEach(function (item, index) { console.log(item, index); }); //Array.prototype.map(function(item, index){}) : 遍历数组返回一个新的数组,返回加工之后的值 var arr1 = arr.map(function (item, index) { return item + 10 }); console.log(arr, arr1); //Array.prototype.filter(function(item, index){}) : 遍历过滤出一个新的子数组, 返回条件为true的值 var arr2 = arr.filter(function (item, index) { return item > 4 }); console.log(arr, arr2); </script>
2.5 Function 扩展
Function.prototype.bind(obj)
将函数内的 this 绑定为 obj, 并将函数返回
面试题: 区别 bind()与 call()和 apply()?
fn.bind(obj) : 指定函数中的 this, 并返回函数
fn.call(obj) : 指定函数中的 this,并调用函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!-- 1. Function.prototype.bind(obj) : * 作用: 将函数内的this绑定为obj, 并将函数返回 2. 面试题: 区别bind()与call()和apply()? * 都能指定函数中的this * call()/apply()是立即调用函数 * bind()是将函数返回 --> <script type="text/javascript"> var obj={'aa':'sfsf'} function foo(data){ console.log(this,data); } foo.call(obj,33);//直接从第二个参数开始,依次传入 foo.apply(obj,[33]); //第二个参数必须是数组,传入放在数组里面 function fun(age) { this.name = 'kobe'; this.age = age; console.log('dddddddddddddd'); } var obj = {}; fun.bind(obj, 12)(); //绑定完不会立即调用,而是将函数返回 console.log(obj.name, obj.age); </script>
2.6 Date 扩展
3 ES6 基础语法 3.1 let 与 const ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。
let 声明的变量只在 let 命令所在的代码块内有效。
const 声明一个只读的常量,一旦声明,常量的值就不能改变。
let 命令
基本用法:
1 2 3 4 5 { let a = 0; a // 0 } a // 报错 ReferenceError: a is not defined
代码块内有效
let 是在代码块内有效,var 是在全局范围内有效
1 2 3 4 5 6 { let a = 0; var b = 1; } a // ReferenceError: a is not defined b // 1
不能重复声明
let 只能声明一次 var 可以声明多次:
1 2 3 4 5 6 let a = 1; let a = 2; var b = 3; var b = 4; a // Identifier 'a' has already been declared b // 4
for 循环计数器很适合用 let
1 2 3 4 5 6 7 8 9 10 11 12 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 声明的,当前的 i 只在本轮循环中有效,每次循环的 j 其实都是一个新的变量,所以 setTimeout 定时器里面的 j 其实是不同的变量,即最后输出 12345。(若每次循环的变量 j 都是重新声明的,如何知道前一个循环的值?这是因为 JavaScript 引擎内部会记住前一个循环的值)。
数组迭代也可以
1 2 3 4 5 6 7 for (let item of ["zero", "one", "two"]) { console.log(item); } // output: // zero // one // two
其他更多用法参见 https://www.runoob.com/w3cnote/es6-iterator.html
不存在变量提升 let 不存在变量提升,var 会变量提升:
1 2 3 4 5 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 不存在,所以会报错
解构赋值
1 2 3 4 5 6 7 let obj={'username':'yishui',age:19} console.log(obj); let {username,age}=obj; console.log({username,age});
const 命令
const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
基本用法:
1 2 3 4 const PI = "3.1415926"; PI // 3.1415926 const MY_AGE; // SyntaxError: Missing initializer in const declaration
暂时性死区:
1 2 3 4 5 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 声明复杂类型对象时要慎重。
3.2 对象的简写模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!-- 简化的对象写法 * 省略同名的属性值 * 省略方法的function * 例如: let x = 1; let y = 2; let point = { x, y, setX (x) {this.x = x} }; --> <script type="text/javascript"> let x = 3; let y = 5; //普通额写法 // let obj = { // x : x, // y : y, // getPoint : function () { // return this.x + this.y // } // }; //简化的写法 let obj = { x, y, getPoint(){ return this.x } }; console.log(obj, obj.getPoint()); </script>
对象的其他方法参见 https://www.runoob.com/w3cnote/es6-object.html
3.3 箭头函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <!-- * 作用: 定义匿名函数 * 基本语法: * 没有参数: () => console.log('xxxx') * 一个参数: i => i+2 * 大于一个参数: (i,j) => i+j * 函数体不用大括号: 默认返回结果 * 函数体如果有多个语句, 需要用{}包围,若有需要返回的内容,需要手动返回 * 使用场景: 多用来定义回调函数 * 箭头函数的特点: 1、简洁 2、箭头函数没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this 3、扩展理解: 箭头函数的this看外层的是否有函数, 如果有,外层函数的this就是内部箭头函数的this, 如果没有,则this是window。 --> <script type="text/javascript"> let fun = function () { console.log('fun()'); }; fun(); //没有形参,并且函数体只有一条语句 let fun1 = () => console.log('fun1()'); fun1(); console.log(fun1()); //一个形参,并且函数体只有一条语句 let fun2 = x => x; console.log(fun2(5)); //形参是一个以上 let fun3 = (x, y) => x + y; console.log(fun3(25, 39));//64 //函数体有多条语句 let fun4 = (x, y) => { console.log(x, y); return x + y; }; console.log(fun4(34, 48));//82 setTimeout(() => { console.log(this); },1000) let btn = document.getElementById('btn'); //没有箭头函数 btn.onclick = function () { console.log(this);//btn }; //箭头函数 let btn2 = document.getElementById('btn2'); let obj = { name : 'kobe', age : 39, getName : () => { btn2.onclick = () => { console.log(this);//obj }; } }; obj.getName(); function Person() { this.obj = { showThis : () => { console.log(this); } } } let fun5 = new Person(); fun5.obj.showThis(); </script>
其他使用方法参见 https://www.runoob.com/w3cnote/es6-function.html
3.4 三点运算符 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!-- * 用途 1. rest(可变)参数 * 用来取代arguments 但比arguments灵活,只能是最后部分形参参数 function add(...values) { let sum = 0; for(value of values) { sum += value; } return sum; } 2. 扩展运算符 let arr1 = [1,3,5]; let arr2 = [2,...arr1,6]; arr2.push(...arr1); --> <script type="text/javascript"> function fun(...values) { console.log(arguments); // arguments.forEach(function (item, index) { // console.log(item, index); // }); console.log(values); values.forEach(function (item, index) { console.log(item, index); }) } fun(1,2,3); let arr = [2,3,4,5,6]; let arr1 = ['abc',...arr, 'fg']; console.log(arr1); </script>
3.5 形参默认值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!-- * 形参的默认值----当不传入参数的时候默认使用形参里的默认值 function Point(x = 1,y = 2) { this.x = x; this.y = y; } --> <script type="text/javascript"> //定义一个点的坐标 function Point(x=12, y=12) { this.x = x; this.y = y; } let point = new Point(25, 36); console.log(point); let p = new Point(); console.log(p); let point1 = new Point(12, 35); console.log(point1); </script>
3.6 Promise 对象 3.6.1 理解 Promise 对象
Promise 对象: 代表了未来某个将要发生的事件(通常是一个异步操作)
有了 promise 对象, 可以将异步操作以同步的流程表达出来, 避免了层层嵌套的回调函数(俗称’回调地狱 ‘)
ES6 的 Promise 是一个构造函数, 用来生成 promise 实例
3.6.2 使用 promise 基本步骤 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 * 创建promise对象 let promise = new Promise((resolve, reject) => { //初始化promise状态为 pending //执行异步操作 if(异步操作成功) { resolve(value);//修改promise的状态为fullfilled } else { reject(errMsg);//修改promise的状态为rejected } }) * 调用promise的then() promise.then(function( result => console.log(result), errorMsg => alert(errorMsg) ))
3.6.3 promise 对象的 3 个状态
pending: 初始化状态
fullfilled: 成功状态
rejected: 失败状态
Promise 异步操作有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。除了异步操作的结果,任何其他操作都无法改变这个状态。
1 2 3 graph LR pending(进行中)-->fulfilled(已成功) pending(进行中)-->rejected(已失败)
Promise 对象只有:从 pending 变为 fulfilled 和从 pending 变为 rejected 的状态改变。只要处于 fulfilled 和 rejected ,状态就不会再变了即 resolved(已定型) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const p1 = new Promise(function(resolve,reject){ resolve('success1'); resolve('success2'); }); const p2 = new Promise(function(){ 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 执行失败时的回调,两个函数只会有一个被调用。
then 方法的特点
在 JavaScript 事件队列的当前运行完成之前,回调函数永远不会被调用。
1 2 3 4 5 6 7 8 9 10 11 const p = new Promise(function(resolve,reject){ resolve('success'); }); p.then(function(value){ console.log(value); }); console.log('first'); // first // success
通过 .then 形式添加的回调函数,不论什么时候,都会被调用。
通过多次调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ,可以添加多个回调函数,它们会按照插入顺序并且独立运行。 const p = new Promise(function(resolve,reject){ resolve(1); }).then(function(value){ // 第一个then // 1 console.log(value); return value * 2; }).then(function(value){ // 第二个then // 2 console.log(value); }).then(function(value){ // 第三个then // undefined console.log(value); return Promise.resolve('resolve'); }).then(function(value){ // 第四个then // resolve console.log(value); return Promise.reject('reject'); }).then(function(value){ // 第五个then //reject:reject console.log('resolve:' + value); }, function(err) { console.log('reject:' + err); });
上述代码的执行结果为:
then 方法将返回一个 resolved 或 rejected 状态的 Promise 对象用于链式调用,且 Promise 对象的值就是这个返回值。
then 方法注意点
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise。
注意总是返回或终止 Promise 链。
1 2 3 4 5 const p1 = new Promise(function(resolve,reject){ resolve(1); }).then(function(result) { p2(result).then(newResult => p3(newResult)); }).then(() => p4());
创建新 Promise 但忘记返回它时,对应链条被打破,导致 p4 会与 p2 和 p3 同时进行。
大多数浏览器中不能终止的 Promise 链里的 rejection,建议后面都跟上
1 .catch(error => console.log(error));
3.6.4 应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //创建一个promise实例对象 let promise = new Promise((resolve, reject) => { //初始化promise的状态为pending---->初始化状态 console.log('1111');//同步执行 //启动异步任务 setTimeout(function () { console.log('3333'); //异步任务执行成功 //resolve('atguigu.com');//修改promise的状态pending---->fullfilled(成功状态) //异步任务执行失败 reject('xxxx');//修改promise的状态pending----->rejected(失败状态) },1000) }); promise.then((data) => { console.log('成功了。。。' + data); }, (error) => { console.log('失败了' + error); }); console.log('2222');
代码的执行结果为
1 2 3 4 5 1111 2222 //(间隔一段时间) 3333 失败了xxxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 //定义一个请求news的方法 function getNews(url) { //创建一个promise对象 let promise = new Promise((resolve, reject) => { //初始化promise状态为pending //启动异步任务 let request = new XMLHttpRequest(); request.onreadystatechange = function () { if(request.readyState === 4){ if(request.status === 200){ let news = request.response; resolve(news); }else{ reject('请求失败了。。。'); } } }; request.responseType = 'json';//设置返回的数据类型 request.open("GET", url);//规定请求的方法,创建链接 request.send();//发送 }) return promise; } getNews('http://localhost:3000/news?id=2') .then((news) => { console.log(news); document.write(JSON.stringify(news)); console.log('http://localhost:3000' + news.commentsUrl); return getNews('http://localhost:3000' + news.commentsUrl); }, (error) => { alert(error); }) .then((comments) => { console.log(comments); document.write('<br><br><br><br><br>' + JSON.stringify(comments)); }, (error) => { alert(error); })
示例 2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function ajax(URL) { return new Promise(function (resolve, reject) { var req = new XMLHttpRequest(); req.open('GET', URL, true); req.onload = function () { if (req.status === 200) { resolve(req.responseText); } else { reject(new Error(req.statusText)); } }; req.onerror = function () { reject(new Error(req.statusText)); }; req.send(); }); } var URL = "/try/ajax/testpromise.php"; ajax(URL).then(function onFulfilled(value){ document.write('内容是:' + value); }).catch(function onRejected(error){ document.write('错误:' + error); });
上面代码中,resolve 方法和 reject 方法调用时,都带有参数。它们的参数会被传递给回调函数。reject 方法的参数通常是 Error 对象的实例,而 resolve 方法的参数除了正常的值以外,还可能是另一个 Promise 实例,比如像下面这样。
1 2 3 4 5 6 7 8 var p1 = new Promise(function(resolve, reject){ // ... some code }); var p2 = new Promise(function(resolve, reject){ // ... some code resolve(p1); })
上面代码中,p1 和 p2 都是 Promise 的实例,但是 p2 的 resolve 方法将 p1 作为参数,这时 p1 的状态就会传递给 p2。如果调用的时候,p1 的状态是 pending,那么 p2 的回调函数就会等待 p1 的状态改变;如果 p1 的状态已经是 fulfilled 或者 rejected,那么 p2 的回调函数将会立刻执行。
更多方法参见 https://www.runoob.com/w3cnote/javascript-promise-object.html
3.7 Symbol 属性 ES5 中对象的属性名都是字符串,容易造成重名,污染环境
ES6 中的添加了一种原始数据类型 symbol(已有的原始数据类型:String, Number, boolean, null, undefined, 对象)
特点
Symbol 属性对应的值是唯一的,解决命名冲突问题
Symbol 值不能与其他数据进行计算,包括同字符串拼串
for in, for of 遍历时不会遍历 symbol 属性。
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
1 2 3 4 5 6 7 let sy = Symbol("KK"); console.log(sy); // Symbol(KK) typeof(sy); // "symbol" // 相同参数 Symbol() 返回的值不相等 let sy1 = Symbol("kk"); sy === sy1; // false
注意点
Symbol 值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问。但是不会出现在 for…in 、 for…of 的循环中,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 返回。如果要读取到一个对象的 Symbol 属性,可以通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys() 取到。
1 2 3 4 5 6 7 8 9 10 11 let syObject = {}; syObject[sy] = "kk"; console.log(syObject); for (let i in syObject) { console.log(i); } // 无输出 Object.keys(syObject); // [] Object.getOwnPropertySymbols(syObject); // [Symbol(key1)] Reflect.ownKeys(syObject); // [Symbol(key1)]
使用
1、调用 Symbol 函数得到 symbol 值
1 2 3 let symbol = Symbol(); let obj = {}; obj[symbol] = 'hello';
2、传参标识
1 2 3 4 let symbol = Symbol('one'); let symbol2 = Symbol('two'); console.log(symbol);// Symbol('one') console.log(symbol2);// Symbol('two')
3、内置 Symbol 值
除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。
对象的 Symbol.iterator 属性,指向该对象的默认遍历器方法(后边讲)
3.8 async 函数
async 函数源自 ES2017
概念: 真正意义上去解决异步回调的问题,同步流程表达异步操作
本质: Generator 的语法糖
语法:
1 2 3 async function name([param[, param[, ... param]]]) { statements }
name: 函数名称。
param: 要传递给函数的参数的名称。
statements: 函数体语句。
即 async 的语法为
1 2 3 4 async function foo(){ await 异步操作; await 异步操作; }
返回值:
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。
1 2 3 4 5 6 7 8 9 async function helloAsync(){ return "helloAsync"; } console.log(helloAsync()) // Promise {<resolved>: "helloAsync"} helloAsync().then(v=>{ console.log(v); // helloAsync })
async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。
await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误
await 语法
1 2 3 //expression: 一个 Promise 对象或者任何要等待的值。 [return_value] = await expression;
特点:
不需要像 Generator 去调用 next 方法,遇到 await 等待,当前的异步操作完成就往下执行
返回的总是 Promise 对象,可以用 then 方法进行下一步操作
async 取代 Generator 函数的星号*,await 取代 Generator 的 yield
语意上更为明确,使用简单,经临床验证,暂时没有任何副作用
代码实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 async function timeout(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }) } async function asyncPrint(value, ms) { console.log('函数执行', new Date().toTimeString()); await timeout(ms); console.log('延时时间', new Date().toTimeString()); console.log(value); } console.log(asyncPrint('hello async', 2000));
上述代码的执行结果为
1 2 3 4 5 6 7 8 9 10 11 // await async function awaitTest() { let result = await Promise.resolve('执行成功'); console.log(result); let result2 = await Promise.reject('执行失败'); console.log(result2); let result3 = await Promise.resolve('还想执行一次');// 执行不了 console.log(result3); } awaitTest();
上述代码的执行结果为
3.9 class 简介 在 ES6 中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。
class 的本质是 function。
它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。
通过 class 定义类/实现类的继承
在类中通过 constructor 定义构造方法
通过 new 来创建类的实例
通过 extends 来实现类的继承
通过 super 调用父类的构造方法
重写从父类中继承的一般方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Person { //调用类的构造方法 constructor(name, age){ this.name = name; this.age = age; } //定义一般的方法 showName(){ console.log(this.name, this.age); } } let person = new Person('kobe', 39); console.log(person, person.showName()); //定义一个子类 class StrPerson extends Person{ constructor(name, age, salary){ super(name, age);//调用父类的构造方法 this.salary = salary; } showName(){//在子类自身定义方法 console.log(this.name, this.age, this.salary); } } let str = new StrPerson('weide', 38, 1000000000); console.log(str); str.showName();
上述代码的执行结果为
3 ES7 扩展
指数运算符(幂): **
Array.prototype.includes(value) : 判断数组中是否包含指定 value
1 2 3 4 console.log(3 ** 3);//27 let arr = [1,2,3,4, 'abc']; console.log(arr.includes(2));//true console.log(arr.includes(5));//false