ECMAScript学习笔记
ECMAScript学习笔记
学习来源:尚硅谷
笔记参考:爱学习的汪
学习时间:2022年2月16日
1 概述
1.1 ECMA简介
ECMA(European Computer Manufacturers Association)中文名称为欧洲计算机制造商协会,这个组织的目标是评估、开发和认可电信和计算机标准。1994 年后该组织改名为 Ecma 国际。
ECMAScript 是由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言。
1.2 ECMA-262
Ecma 国际制定了许多标准,而 ECMA-262 只是其中的一个。
版数 | 年份 | 内容 |
---|---|---|
第 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。
TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。其会员都是公司(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39 定期召开会议,会议由会员公司的代表与特邀专家出席。
1.3 ES6更新内容概括
表达式: 声明、解构赋值
内置对象: 字符串扩展、数值扩展、对象扩展、数组扩展、函数扩展、正则扩展、Symbol、Set、Map、Proxy、Reflect
语句与运算: Class、Module、Iterator
异步编程: Promise、Generator、Async
2 ES6新特性
2.1 let、const关键字和作用域
2.1.1 let关键字
1) 基本使用
let 关键字用来声明变量。
1 | // 声明变量和赋值 |
使用 let 声明的变量有如下特点:
不允许重复声明
块级作用域
- js中存在三种作用域:全局(作为window的属性)、函数(
function() {}
)和块级({}
,例如for、while、if、else等) var
关键字在全局代码中运行let命令
只能在代码块中执行
1
2
3
4{
var a = 123;
}
console.log(a); // 1231
2
3
4{
let a = 123;
}
console.log(a); // undefined- js中存在三种作用域:全局(作为window的属性)、函数(
不存在变量提升
1 | console.log(a); // undefined |
1 | console.log(a); |
- 不影响作用域链
1 | { |
如上,虽然在函数级作用域中没有name变量,但函数func会沿着作用域链向上寻找至块级作用域中的name变量。
应用场景:以后声明变量使用 let 就对了
2) 实践案例
需求:三个div块,点击时改变块的颜色
方式①:在遍历绑定事件,使用var
时,不生效
1 |
|
失败原因
- var不存在块作用域,而是在全局代码中运行(作为window对象的属性),当for循环遍历结束时,i的值变为了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// 以下均在for的块内
{
var i = 0;
// 绑定事件,等待点击执行回调
items[0].onclick = function() {
items[i].style.background = "pink"; // 这里的i并不确定,因为尚未点击触发事件
}
}
{
var i = 1;
// 绑定事件,等待点击执行回调
items[1].onclick = function() {
items[i].style.background = "pink"; // 这里的i并不确定,因为尚未点击触发事件
}
}
{
var i = 2;
// ...
}
// 最后跳出循环时
{
var i = 3;
// ...
}- 外层for循环只是给每个item绑定了点击事件,然后点击事件是异步任务,此时for已经执行完了。每次点击块时,回调函数在函数作用域找不到i,向外在全局中找到i,所以点击任何一个按钮,回调函数实际上执行的语句为:
1
items[3].style.background = "pink";
超出了边界,因此不生效
方式②:使用let关键字
1 | for(let i = 0;i < items.length; i++) { |
成功原因
- let关键字只在块级生效,当每次点击块时,回调函数在函数作用域找不到i,向外(在for块内)找到i,实际执行的语句为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 以下均在for的块内
{
let i = 0;
// 绑定事件,等待点击执行回调
items[0].onclick = function() {
items[i].style.background = "pink"; // 这里的i并不确定,因为尚未点击触发事件
}
}
{
let i = 1;
// 绑定事件,等待点击执行回调
items[1].onclick = function() {
items[i].style.background = "pink"; // 这里的i并不确定,因为尚未点击触发事件
}
}
{
let i = 2;
// ...
}
// 最后跳出循环时
{
let i = 3;
// ...
}- 执行回调时,找到块内的i,并执行,此时,window对象里并没有i的属性
- 执行结果:
2.1.2 const关键字
const 关键字用来声明常量。
1 | const NAME = "Hongyi"; |
使用 const 声明的变量有如下特点:
- 不允许重复声明
值不允许修改
- 不存在变量提升
- 块级作用域
- 声明必须赋初始值
- 标识符一般为大写
对象属性修改和数组元素变化不会触发 const 错误
const
实际上保证的,并不是变量的值不得改动, 而是变量指向的那个内存地址所保存的数据不得改动
1
2const TEAM = ["UZI", "MING", "LETME"];
TEAM.push("MEIKO"); // 允许
**应用场景:声明对象类型使用 const,非对象类型声明选择 let **
2.1.3 作用域
1) 为什么需要块级作用域
ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。
例如用来计数的循环变量泄露为全局变量
1 | var s = 'Hongyi'; |
上面代码中,变量i
只用来控制循环,但是循环结束后它并没有消失,泄露成了全局变量。
2) ES6 的块级作用域
let
实际上为 JavaScript 新增了块级作用域
1 | function f1() { |
上面的函数有两个代码块, 都声明了变量n
, 运行后输出 5. 这表示外层代码块不受内层代码块的影响. 如果两次都使用var
定义变量n
, 最后输出的值才是 10.
- ES6 允许块级作用域的任意嵌套,并且外层不能读取内层的变量
1 | {{{{ |
上面代码使用了一个五层的块级作用域, 每一层都是一个单独的作用域. 第四层作用域无法读取第五层作用域的内部变量
.
- 内层作用域可以定义外层作用域的同名变量
1 | {{{{ |
块级作用域的出现, 实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了
1 | // IIFE 写法 |
- ES6 的块级作用域必须有大括号, 如果没有大括号 , JavaScript 引擎就认为不存在块级作用域.
1 | // 第一种写法, 报错 |
上面代码中, 第一种写法没有大括号, 所以不存在块级作用域, 而let
只能出现在当前作用域的顶层, 所以报错.
2.2 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
本质上,这种写法属于“模式匹配
”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
2.2.1 数组的解构赋值
以前,为变量赋值,只能直接指定值:
1 | let a = 1; |
ES6 允许写成下面这样.:
1 | let [a, b, c] = [1, 2, 3]; |
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的栗子。
1 | let [foo, [[bar], baz]] = [1, [[2], 3]];//foo : 1 bar : 2 baz : 3 |
如果解构不成功, 变量的值就等于 undefined:
1 | let [foo] = []; // foo: undefined |
2.2.2 对象的解构赋值
1 | // 对象的解构赋值 |
对象的解构与数组有一个重要的不同:数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
如果变量名与属性名不一致,必须写成下面这样–>取别名
1 | // 左侧:给foo属性取一个别名baz,并赋值给baz |
2.2.3 字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
1 | const [a, b, c, d, e] = 'hello'; |
类似数组的对象都有一个length
属性,因此还可以对这个属性解构赋值
1 | let {length : len} = 'hello';//len == 5 |
2.2.4 函数参数的解构赋值
1 | function add([x, y]){ |
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。
2.2.5 应用举例
- 交换变量的值
1 | let x = 1; |
- 从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
1 | // 返回一个数组 |
- 提取 JSON 数据
1 | let jsonData = { |
- 输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
1 | const { SourceMapConsumer, SourceNode } = require("source-map"); |
2.3 模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识
1 | // 声明 |
特点:
字符串中可以出现换行符
- 单引号或双引号的情况:报错
- 使用模板字符串:
1
2
3
4
5let str = `<ul>
<li>沈腾</li>
<li>马丽</li>
<li>艾伦</li>
</ul>`;可以使用
${xxx}
形式输出变量,拼接字符串
1 | let name = "Hongyi"; |
注意:当遇到字符串与变量拼接的情况使用模板字符串
2.4 简化对象写法
ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
原始写法:
1 | let name = "Hongyi"; |
ES6简化写法:
1 | let name = "Hongyi"; |
2.5 箭头函数
2.5.1 基本使用
ES6 允许使用「箭头」(=>
)定义函数。
原始写法:
1 | let fn = function(a, b) { |
简化的通用写法:
1 | let fn = (a, b) => { |
注意点
- this是静态的,始终指向函数声明时所在的作用域下的this的值
1 | function getName() { |
- 箭头函数不能作为构造函数实例化
1 | let Person = (name, age) => { |
- 不能使用 arguments
1 | let fn = () => { |
更多的简写形式
- 如果形参只有一个,则小括号可以省略
1 | let add = n => { |
- 函数体如果只有一条语句,则花括号可以省略,return必须省略,函数的返回值为该条语句的执行结果
1 | let pow = n => n * n; |
2.5.2 实践案例
需求-1:点击一个div块,2s后块变色
- 原始方法
1 |
|
- 利用箭头函数
1 | // 绑定事件 |
原因:()
函数在function() { // 定时器... }
的作用域中声明,因此()
函数的this指向所在作用域下的this。
需求-2:从数组中返回偶数的元素
- 原始方法
1 | let arr = [1, 6, 9, 10, 100, 25]; |
- 使用箭头函数
1 | let arr = [1, 6, 9, 10, 100, 25]; |
总结
- 箭头函数不会更改 this 指向,用来指定与this无关的回调函数会非常合适,即不新开辟this的指向。例如定时器,数组的方法回调
- 箭头函数不适合与this有关的回调,例如事件的回调,对象的方法等
1 | var obj = { |
2.5.3 补充:回调函数和箭头函数的this指向
什么是 this:自动引用正在调用当前方法的
.
前的对象正常情况下,this指向的三种情况
obj.fun()
:fun 中的 this->obj ,自动指向.前的对象new Fun()
:Fun 中的 this->正在创建的新对象,new 改变了函数内部的 this 指向,导致 this 指向实例化 new 的对象fun()
直接调用和匿名函数自调:this 默认->window,即函数内部的 this 默认是指向 window 的
示例1
1 | let Bob={ |
有三个函数:intr()
,forEach()
和回调函数function(ele){...}
打印结果:
1 | undefined认识Jack |
可见,回调函数中的this默认是指向window的
示例2
1 | var Bob={ |
也有三个函数:intr()
,forEach()
和箭头函数形式的回调函数ele => {...}
打印结果:
1 | 鲍勃认识Jack |
可以看出箭头函数内的this自动指向了回调函数外层的 this 。
或者利用函数闭包,将外层函数的this赋值给另一个变量self
1 | let Bob={ |
总结
箭头函数中的 this:
函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this。正是因为它没有 this,所以也就不能用作构造函数
2.5.4 函数参数的默认值设置
ES6允许给函数参数赋值初始值。
- 形参初始值:具有默认值的参数,一般位置要靠后(潜规则)
1
2
3
4
5
6function add(a, b, c = 10) {
return a + b + c;
}
let result = add(1, 2);
console.log(result); // 13- 与解构赋值结合
1
2
3
4
5
6
7
8
9
10
11
12function connect({host="127.0.0.1", username, password, port}) {
console.log(host); // "127.0.0.1"
console.log(username); // "root"
console.log(password); // "root"
console.log(port); // 3306
}
connect({
username: "root",
password: "root",
port: 3306
});这里host默认值为
127.0.0.1
2.6 Rest参数
- ES6 引入 rest 参数,用于获取函数的实参,用来代替 arguments
1 | // ES5形式 |
1 | // rest参数 |
- rest参数必须是最后一个形参
1 | function fn(a, b, ...args) { |
- rest 参数非常适合不定个数参数函数的场景
2.7 Spread扩展运算符
2.7.1 基本使用
扩展运算符(spread)也是三个点(...
)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
1 | const names = ["Hongyi", "Mark", "Yiming"]; |
2.7.2 实践案例
1 |
|
执行结果:
2.8 Symbol
2.8.1 介绍和创建
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。
1 | // 创建symbol |
Symbol 特点
- Symbol 的值是唯一的,用来解决命名冲突的问题
- Symbol 值不能与其他数据进行运算,以下操作都是非法的
1
2
3
4
5let s = Symbol();
let res = s + 100;
let res1 = s + "Hongyi";
let res2 = s + s;- Symbol 定义 的 对象属 性 不能 使 用
for...in
循 环遍 历,但 是可 以 使 用Reflect.ownKeys
来获取对象的所有键名
遇到唯一性的场景时要想到 Symbol
总结——JS中的数据类型
USONB
:You are so niubility
- u : undefined
- s : string symbol
- o : object
- n : null number
- b : boolean
2.8.2 使用场景
- 向对象添加属性和方法
暂略
2.8.3 内置值
暂略
2.9 迭代器
2.9.1 概述
遍历器(Iterator
)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历命令
for...of
循环,Iterator 接口主要供for...of
消费 - 原生具备 iterator 接口的数据结构(可用 for of 遍历)
Array
Arguments
Set
Map
String
TypedArray
NodeList
代码示例
1 | // 定义一个数组 |
工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
- 每调用 next 方法返回一个包含 value 和 done 属性的对象
1 | // 获取迭代器 |
2.9.2 应用:自定义遍历数据
对于不具有iterator 接口的数据结构,例如object对象,可以在其内部自定义一个迭代器属性:
1 | const banji = { |
2.10 生成器
2.10.1 概述
生成器函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
*
的位置没有限制- 生成器函数返回的结果是迭代器对象,调用迭代器对象的
next
方法可以得到yield
语句前的值 yield
相当于函数的暂停标记,也可以认为是函数的分隔符,每调用一次 next方法,执行一段代码
代码示例1
1 | // 声明生成器函数 |
代码示例2
1 | function * gen() { |
执行结果:
1 | 111 |
代码示例3
1 | function * gen() { |
执行结果:
1 | 我是1 |
2.10.2 参数传递
next 方法可以传递实参,作为上一个 yield 语句的返回值。
1 | function * gen(arg) { |
2.10.3 程序示例
- 需求1:1s后输出111,然后再过2s后输出222,然后再过3s后输出333
定时器方法
1 | // 回调地狱 |
生成器函数方法
1 | function one() { |
- 需求2:模拟按先后顺序获取数据:用户数据 –> 订单数据 –> 商品数据
1 | function getUsers() { |
2.11 集合Set
2.11.1 概述
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
size
:返回集合的元素个数add
:增加一个新元素,返回当前集合delete
:删除元素,返回 boolean 值has
:检测集合中是否包含某个元素,返回 boolean 值clear
:清空集合,返回 undefined
代码示例
1 | // 声明一个空set |
2.11.2 程序示例
- 需求1:数组去重
1 | let arr1 = [1, 2, 3, 4, 1, 2, 4]; |
- 需求2:求交集
1 | let arr1 = [1, 2, 3, 4, 1, 2, 4]; |
- 需求3:求并集
1 | let arr1 = [1, 2, 3, 4, 1, 2, 4]; |
- 需求4:求差集,即交集取反
1 | let arr1 = [1, 2, 3, 4, 1, 2, 4]; |
2.12 Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法:
size
:返回 Map 的元素个数set
:增加一个新元素,返回当前 Mapget
:返回键名对象的键值has
:检测 Map 中是否包含某个元素,返回 boolean 值clear
:清空集合,返回 undefined
代码示例
1 | // 声明一个空map |
执行结果:
2.13 class类
2.13.1 概述
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
代码示例
class
声明类constructor
定义构造函数初始化
1 | // 采用构造函数的形式 |
2.13.2 静态成员
static
定义静态方法和属性,静态方法和属性属于类,不属于实例对象。
1 | class Phone{ |
2.13.3 继承
关于ES5的继承,可查看
JS笔记中的9.2节
。extends
继承父类super
调用父级构造方法
1 | class Phone { |
执行结果:
- 父类方法可以重写
1 | class Phone { |
2.13.4 set和get
1 | // set和get |
执行结果:
2.14 对象方法的扩展
ES6 新增了一些 Object 对象的方法:
Object.is
比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
1 | console.log(Object.is(120, 120)); // true |
Object.assign
对象的合并,将源对象的所有可枚举属性,复制到目标对象
1 | let config1 = { |
setPrototypeOf
可以直接设置对象的原型,不建议使用
1 | let school = { |
2.15 模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来。
2.15.1 概述
模块化的好处
防止命名冲突
代码复用
高维护性
模块化规范产品
ES6之前的模块化规范和相应实现有:
规范 | 实现 |
---|---|
CommonJS | NodeJS,Browserify |
AMD | requireJS |
CMD | seaJS |
2.15.2 模块化语法
模块功能主要由两个命令构成:export 和 import。
export
命令用于规定模块的对外接口import
命令用于输入其他模块提供的功能
代码示例
- 创建
m1.js
1 | // 使用export向外暴露属性和方法 |
- 创建一个页面
1 |
|
注意:如果以静态页面的方式打开页面,会出现跨域错误。可在vs中下载live server
插件,再打开页面即可。
执行结果:
2.15.3 暴露数据语法
2.15.2小节的暴露属于分别暴露
,此小节介绍统一暴露
和默认暴露
- 统一暴露
1 | // 统一暴露 |
测试:
1 | import * as m2 from "./m2.js"; |
- 默认暴露
1 | // 默认暴露 |
测试:
1 | import * as m3 from "./m3.js"; |
2.15.4 引入数据语法
2.15.2小节的引入属于通用的导入方式。此外还有解构赋值形式和简便形式
- 解构赋值形式:针对三种暴露数据形式都可
1 | // 分别暴露 |
- 简便形式:只针对默认暴露
1 | import m3 from "./m3.js"; |
2.15.5 模块化方式二
将所有的模块引入都放在一个入口文件app.js
中:
1 | // 入口文件 |
然后在页面中引入该文件:
1 | <script src="./app.js" type="module"></script> |
执行结果:
2.15.6 babel对ES6模块化代码转换
Babel 是一个 JavaScript 编译器。作用是将ES6的代码转换为ES5的代码,用以向下兼容仅支持ES5的浏览器。
步骤
- 将之前编写的四个js文件放置于
src/js
- 安装babel工具:
babel-cli
:bable命令行工具babel-preset-env
:预设包,将es6语法转换为es5语法browserify
:轻量级打包工具,项目中使用webpack
1 | npm init --yes # 初始化 |
- 转换执行结果:以
m1.js
为例
1 | ; |
- 打包:以
app.js
为例
1 | npx browserify dist/js/app.js -o dist/bundle.js |
- 在页面中引入这个打包文件
1 | <script src="./dist/bundle.js"></script> |
3 ES7~ES11
略