ECMAScript基础入门教程

ECMAScript基础入门教程

Posted by yishuifengxiao on 2019-05-30

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 的批准。

  1. stage-0 - Strawman: just an idea, possible Babel plugin.

任何讨论、想法、改变或者还没加到提案的特性都在这个阶段。只有 TC39 成员可以提交。

  1. stage-1 - Proposal: this is worth working on.

什么是 Proposal?一份新特性的正式建议文档。提案必须指明此建议的潜在问题,例如与其他特性之间的关联,实现难点等。

  1. stage-2 - Draft: initial spec.

什么是 Draft?草案是规范的第一个版本。其与最终标准中包含的特性不会有太大差别。

草案之后,原则上只接受增量修改。这个阶段开始实验如何实现,实现形式包括 polyfill, 实现引擎(提供草案执行本地支持),或者编译转换(例如 babel)

  1. stage-3 - Candidate: complete spec and initial browser implementations.

候选阶段,获得具体实现和用户的反馈。此后,只有在实现和使用过程中出现了重大问题才会修改。至少要在一个浏览器中实现,提供 polyfill 或者 babel 插件。

  1. stage-4 - Finished: will be added to the next yearly release.

已经准备就绪,该特性会出现在下个版本的 ECMAScript 规范之中。

1.4 几个重要版本

  1. es5: 09 年发布
  2. es6(ES2015):    2015 年发布,也称之为 ECMA2015
  3. es7(ES2016):    2016 年发布,变化不大
  4. 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. 不允许使用未声明的变量:
1
2
"use strict";
x = 3.14; // 报错 (x 未定义)
  1. 对象也是一个变量
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>

一些常用的方法

  1. Object.assign()

    通过复制一个或多个对象来创建一个新的对象。

  2. Object.getOwnPropertyNames()

返回一个数组,它包含了指定对象所有的可枚举或不可枚举的属性名。

  1. Object.is()

比较两个值是否相同。所有 NaN 值都相等(这与==和===不同)。

  1. Object.keys()

    返回一个包含所有给定对象自身可枚举属性名称的数组。

  2. 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 扩展

  • Date.now() : 得到当前时间值

3 ES6 基础语法

3.1 let 与 const

ES2015(ES6) 新增加了两个重要的 JavaScript 关键字: let 和 const。

let 声明的变量只在 let 命令所在的代码块内有效。

const 声明一个只读的常量,一旦声明,常量的值就不能改变。

let 命令

  1. 基本用法:
1
2
3
4
5
{
let a = 0;
a // 0
}
a // 报错 ReferenceError: a is not defined
  1. 代码块内有效

let 是在代码块内有效,var 是在全局范围内有效

1
2
3
4
5
6
{
let a = 0;
var b = 1;
}
a // ReferenceError: a is not defined
b // 1
  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
  1. 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

  1. 不存在变量提升
    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. 解构赋值
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
});

上述代码的执行结果为:
image

状态的缺点

  • 无法取消 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
.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);
});

上述代码的执行结果为:

image
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 应用:

  • 使用 promise 实现超时处理
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
  • 使用 promise 封装处理 ajax 请求
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, 对象)

特点

  1. Symbol 属性对应的值是唯一的,解决命名冲突问题
  2. Symbol 值不能与其他数据进行计算,包括同字符串拼串
  3. 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

对象的 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));

上述代码的执行结果为

image

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();

上述代码的执行结果为

image

3.9 class 简介

在 ES6 中,class (类)作为对象的模板被引入,可以通过 class 关键字定义类。

class 的本质是 function。

它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。

    1. 通过 class 定义类/实现类的继承
    1. 在类中通过 constructor 定义构造方法
    1. 通过 new 来创建类的实例
    1. 通过 extends 来实现类的继承
    1. 通过 super 调用父类的构造方法
    1. 重写从父类中继承的一般方法
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();

上述代码的执行结果为

image

3 ES7 扩展

  1. 指数运算符(幂): **
  2. 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