说明
本笔记是本人
ES6-ES11系统学习笔记
,将ES系列全部梳理一遍,包括新特性等观阅或查阅的资料:[
尚硅谷Web前端ES6教程,涵盖ES6-ES11
]、阮一峰的ES6文档、[JowayYoung的1.5万字概括ES6全部特性(已更新ES2020)
]笔记中每部分都会首先给出[
概括总结
],总结概括此部分知识点,然后再于下方给出部分常用重点
知识点详解除此笔记外大家可以看我其他笔记 :全栈笔记、编程_前端开发学习笔记、Vue笔记整合 、React笔记、 ReactHooks笔记 、微信小程序学习笔记、Chrome开发使用及学习笔记 以及许多其他笔记就不一一例举了
一、ECMAScript引出
1、什么是 ECMA和ECMAScript
ECMA
(European Computer Manufacturers Association)中文名称为欧洲计算机制 造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该 组织改名为 Ecma 国际。
ECMAScript
是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。
2、ECMA-262
Ecma 国际制定了许多标准,而 ECMA-262 只是其中的一个,所有标准列表查看 —>点我传送
Ⅰ-ECMA-262 历史
ECMA-262(ECMAScript)历史版本查看网址: —>点我传送
版数 年份 内容 第 1 版 1997 年 制定了语言的基本语法 第 2 版 1998 年 较小改动 第 3 版 1999 年 引入正则、异常处理、格式化输出等。IE 开始支持 第 4 版 2007 年 过于激进,未发布 第 5 版 2009 年 引入严格模式、JSON,扩展对象、数组、原型、字符串、日期方法 第 6 版
2015 年
模块化、面向对象语法、 Promise、箭头函数、let、 const、数组解构赋值等等
因为发布内容很多,堪称里程碑,所以我们目前通常主要学这个第 7 版 2016 年 幂运算符、数组扩展、 Async/await 关键字 第 8 版 2017 年 Async/await、字符串扩展 第 9 版 2018 年 对象解构赋值、正则扩展 第 10 版 2019 年 扩展对象、数组方法 ES.next 动态指向下一个版本 后续学到我会进行补充
注:从 ES6 开始,每年发布一个版本,版本号比年份最后一位大 1
所以有些文章上提到的
ES7
(实质上是ES2016
)、ES8
(实质上是ES2017
)、ES9
(实质上是ES2018
)、ES10
(实质上是ES2019
)、ES11
(实质上是ES2020
),实质上都是一些不规范的概念。从ES1到ES6,每个标准都是花了好几年甚至十多年才制定下来,你一个ES6到ES7,ES7到ES8,才用了一年,按照这样的定义下去,那不是很快就ES20了。用正确的概念来说ES6目前涵盖了ES2015、ES2016、ES2017、ES2018、ES2019、ES2020。
Ⅱ-谁在维护 ECMA-262
TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。其会员都是公司(
其中主要是浏览器厂商
:有苹果、谷歌、微软、因特尔等)。TC39 定期 召开会议,会议由会员公司的代表与特邀专家出席
3、为什么要重点学习 ES6
- ES6 的版本变动内容最多,具有里程碑意义
- ES6 加入许多新的语法特性,编程实现更简单、高效
- ES6 是前端发展趋势,就业必备技能
4、ES6 兼容性
可以查看gitHub上的这个图—>点我传送
二、ECMASript 6 新特性
想要查看更详细的ES6,可以看阮一峰的ES6文档,本人当初对其进行了摘录放至此处方便查阅—>ES6资料文档摘录 ,当然此笔记中也会对其内容有所摘录梳理,毕竟此文档已经写的非常详细了
此处ES6部分笔记主要为:
查阅的资料博客整合摘录
,加上学习ES6时笔记、个人心得体会以及在相当一段工作时间中觉得常用或者是需要重点学习的理解整合
1、ES6更新的内容概括
表达式:声明、解构赋值
内置对象:字符串扩展、数值扩展、对象扩展、数组扩展、函数扩展、正则扩展、Symbol、Set、Map、Proxy、Reflect
语句与运算:Class、Module、Iterator
异步编程:Promise、Generator、Async
2、let和const命令、作用域
注意:
不存在变量提升
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。为了纠正这种现象,
let
、const
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错
Ⅰ-概括与总结
声明
- const命令:声明常量
- let命令:声明变量
作用
- 作用域
- 全局作用域
- 函数作用域:
function() {}
- 块级作用域:
{}
- 作用范围
var命令
在全局代码中执行const命令
和let命令
只能在代码块中执行
- 赋值使用
const命令
声明常量后必须立马赋值let命令
声明变量后可立马赋值或使用时赋值
- 声明方法:
var
、const
、let
、function
、class
、import
重点难点
- 不允许重复声明
- 未定义就使用会报错:
const命令
和let命令
不存在变量提升- 暂时性死区:在代码块内使用
const命令
和let命令
声明变量之前,该变量都不可用下一节为赋值解构的概括总结 —>点我传送
Ⅱ-let关键字命令
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
应用场景:声明重复赋值的变量时可以用这个,如果你不是要求很高的话,基本上都能用let进行声明(var声明的可以都用这个替代了)
let创建变量代码示例
// let关键字使用示例: let a; // 单个声明 let b,c,d; // 批量声明 let e = 100; // 单个声明并赋值 let f = 521, g = 'iloveyou', h = []; // 批量声明并赋值
不允许重复声明:
代码实现:
// 1. 不允许重复声明; let dog = "狗"; let dog = "狗"; // 报错:Uncaught SyntaxError: Identifier 'dog' has already been declared
运行结果:
块儿级作用域(局部变量): {} if else while for
代码实现:
// 2. 块儿级作用域(局部变量); { let cat = "猫"; console.log(cat); } console.log(cat);// 报错:Uncaught ReferenceError: cat is not defined
运行结果:
不存在变量提升:
什么是变量提升:
就是在变量创建之前使用(比如输出:输出的是默认值),let不存在,var存在;
代码实现:
// 3. 不存在变量提升; // 什么是变量提升:就是在变量创建之前使用(比如输出:输出的是默认值),let不存在,var存在; console.log(people1); // 可输出默认值 console.log(people2); // 报错:Uncaught ReferenceError: people2 is not defined var people1 = "大哥"; // 存在变量提升 let people2 = "二哥"; // 不存在变量提升
运行结果:
不影响作用域链:
代码实现:
// 4. 不影响作用域链; // 什么是作用域链:很简单,就是代码块内有代码块,跟常规编程语言一样,上级代码块中 的局部变量下级可用 { let p = "大哥"; function fn(){ console.log(p); // 这里是可以使用的 } fn(); }
运行结果:
全部演示代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>let</title> </head> <body> let <script> // let 关键字使用示例 let a; // 单个声明 let b,c,d; // 批量声明应用场景: 以后声明变量使用 let 就对了; let案例:点击div更改颜色 代码实现: let e = 100; // 单个声明并赋值 let f = 521, g = 'iloveyou', h = []; // 批量声明并赋值 // let 关键字特性 // 1. 不允许重复声明; // let dog = "狗"; // let dog = "狗"; // 报错:Uncaught SyntaxError: Identifier 'dog' has already been declared // 2. 块儿级作用域(局部变量); // { // let cat = "猫"; // console.log(cat); // } // console.log(cat); // 报错:Uncaught ReferenceError: cat is not defined // 3. 不存在变量提升; // 什么是变量提升:就是在变量创建之前使用(比如输出:输出的是默认值),let不存在,var存在; // console.log(people1); // 可输出默认值 // console.log(people2); // 报错:Uncaught ReferenceError: people2 is not defined // var people1 = "大哥"; // 存在变量提升 // let people2 = "二哥"; // 不存在变量提升 // 4. 不影响作用域链; // 什么是作用域链:很简单,就是代码块内有代码块,跟常规编程语言一样,上级代码块中的局部变量下级可用 // { // let p = "大哥"; // function fn(){ // console.log(p); // 这里是可以使用的 // } // fn(); // } </script> </body> </html>
应用场景:
以后声明变量使用 let 就对了;
let案例:点击div更改颜色
代码实现:
```javascript <!DOCTYPE html>
let案例:点击div更改颜色
> **运行结果:**
>
<a name="565c2888"></a>
### Ⅲ-const关键字命令
> const 关键字用来声明常量,const 声明有以下**特点**:
> 1. 不允许重复声明
> 2. 值不允许修改
> 3. 不存在变量提升
> 4. 块级作用域
> 5. 声明必须赋初始值
> 6. 标识符一般为大写
> 注意: `对象属性修改和数组元素变化不会触发 const 错误`
> > `const`实际上保证的,`并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动`。
> > 对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,`const`只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
> 应用场景:声明对象类型、确定不会再次赋值的变量使用 const,其他的可以用let
> > <a name="f1d465e7"></a>
### const创建变量代码示例:
> ```javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>const</title>
</head>
<body>
<script>
// const声明常量
const DOG = "旺财";
console.log(DOG);
</script>
</body>
</html>
声明必须赋初始值:
代码实现:
// 1. 声明必须赋初始值; const CAT;
运行结果:
不允许重复声明:
代码实现:
// 3. 不允许重复声明; const CAT = "喵喵"; const CAT = "喵喵";
运行结果:
值不允许修改:
注意:
对数组元素的修改和对对象内部的修改是可以的(数组和对象存的是引用地址);
// 4. 值不允许修改; const CAT = "喵喵"; CAT = "咪咪";
运行结果:
对于数组和对象的元素修改,不算做对常量的修改,不会报错
const team = ['123','12','1234'] team.push('meiko')
运行结果
块儿级作用域(局部变量)
代码实现:
// 5. 块儿级作用域(局部变量); { const CAT = "喵喵"; console.log(CAT); } console.log(CAT);
运行结果:
全部演示代码:
```javascript <!DOCTYPE html>
<a name="9984bbb8"></a>
### Ⅳ-ES6 声明变量的六种方法
> ES5 只有两种声明变量的方法:`var`命令和`function`命令。ES6 除了添加`let`和`const`命令,后面还会提到,另外两种声明变量的方法:`import`命令和`class`命令。所以,ES6 一共有 6 种声明变量的方法。
<a name="9f7e8cb9"></a>
### Ⅴ-块级作用域
<a name="e2dfd89c"></a>
#### ① 为什么需要块级作用域?
> ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
> 第一种场景,内层变量可能会覆盖外层变量。
> ```javascript
var tmp = new Date();
function f() {
console.log(tmp);
if (false) { var tmp = 'hello world'; }
}
f(); // undefined
上面代码的原意是,
if
代码块的外部使用外层的tmp
变量,内部使用内层的tmp
变量。但是,函数f
执行后,输出结果为undefined
,原因在于变量提升,导致内层的tmp
变量覆盖了外层的tmp
变量。第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello'; for (var i = 0; i < s.length; i++) { console.log(s[i]);} console.log(i); // 5
上面代码中,变量
i
只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。
② ES6 的块级作用域
let
实际上为 JavaScript 新增了块级作用域。function f1() { let n = 5; if (true) { let n = 10; } console.log(n); // 5 }
上面的函数有两个代码块,都声明了变量
n
,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var
定义变量n
,最后输出的值才是 10。ES6 允许块级作用域的任意嵌套。
{{{{ {let insane = 'Hello World'} console.log(insane); // 报错 因为外层不能取到内层数据 }}}};
上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。
第四层作用域无法读取第五层作用域的内部变量
。内层作用域可以定义外层作用域的同名变量。
{{{{ let insane = 'Hello World'; {let insane = 'Hello World'} }}}};
块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。—>对于IIFE不懂的可以看本人JS进阶笔记,点我跳转
```javascript // IIFE 写法 (function () { var tmp = …; … }());
// 块级作用域写法 { let tmp = …; … }
<a name="b04c558e"></a>
#### ③ 块级作用域与函数声明
> 函数能不能在块级作用域之中声明?这是一个相当令人混淆的问题。
> ES5 规定,函数只能在顶层作用域和函数作用域之中声明,不能在块级作用域声明。
> ```javascript
// 情况一
if (true) {
function f() {}
}
// 情况二
try {
function f() {}
} catch(e) {
// ...
}
上面两种函数声明,根据 ES5 的规定都是非法的。
但是,浏览器没有遵守这个规定,为了兼容以前的旧代码,还是支持在块级作用域之中声明函数,因此上面两种情况实际都能运行,不会报错。
ES6 引入了块级作用域,明确允许在块级作用域之中声明函数。ES6 规定,块级作用域之中,函数声明语句的行为类似于
let
,在块级作用域之外不可引用。function f() { console.log('I am outside!'); } (function () { // 重复声明一次函数f if (false) { function f() { console.log('I am inside!'); }} f(); }());
上面代码在 ES5 中运行,会得到“I am inside!”,因为在
if
内声明的函数f
会被提升到函数头部,实际运行的代码如下。// ES5 环境 function f() { console.log('I am outside!'); } (function () { function f() { console.log('I am inside!'); } if (false) {} f(); }());
ES6 就完全不一样了,理论上会得到“I am outside!”。因为块级作用域内声明的函数类似于
let
,对作用域之外没有影响。但是,如果你真的在 ES6 浏览器中运行一下上面的代码,是会报错的,这是为什么呢?// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { // 重复声明一次函数f if (false) { function f() { console.log('I am inside!'); } } f(); }()); // Uncaught TypeError: f is not a function
上面的代码在 ES6 浏览器中,都会报错。
原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。- 同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作
let
处理。根据这三条规则,浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于
var
声明的变量。上面的例子实际运行的代码如下。// 浏览器的 ES6 环境 function f() { console.log('I am outside!'); } (function () { var f = undefined; if (false) { function f() { console.log('I am inside!'); }} f(); }()); // Uncaught TypeError: f is not a function
考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
```javascript // 块级作用域内部的函数声明语句,建议不要使用 { let a = ‘secret’; function f() { return a; } }
// 块级作用域内部,优先使用函数表达式 { let a = ‘secret’; let f = function () { return a; }; }
> 另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
> ```javascript
// 第一种写法,报错
if (true) let x = 1;
// 第二种写法,不报错
if (true) {
let x = 1;
}
上面代码中,第一种写法没有大括号,所以不存在块级作用域,而
let
只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。
```javascript // 不报错 ‘use strict’; if (true) { function f() {} }
// 报错 ‘use strict’; if (true) function f() {}
<a name="5ed86d65"></a>
## 3、赋值解构
> ES6 允许按照一定模式,`从数组和对象中提取值,对变量进行赋值`,这被称为解构(Destructuring)。
> 本质上,这种写法属于“`模式匹配`”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
> > <a name="be9dcc7f"></a>
### 代码演示及相关说明:
> ```javascript
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>解构赋值</title>
</head>
<body>
<script>
// ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值;
// 1、数组的解构赋值
const F4 = ["大哥", "二哥", "三哥", "四哥"];
let [a, b, c, d] = F4;
// 这就相当于我们声明4个变量a,b,c,d,其值分别对应"大哥","二哥","三哥","四哥"
console.log(a + b + c + d); // 大哥二哥三哥四哥
// 2、对象的解构赋值 更常用
const F3 = {
name: "大哥",
age: 22,
sex: "男",
xiaopin: function () { // 常用
console.log("我会演小品!");
}
}
let { name, age, sex, xiaopin } = F3; // 注意解构对象这里用的是{}
console.log(name + age + sex + xiaopin); // 大哥22男
xiaopin(); // 此方法可以正常调用
//最常用 这里是寻找同名属性进行赋值
let {xiaopin} = F3;
xiaopin();
</script>
</body>
</html>
Ⅰ-概括总结
- 字符串解构:
const [a, b, c, d, e] = "hello"
- 数值解构:
const { toString: s } = 123
- 布尔解构:
const { toString: b } = true
- 对象解构
- 形式:
const { x, y } = { x: 1, y: 2 }
- 默认:
const { x, y = 2 } = { x: 1 }
- 改名:
const { x, y: z } = { x: 1, y: 2 }
- 数组解构
- 规则:数据结构具有
Iterator接口
可采用数组形式的解构赋值- 形式:
const [x, y] = [1, 2]
- 默认:
const [x, y = 2] = [1]
- 函数参数解构
- 数组解构:
function Func([x = 0, y = 1]) {}
- 对象解构:
function Func({ x = 0, y = 1 } = {}) {}
应用场景
- 交换变量值:
[x, y] = [y, x]
- 返回函数多个值:
const [x, y, z] = Func()
- 定义函数参数:
Func([1, 2])
- 提取JSON数据:
const { name, version } = packageJson
- 定义函数参数默认值:
function Func({ x = 1, y = 2 } = {}) {}
- 遍历Map结构:
for (let [k, v] of Map) {}
- 输入模块指定属性和方法:
const { readFile, writeFile } = require("fs")
重点难点
- 匹配模式:只要等号两边的模式相同,左边的变量就会被赋予对应的值
- 解构赋值规则:只要等号右边的值不是对象或数组,就先将其转为对象
- 解构默认值生效条件:属性值严格等于
undefined
- 解构遵循匹配模式
- 解构不成功时变量的值等于
undefined
undefined
和null
无法转为对象,因此无法进行解构下一节为字符串的拓展概括 —>点我传送
Ⅱ-基本用法
① 基本用法举例
以前,为变量赋值,只能直接指定值。
let a = 1; let b = 2; let c = 3;
ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。
```javascript let [foo, [[bar], baz]] = [1, [[2], 3]];//foo : 1 bar : 2 baz : 3
let [ , , third] = [“foo”, “bar”, “baz”];//third : “baz”
let [x, , y] = [1, 2, 3];//x : 1 y : 3
let [head, …tail] = [1, 2, 3, 4];//head : 1 tail : [2, 3, 4]
let [x, y, …z] = [‘a’];//x : “a” y : undefined z : []
> 如果解构不成功,变量的值就等于`undefined`。
> ```javascript
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
上面两个例子,都属于不完全解构,但是可以成功。
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
// 报错 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
上面的语句都会报错,因为等号右边的值,要么转为对象以后不具备 Iterator 接口(前五个表达式),要么本身就不具备 Iterator 接口(最后一个表达式)。
对于 Set 结构,也可以使用数组的解构赋值
。let [x, y, z] = new Set(['a', 'b', 'c']); x // "a"
事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
```javascript function* fibs() { let a = 0; let b = 1; while (true) { yield a; [a, b] = [b, a + b]; } }
let [first, second, third, fourth, fifth, sixth] = fibs(); sixth // 5
> 上面代码中,`fibs`是一个 Generator 函数(详见《Generator 函数》),原生具有 Iterator 接口。解构赋值会依次从这个接口获取值。
<a name="dbe7c129"></a>
#### ② 默认值
> 解构赋值允许指定默认值。
> ```javascript
let [foo = true] = [];//foo = true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
注意,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] === undefined) { x = f()} else { x = [1]; }
默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [x = 1, y = x] = []; // x=1; y=1 let [x = 1, y = x] = [2]; // x=2; y=2 let [x = 1, y = x] = [1, 2]; // x=1; y=2 let [x = y, y = 1] = []; // ReferenceError: y is not defined
上面最后一个表达式之所以会报错,是因为
x
用y
做默认值时,y
还没有声明。
Ⅲ-对象的赋值解构
此处应用的非常多
,需要多查阅
① 基本用法
解构不仅可以用于数组,还可以用于对象。
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
变量。如果变量名与属性名不一致,必须写成下面这样—>
取别名
```javascript 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’
> 这实际上说明,对象的解构赋值是下面形式的简写(详见《对象的扩展》)。
> ```javascript
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
。与数组一样,解构也可以用于嵌套结构的对象。
```javascript let obj = { p: [‘Hello’, { y: ‘World’ }] };
let { p: [x, { y }] } = obj; //x == “Hello” //y == “World”
> 注意,这时`p`是模式,不是变量,因此不会被赋值。如果`p`也要作为变量赋值,可以写成下面这样。
> ```javascript
let obj = {
p: [ 'Hello', { y: 'World' }]
};
let { p, p: [x, { y }] } = obj;
//x == "Hello"
//y == "World"
//p == ["Hello", {y: "World"}]
下面是另一个例子。
```javascript const node = { loc: { start: { line: 1, column: 5 } } };
let { loc, loc: { start }, loc: { start: { line }} } = node; //line == 1 //loc == Object {start: Object} //start == Object {line: 1, column: 5}
> 上面代码有三次解构赋值,分别是对`loc`、`start`、`line`三个属性的解构赋值。注意,最后一次对`line`属性的解构赋值之中,只有`line`是变量,`loc`和`start`都是模式,不是变量。
> 下面是嵌套赋值的例子。-->`注意:外部包着一层()`:
> ```javascript
let obj = {};
let arr = [];
({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });
//因为 JavaScript 引擎会将`{x}`理解成一个代码块,从而发生语法错误。`只有不将大括号写在行首`,避免 JavaScript 将其解释为代码块,才能解决这个问题。
//obj == {prop:123}
//arr == [true]
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
// 报错 let {foo: {bar}} = {baz: 'baz'};
上面代码中,等号左边对象的
foo
属性,对应一个子对象。该子对象的bar
属性,解构时会报错。原因很简单,因为foo
这时等于undefined
,再取子属性就会报错。注意,对象的解构赋值可以取到继承的属性。
const obj1 = {}; const obj2 = { foo: 'bar' }; Object.setPrototypeOf(obj1, obj2);//Object.setPrototypeOf() 方法设置一个指定的对象的原型 ( 即, 内部[[Prototype]]属性)到另一个对象或 null const { foo } = obj1; foo // "bar"
上面代码中,对象
obj1
的原型对象是obj2
。foo
属性不是obj1
自身的属性,而是继承自obj2
的属性,解构赋值可以取到这个属性。注:
Object.setPrototypeOf()
详解,不知道此方法的同学们看这里 —>点我传送
② 默认值
对象的解构也可以指定默认值。
var {x = 3} = {};//x == 3 var {x, y = 5} = {x: 1}; //x == 1 //y == 5 var {x: y = 3} = {};//y == 3 var {x: y = 3} = {x: 5};//y == 5 var { message: msg = 'Something went wrong' } = {};//msg == "Something went wrong"
默认值生效的条件是,对象的属性值严格等于
undefined
。var {x = 3} = {x: undefined};//x == 3 var {x = 3} = {x: null};//x == null
上面代码中,属性
x
等于null
,因为null
与undefined
不严格相等,所以是个有效的赋值,导致默认值3
不会生效。—>原因上面讲过
③ 注意点
(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
。方括号这种写法,属于“属性名表达式”(详见《对象的扩展》)。
Ⅳ-字符串的赋值结构
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello'; //a == "h" ;b == "e" ; c == "l" ; d == "l" ;e == "o"
类似数组的对象都有一个
length
属性,因此还可以对这个属性解构赋值。let {length : len} = 'hello';//len == 5
Ⅴ-数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
```javascript let {toString: s} = 123; s === Number.prototype.toString // true
let {toString: s} = true; s === Boolean.prototype.toString // true
> 上面代码中,数值和布尔值的包装对象都有`toString`属性,因此变量`s`都能取到值。
> 解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于`undefined`和`null`无法转为对象,所以对它们进行解构赋值,都会报错。
> ```javascript
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
Ⅵ-函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){ return x + y; } add([1, 2]); // 3
上面代码中,函数
add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。下面是另一个例子。
[[1, 2], [3, 4]].map(([a, b]) => a + b); // [ 3, 7 ]
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) { return [x, y];} move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, 0] move({}); // [0, 0] move(); // [0, 0]
上面代码中,函数
move
的参数是一个对象,通过对这个对象进行解构,得到变量x
和y
的值。如果解构失败,x
和y
等于默认值。注意,下面的写法会得到不一样的结果。
```javascript function move({x, y} = { x: 0, y: 0 }) { return [x, y]; }
move({x: 3, y: 8}); // [3, 8] move({x: 3}); // [3, undefined] move({}); // [undefined, undefined] move(); // [0, 0]
> 上面代码是为函数`move`的参数指定默认值,而不是为变量`x`和`y`指定默认值,所以会得到与前一种写法不同的结果。
> `undefined`就会触发函数参数的默认值。
> ```javascript
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
Ⅶ-圆括号问题
解构赋值虽然很方便,但是解析起来并不容易。对于编译器来说,
一个式子到底是模式,还是表达式
,没有办法从一开始就知道,必须解析到(或解析不到)等号才能知道。由此带来的问题是,如果模式中出现圆括号怎么处理。ES6 的规则是,只要有可能导致解构的歧义,就不得使用圆括号。
但是,这条规则实际上不那么容易辨别,处理起来相当麻烦。因此,建议只要有可能,就不要在模式中放置圆括号。
① 不能使用圆括号的情况
以下三种解构赋值不得使用圆括号。
(1)
变量声明
语句// 全部报错 let [(a)] = [1]; let {x: (c)} = {}; let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };
上面 6 个语句都会报错,
因为它们都是变量声明语句
,模式不能使用圆括号。(2)函数参数
函数参数也属于变量声明,因此不能带有圆括号。
// 报错 function f([(z)]) { return z; } // 报错 function f([z,(x)]) { return x; }
(3)赋值语句的模式
// 全部报错 ({ p: a }) = { p: 42 }; ([a]) = [5];
上面代码将整个模式放在圆括号之中,导致报错。
// 报错 [({ p: a }), { x: c }] = [{}, {}];
上面代码将一部分模式放在圆括号之中,导致报错。
② 可以使用圆括号的情况
可以使用圆括号的情况只有一种:赋值语句的非模式部分,可以使用圆括号。
[(b)] = [3]; // 正确 ({ p: (d) } = {}); // 正确 [(parseInt.prop)] = [3]; // 正确
上面三行语句都可以正确执行,因为
首先它们都是赋值语句,而不是声明语句
;其次它们的圆括号都不属于模式的一部分。第一行语句中,模式是取数组的第一个成员,跟圆括号无关;第二行语句中,模式是p
,而不是d
;第三行语句与第一行语句的性质一致。
Ⅷ-具体应用场景举例
变量的解构赋值用途很多
① 交换变量的值
let x = 1; let y = 2; [x, y] = [y, x];
上面代码交换变量
x
和y
的值,这样的写法不仅简洁,而且易读,语义非常清晰。
② 从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
```javascript // 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example();
// 返回一个对象 function example() { return { foo: 1,bar: 2}; } let { foo, bar } = example();
<a name="903d2e30"></a>
#### ③ 函数参数的定义
> 解构赋值可以方便地将一组参数与变量名对应起来。
> ```javascript
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
④ 提取 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 数据的值。
⑤ 函数参数的默认值
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';
这样的语句。
⑥ 遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用
for...of
循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。```javascript 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
> 如果只想获取键名,或者只想获取键值,可以写成下面这样。
> ```javascript
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
⑦ 输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
4、字符串的拓展
Ⅰ-概括总结
Unicode表示法:
大括号包含
表示Unicode字符(\u{0xXX}
或\u{0XXX}
)字符串遍历:可通过
for-of
遍历字符串字符串模板:可单行可多行可插入变量的增强版字符串
标签模板:函数参数的特殊调用
String.raw():返回把字符串所有变量替换且对斜杠进行转义的结果
String.fromCodePoint():返回码点对应字符
codePointAt():返回字符对应码点(
String.fromCodePoint()
的逆操作)normalize():把字符的不同表示方法统一为同样形式,返回
新字符串
(Unicode正规化)repeat():把字符串重复n次,返回
新字符串
matchAll():返回正则表达式在字符串的所有匹配
includes():是否存在指定字符串
startsWith():是否存在字符串头部指定字符串
endsWith():是否存在字符串尾部指定字符串
以上扩展方法均可作用于由
4个字节储存
的Unicode字符
上
Ⅱ-模板字符串
模板字符串(template string)是增强版的字符串,用反引号[ ` ]标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
嵌入变量使用[
${变量名}
]:如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString
方法。如果大括号内部是一个字符串,将会原样输出。代码演示及相关说明:
```javascript <!DOCTYPE html>
> > <a name="97c957fc-3"></a> ### 运行结果 > ![](image-20210810165939775.png#alt=image-20210810165939775) > ![](image-20210810165903875.png#alt=image-20210810165903875) <a name="4ecfbd07"></a> ### Promise封装读取文件: > > <a name="cc6946cf"></a> ### 一般写法 + 封装写法: > ```javascript //1. 引入 fs 模块 const fs = require('fs'); //2. 调用方法读取文件 // fs.readFile('./resources/为学.md', (err, data)=>{ // //如果失败, 则抛出错误 // if(err) throw err; // //如果没有出错, 则输出内容 // console.log(data.toString()); // }); //3. 使用 Promise 封装 const p = new Promise(function(resolve, reject){ fs.readFile("./resources/为学.mda", (err, data)=>{ //判断如果失败 if(err) reject(err); //如果成功 resolve(data); }); }); p.then(function(value){ console.log(value.toString()); }, function(reason){ console.log("读取失败!!"); });
运行结果
Promise封装Ajax请求:
原生请求:
```html <!DOCTYPE html>
> > <a name="d3ca7e34-5"></a> ### 运行结果: > ![](image-20210810171537453.png#alt=image-20210810171537453) > > <a name="6050eef1-1"></a> ### Promise封装Ajax请求: > ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Promise封装Ajax请求</title> </head> <body> <script> // 请求地址:https://api.apiopen.top/getJoke const p = new Promise(function (resolve, reason) { // 原生请求 // 1、创建对象 const xhr = new XMLHttpRequest(); // 2、初始化 xhr.open("GET", "https://api.apiopen.top/getJoke"); // 3、发送 xhr.send(); // 4、绑定事件,处理响应结果 xhr.onreadystatechange = function () { // 判断状态 if (xhr.readyState == 4) { // 判断响应状态码 200-299 if (xhr.status >= 200 && xhr.status <= 299) { // 成功 resolve(xhr.response); } else { // 失败 reason(xhr.status); } } } }); p.then(function (value) { console.log(value.toString()); }, function (reason) { console.log(reason); // 读取失败 }) </script> </body> </html>
运行结果:
Promise.prototype.then:
代码实现及相关说明:
```html <!DOCTYPE html>
> > <a name="d3ca7e34-7"></a> ### 运行结果: > ![](image-20210810193532625.png#alt=image-20210810193532625) <a name="44bbe036"></a> ### Promise实践练习: > > <a name="4ff7afa7"></a> ### “回调地狱”方式写法: > ```javascript // 1、引入 fs 模块 const fs = require("fs"); // 2、调用方法,读取文件 fs.readFile("resources/text.txt", (err, data1) => { fs.readFile("resources/test1.txt", (err, data2) => { fs.readFile("resources/test2.txt", (err, data3) => { let result = data1 + data2 + data3; console.log(result); }); }); });
运行结果:
Promise实现:
// 1、引入 fs 模块 const fs = require("fs"); // 2、使用Promise实现 const p = new Promise((resolve, reject) => { fs.readFile("resources/text.txt", (err, data) => { resolve(data); }); }); p.then(value => { return new Promise((resolve, reject) => { fs.readFile("resources/test1.txt", (err, data) => { resolve([value, data]); }); }) }).then(value => { return new Promise((resolve, reject) => { fs.readFile("resources/test2.txt", (err, data) => { // 存入数组 value.push(data); resolve(value); }); }) }).then(value => { console.log(value.join("\r\n")); })
运行结果:
Promise对象catch方法:
代码示例及相关说明:
```html <!DOCTYPE html>
> > <a name="d3ca7e34-29"></a> ### 运行结果: > ![](image-20210813103356276.png#alt=image-20210813103356276) <a name="222242cd"></a> ## 3、正则扩展:反向断言 <a name="2485c08b-18"></a> ### 概述: > **ES9 支持反向断言,通过对匹配结果前面的内容进行判断,对匹配进行筛选;** <a name="10c8962a-18"></a> ### 代码实现: > ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>正则扩展-反向断言</title> </head> <body> <script> //声明字符串 提取数字 let str = 'JS5211314你知道么555啦啦啦'; //正向断言 const reg = /\d+(?=啦)/;// 前面是数字后面是啦 const result = reg.exec(str); //反向断言 const reg = /(?<=么)\d; // 后面是数字前面是么 const result = reg.exec(str); console.log(result); </script> </body> </html>
运行结果:
4、正则扩展:dotAll 模式
概述:
正则表达式中点.匹配除回车外的任何单字符,标记『s』改变这种行为,允许行终止符出现;
代码实现:
```html <!DOCTYPE html>
> > <a name="d3ca7e34-31"></a> ### 运行结果: > ![](image-20210813105321704.png#alt=image-20210813105321704) <a name="f45e9cdb"></a> # 六、ES10 新特性 <a name="1cdea417-3"></a> ## 0、功能概述 **1、Object.fromEntries** - **将二维数组或者map转换成对象;** **2、trimStart 和 trimEnd ** - **去除字符串前后的空白字符;** **3、Array.prototype.flat 与 flatMap** - **将多维数组降维; ** **4、Symbol.prototype.description** - **获取Symbol的字符串描述; ** <a name="59a6b6e4"></a> ## 1、Object.fromEntries <a name="2485c08b-20"></a> ### 概述: > **将二维数组或者map转换成对象;**<br /> **之前学的Object.entries是将对象转换成二维数组;** <a name="10c8962a-20"></a> ### 代码实现: > ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Object.fromEntries</title> </head> <body> <script> // Object.fromEntries:将二维数组或者map转换成对象 // 之前学的Object.entries是将对象转换成二维数组 // 此方法接收的是一个二维数组,或者是一个map集合 将其转换为对象 // 二维数组 const result = Object.fromEntries([ ["name","訾博"], ["age",24], ]); console.log(result); const m = new Map(); m.set("name","訾博"); m.set("age",24); const result1 = Object.fromEntries(m); console.log(result1); //Object.entries ES8 const arr = Object.entries({ name: "尚硅谷" }) console.log(arr); </script> </body> </html>
运行结果:
2、trimStart 和 trimEnd
概述:
去掉字符串前后的空白字符;
代码实现
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>trimStart 与 trimEnd</title> </head> <body> <script> // trimStart 和 trimEnd let str = " zibo "; console.log(str.trimLeft()); console.log(str.trimRight()); console.log(str.trimStart()); console.log(str.trimEnd()); </script> </body> </html>
运行结果:
3、Array.prototype.flat 与 flatMap
概述:
将多维数组转换成低维数组;
代码实现
```html <!DOCTYPE html>
> > <a name="d3ca7e34-36"></a> ### 运行结果: > ![](image-20210813145816027.png#alt=image-20210813145816027) <a name="bcef2fbd"></a> ## 2、类的私有属性 <a name="2485c08b-25"></a> ### 概述: > **私有属性外部不可直接访问; ** <a name="10c8962a-23"></a> ### 代码实现: > ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>私有属性</title> </head> <body> <script> class Person{ //公有属性 name; //私有属性 #age; #weight; //构造方法 constructor(name, age, weight){ this.name = name; this.#age = age; this.#weight = weight; } intro(){ console.log(this.name); console.log(this.#age); console.log(this.#weight); } } //实例化 const girl = new Person('晓红', 18, '45kg'); console.log(girl.name); console.log(girl.#age); console.log(girl.#weight); girl.intro(); </script> </body> </html>
运行结果:
3、Promise.allSettled
概述:
- 获取多个promise执行的结果集;
代码实现:
```html <!DOCTYPE html>