ECMAScript2015~2021全特性学习宝典
Posted 蛙哇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ECMAScript2015~2021全特性学习宝典相关的知识,希望对你有一定的参考价值。
前言
ES全称ECMAScript,ECMAScript是ECMA制定的标准化脚本语言。目前javascript使用的ECMAScript版本为ECMA-417。关于ECMA的最新资讯可以浏览 ECMA news查看。
2015年正式发布的ECMAScript6(2015)已经成为了JavaScript这门语言的下一代标准, 随着ES2015的发布,标准委员会决定在每年都会发布一个ES的新版本。本文集合了 ES6 至 ES11 常用到的特性,包括还在规划的 ES12,只列举大概使用,使用新特性需要使用最新版的 bable 就行转义。
ECMAScript2015(ES6)
Let
使用let声明的变量:1.不属于顶层对象window, 2.不允许重复声明,3.不存在变量提升,4.暂时性死区,5.块级作用域
1. let 声明的全局变量不是全局对象window的属性
let a = 5
console.log(window.a) // undefined
2. 用let定义变量不允许重复声明
let a = 5
let a = 6
// VM131:1 Uncaught SyntaxError: Identifier \'a\' has already been declared
// at <anonymous>:1:1
3. let声明的变量不存在变量提升
function foo() {
console.log(a)
let a = 5
}
foo()
// Uncaught ReferenceError: Cannot access \'a\' before initialization
4. let声明的变量具有暂时性死区
var a = 5
if (true) {
a = 6
let a
}
// Uncaught ReferenceError: Cannot access \'a\' before initialization
上面代码中,存在全局变量 a ,但是块级作用域内 let 又声明了一个局部变量 a ,导致后者绑定这个块级作用域,所以在let声明变量前,对 a 赋值会报错。
有时“暂时性死区”比较隐蔽,比如:
function foo(b = a, a = 2) {
console.log(a, b)
}
foo()
// Uncaught ReferenceError: Cannot access \'a\' before initialization
5. let 声明的变量拥有块级作用域
let实际上为 JavaScript 新增了块级作用域
{
let a = 5
}
console.log(a) // undefined
Const
使用const声明的常量:1.不属于顶层对象window,2.不允许重复声明,3.不存在变量提升,4.暂时性死区,5. 块级作用域
1.const 定义变量后,不能修改它了,对变量的修改会抛出异常。
const PI = 3.1415
console.log(PI)
PI = 5
console.log(PI)
// Uncaught TypeError: Assignment to constant variable.
2.const声明变量不能改变,如果声明的是一个引用类型,则不能改变它的内存地址,可以改变它的内容。
const obj = {
name: \'wawa\',
age: 34
}
obj.school = \'imooc\'
console.log(obj)
// {name: "wawa", age: 34, school: "imooc"}
obj = {name: \'xx\'}
// VM109:9 Uncaught TypeError: Assignment to constant variable.
3. const 声明的时候必须初始化值,否则会报错
const PI
PI = 3.1415
// Uncaught SyntaxError: Missing initializer in const declaration
解构赋值
在 ES6 中新增了变量赋值的方式:解构赋值。允许按照一定模式,从数组和对象中提取值,对变量进行赋值。
1. 数组解构赋值
赋值元素可以是任意可遍历的对象
let [a, b, c] = "abc" // ["a", "b", "c"] let [one, two, three] = new Set([1, 2, 3])
被赋值的变量还可以是对象的属性,不局限于单纯的变量。
let user = {} [user.firstName, user.secondName] = \'Kobe Bryant\'.split(\' \') console.log(user.firstName, user.secondName) // Kobe Bryant
解构赋值在循环体中的应用,可以配合 entries 使用。
let user = { name: \'John\', age: 30 } // loop over keys-and-values for (let [key, value] of Object.entries(user)) { console.log(`${key}:${value}`) // name:John, then age:30 }
可以跳过赋值元素,如果想忽略数组的某个元素对变量进行赋值,可以使用逗号来处理。
// second element is not needed let [name, , title] = [\'John\', \'Jim\', \'Sun\', \'Moon\'] console.log( title ) // Sun
rest 参数
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"] console.log(name1) // Julius console.log(name2) // Caesar // Note that type of `rest` is Array. console.log(rest[0]) // Consul console.log(rest[1]) // of the Roman Republic console.log(rest.length) // 2
如果数组的内容少于变量的个数,并不会报错,没有分配到内容的变量会是 undefined。
let [firstName, surname] = [] console.log(firstName) // undefined console.log(surname) // undefined
当然你也可以给变量赋予默认值,防止 undefined 的情况出现:
// default values let [name = "Guest", surname = "Anonymous"] = ["Julius"] console.log(name) // Julius (from array) console.log(surname) // Anonymous (default used)
2. 对象解构赋值
解构赋值除了可以应用在 Array,也可以应用在 Object。基本的语法如下:let {var1, var2} = {var1:…, var2…}
let options = { title: "Menu", width: 100, height: 200 } let {title, width, height} = options console.log(title) // Menu console.log(width) // 100 console.log(height) // 200
赋值的过程中可以指定默认值的:
let options = { title: "Menu" } let {width = 100, height = 200, title} = options console.log(title) // Menu console.log(width) // 100 console.log(height) // 200
rest 运算符
let options = { title: "Menu", height: 200, width: 100 } let {title, ...rest} = options // now title="Menu", rest={height: 200, width: 100} console.log(rest.height) // 200 console.log(rest.width) // 100
嵌套对象
如果一个 Array 或者 Object 比较复杂,它嵌套了 Array 或者 Object,那只要被赋值的结构和右侧赋值的元素一致就好了
let options = { size: { width: 100, height: 200 }, items: ["Cake", "Donut"], extra: true // something extra that we will not destruct } // destructuring assignment on multiple lines for clarity let { size: { // put size here width, height }, items: [item1, item2], // assign items here title = \'Menu\' // not present in the object (default value is used) } = options console.log(title) // Menu console.log(width) // 100 console.log(height) // 200 console.log(item1) // Cake console.log(item2) // Donut
3.字符串解构赋值
可以当做是数组的解构:
let str = \'imooc\'
let [a, b, c, d, e] = str
console.log(a, b, c, d, e)
Array
在 ES6 中新增了很多实用的原生 API,方便开发者对 Array 的操控性更强,如 for...of、from、of、fill、find、findIndex等。
1. ES6 中数组遍历方式 for...of
for (let val of [1, 2, 3]) {
console.log(val);
}
// 1,2,3
for...of是支持 break、continue、return的,所以在功能上非常贴近原生的 for。
2. Array.from()将为数组转换为数组
let arrLike = {
0: \'a\',
1: \'b\',
2: \'c\',
length: 3
}
let arr = Array.from(arrLike);
// ["a", "b", "c"]
3.Array.of()
Array.of() 方法创建一个具有可变数量参数的新数组实例,而不考虑参数的数量或类型。
Array.of() 和 Array 构造函数之间的区别在于处理整数参数:Array.of(7) 创建一个具有单个元素 7 的数组,而 Array(7) 创建一个长度为7的空数组(注意:这是指一个有7个空位(empty)的数组,而不是由7个undefined组成的数组)。
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
Array(7); // [ , , , , , , ]
Array(1, 2, 3); // [1, 2, 3]
4.Array.prototype.fill()
fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
let array = [1, 2, 3, 4]
array.fill(0, 1, 2)
// [1,0,3,4]
5. Array.prototype.find()
find() 方法返回数组中满足提供的测试函数的第一个元素的值,否则返回 undefined。
let array = [5, 12, 8, 130, 44];
let found = array.find(function(element) {
return element > 10;
});
console.log(found);
// 12
6.Array.prototype.findIndex()
findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回-1。其实这个和 find() 是成对的,不同的是它返回的是索引而不是值。
let array = [5, 12, 8, 130, 44];
let found = array.find(function(element) {
return element > 10;
});
console.log(found);
// 1
7. Array.prototype.copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
let arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(1, 3))
// [1, 4, 5, 4, 5]
Function
1.默认参数
function foo(x, y = \'world\') {
console.log(x, y)
}
foo(\'hello\', 0)
2.Rest 参数
function sum(...nums) {
let num = 0
nums.forEach(function(item) {
num += item * 1
})
return num
}
console.log(sum(1, 2, 3)) // 6
console.log(sum(1, 2, 3, 4)) // 10
3.扩展运算符
Spread Operator 和 Rest Parameter 是形似但相反意义的操作符,简单的来说 Rest Parameter 是把不定的参数“收敛”到数组,而 Spread Operator 是把固定的数组内容“打散”到对应的参数。示例如下:
function sum(x = 1, y = 2, z = 3) {
return x + y + z
}
console.log(sum(...[4])) // 9
console.log(sum(...[4, 5])) // 12
console.log(sum(...[4, 5, 6])) // 15
4.length属性
函数指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。
function foo(x = 1, y = 2, z = 3) {
console.log(x, y)
}
console.log(foo.length)
// 0
5.name属性
function foo() {}
foo.name // "foo"
6.箭头函数
1、箭头函数中this指向定义时所在的对象,而不是调用时所在的对象,
2、箭头函数不可以当作构造函数,
3、箭头函数不可以使用arguments对象
如果只有一个参数,可以省略括号,如果大于一个参数一定要记得带括号.
let hello = (name) => { console.log(\'say hello\', name) } // 或者 let hello = name => { console.log(\'say hello\', name) }
如果返回值是表达式,如果返回值是表达式可以省略 return 和 {}
let pow = x => x * x
如果返回值是字面量对象,一定要用小括号包起来
let person = (name) => ({ age: 20, addr: \'Beijing City\' })
7.拓展
let foo = { name: \'es\', say: () => { console.log(this.name, this) } } console.log(foo.say()) // undefined
因为箭头函数中对 this 的处理是定义时,this 的指向也就是 foo 外层的所指向的 window,而 window 没有 name 属性,所以结果是 undefined。
Object
1. 属性简洁表示法
let name = \'xx\'
let age = 18
let obj = {
name,
age
}
2.属性名表达式
let s = \'school\'
let obj = {
foo: \'bar\',
[s]: \'xx\'
}
3.Object.is()
判断两个对象是否相等。
let obj1 = { // new Object()
name: \'xx\',
age: 34
}
let obj2 = { // new Object()
name: \'xx\',
age: 34
}
console.log(obj1 == obj2) // false
console.log(Object.is(obj1, obj2)) // false
let obj2 = obj1
console.log(Object.is(obj1, obj2)) // true
4.Object.assign()
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,它将返回目标对象。
const target = {
a: 1,
b: 2
}
const source = {
b: 4,
c: 5
}
const returnedTarget = Object.assign(target, source)
console.log(target)
// expected output: Object { a: 1, b: 4, c: 5 }
console.log(returnedTarget)
// expected output: Object { a: 1, b: 4, c: 5 }
Class
1. 声明类
class Animal {
constructor(type) {
this.type = type
}
walk() {
console.log( `I am walking` )
}
}
let dog = new Animal(\'dog\')
let monkey = new Animal(\'monkey\')
2. Setters & Getters
对于类中的属性,可以直接在 constructor 中通过 this 直接定义,还可以直接在类的顶层来定义:
class Animal {
constructor(type, age) {
this.type = type
this._age = age
}
get age() {
return this._age
}
set age(val) {
this._age = val
}
}
3. 静态方法
在 ES6 中使用 static 的标记是不是静态方法,代码如下:
class Animal {
constructor(type) {
this.type = type
}
walk() {
console.log( `I am walking` )
}
static eat() {
console.log( `I am eating` )
}
}
4. 继承
class Animal {
constructor(type) {
this.type = type
}
walk() {
console.log( `I am walking` )
}
static eat() {
console.log( `I am eating` )
}
}
class Dog extends Animal {
constructor () {
super(\'dog\')
}
run () {
console.log(\'I can run\')
}
}
Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
1. 声明方式
let s = Symbol()
typeof s
// "symbol"
变量s就是一个独一无二的值。typeof的结果说明s是 Symbol 数据类型。
既然是独一无二的,那么两个Symbol()就一定是不相等的:
let s1 = Symbol()
let s2 = Symbol()
console.log(s1)
console.log(s2)
console.log(s1 === s2) // false
Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
2. Symbol.for()
Symbol.for() 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建一个以该字符串为名称的 Symbol 值,并将其注册到全局。
let s1 = Symbol.for(\'foo\')
let s2 = Symbol.for(\'foo\')
console.log(s1 === s2) // true
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。
3. Symbol.keyFor()
Symbol.keyFor()方法返回一个已登记的 Symbol 类型值的key。
const s1 = Symbol(\'foo\')
console.log(Symbol.keyFor(s1)) // undefined
const s2 = Symbol.for(\'foo\')
console.log(Symbol.keyFor(s2)) // foo
4.作为属性名
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。
const stu1 = Symbol(\'李四\')
const stu2 = Symbol(\'李四\')
const grade = {
[stu1]: {
address: \'yyy\',
tel: \'222\'
},
[stu2]: {
address: \'zzz\',
tel: \'333\'
},
}
console.log(grade)
console.log(grade[stu1])
console.log(grade[stu2])
5.属性遍历
const sym = Symbol(\'imooc\')
class User {
constructor(name) {
this.name = name
this[sym] = \'imooc.com\'
}
getName() {
return this.name + this[sym]
}
}
const user = new User(\'xiecheng\')
console.log(user.getName())
for (let key in user) {
console.log(key)
}
for (let key of Object.keys(user)) {
console.log(key)
}
for (let key of Object.getOwnPropertySymbols(user)) {
console.log(key)
}
for (let key of Reflect.ownKeys(user)) {
console.log(key)
}
6.消除魔术字符串
魔术字符串指的是,在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值。风格良好的代码,应该尽量消除魔术字符串,改由含义清晰的变量代替。
function getArea(shape) {
let area = 0
switch (shape) {
case \'Triangle\':
area = 1
break
case \'Circle\':
area = 2
break
}
return area
}
console.log(getArea(\'Triangle\'))
上面代码中,字符串Triangle和Circle就是魔术字符串。它多次出现,与代码形成“强耦合”,不利于将来的修改和维护。
使用Symbol就可以很好的解决这个问题:
const shapeType = {
triangle: Symbol(),
circle: Symbol()
}
function getArea(shape) {
let area = 0
switch (shape) {
case shapeType.triangle:
area = 1
break
case shapeType.circle:
area = 2
break
}
return area
}
console.log(getArea(shapeType.triangle))
Set
在 JavaScript 里通常使用 Array 或 Object 来存储数据。但是在频繁操作数据的过程中查找或者统计并需要手动来实现,并不能简单的直接使用。 比如如何保证 Array 是去重的,如何统计 Object 的数据总数等,必须自己去手动实现类似的需求,不是很方便。 在 ES6 中为了解决上述痛点,新增了数据结构 Set 和 Map,它们分别对应传统数据结构的“集合”和“字典”。
1.基本语法
生成 Set 实例
let s = new Set()
可以定义一个空的 Set 实例,也可以在实例化的同时传入默认的数据。
let s = new Set([1, 2, 3, 4])
初始化的参数必须是可遍历的,可以是数组或者自定义遍历的数据结构。
添加数据
s.add(\'hello\') s.add(\'goodbye\') 或者 s.add(\'hello\').add(\'goodbye\')
Set 数据结构不允许数据重复,所以添加重复的数据是无效的
删除数据
删除数据分两种,一种是删除指定的数据,一种是删除全部数据。// 删除指定数据 s.delete(\'hello\') // true // 删除全部数据 s.clear()
统计数据
Set 可以快速进行统计数据,如数据是否存在、数据的总数。// 判断是否包含数据项,返回 true 或 false s.has(\'hello\') // true // 计算数据项总数 s.size // 2
数组去重
let arr = [1, 2, 3, 4, 2, 3] let s = new Set(arr) console.log(s)
合并去重
let arr1 = [1, 2, 3, 4] let arr2 = [2, 3, 4, 5, 6] let s = new Set([...arr1, ...arr2]) console.log(s) console.log([...s]) console.log(Array.from(s))
交集
let s1 = new Set(arr1) let s2 = new Set(arr2) let result = new Set(arr1.filter(item => s2.has(item))) console.log(Array.from(result))
差集
let arr3 = new Set(arr1.filter(item => !s2.has(item))) let arr4 = new Set(arr2.filter(item => !s1.has(item))) console.log(arr3) console.log(arr4) console.log([...arr3, ...arr4])
2.遍历方式
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
for...of:可以直接遍历每个成员
console.log(s.keys()) // SetIterator {"hello", "goodbye"} console.log(s.values()) // SetIterator {"hello", "goodbye"} console.log(s.entries()) // SetIterator {"hello" => "hello", "goodbye" => "goodbye"} s.forEach(item => { console.log(item) // hello // goodbye }) for (let item of s) { console.log(item) } for (let item of s.keys()) { console.log(item) } for (let item of s.values()) { console.log(item) } for (let item of s.entries()) { console.log(item[0], item[1]) }
3.WeakSet
WeakSet 结构与 Set 类似,也是不重复的值的集合。但是,它与 Set 有两个区别。WeakSet 的成员只能是对象,而不能是其他类型的值。
const ws = new WeakSet()
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set
let ws = new WeakSet()
const obj1 = {
name: \'imooc\'
}
const obj2 = {
age: 5
}
ws.add(obj1)
ws.add(obj2)
ws.delete(obj1)
console.log(ws)
console.log(ws.has(obj2))
WeakSet 没有size属性,没有办法遍历它的成员。
WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。
1.基本语法
实例化
let map = new Map([iterable])
Iterable 可以是一个数组或者其他 iterable 对象,其元素为键值对(两个元素的数组,例如: [[ 1, \'one\' ], [ 2, \'two\' ]])。 每个键值对都会添加到新的 Map。null 会被当做 undefined。
添加数据
let keyObj = {} let keyFunc = function() {} let keyString = \'a string\' // 添加键 map.set(keyString, "和键\'a string\'关联的值") map.set(keyObj, \'和键keyObj关联的值\') map.set(keyFunc, \'和键keyFunc关联的值\')
删除数据
// 删除指定的数据 map.delete(keyObj) // 删除所有数据 map.clear()
统计数据
// 统计所有 key-value 的总数 console.log(map.size) //2 // 判断是否有 key-value console.log(map.has(keyObj)) // true
查询数据
get() 方法返回某个 Map 对象中的一个指定元素console.log(map.get(keyObj)) // 和键keyObj关联的值
2.遍历方式
- keys() 返回一个新的 Iterator 对象。它包含按照顺序插入 Map 对象中每个元素的 key 值
- values() 方法返回一个新的 Iterator 对象。它包含按顺序插入Map对象中每个元素的 value 值
- entries() 方法返回一个新的包含 [key, value] 对的 Iterator ? 对象,返回的迭代器的迭代顺序与 Map 对象的插入顺序相同
- forEach() 方法将会以插入顺序对 Map 对象中的每一个键值对执行一次参数中提供的回调函数
for...of 可以直接遍历每个成员
map.forEach((value, key) => console.log(value, key)) for (let [key, value] of map) { console.log(key, value) } for (let key of map.keys()) { console.log(key) } for (let value of map.values()) { console.log(value) } for (let [key, value] of map.entries()) { console.log(key, value) }
其实 Object 也是按键值对存储和读取的,那么他俩之间除了我们之前说的区别以外还有其他的吗?
- 键的类型
一个Object的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值,包括函数、对象、基本类型。
- 键的顺序
Map 中的键值是有序的,而添加到对象中的键则不是。因此,当对它进行遍历时,Map 对象是按插入的顺序返回键值。
- 键值对的统计
你可以通过 size 属性直接获取一个 Map 的键值对个数,而 Object 的键值对个数只能手动计算。
- 键值对的遍历
Map 可直接进行迭代,而 Object 的迭代需要先获取它的键数组,然后再进行迭代。
- 性能
Map 在涉及频繁增删键值对的场景下会有些性能优势。
3.WeekMap
WeakMap结构与Map结构类似,也是用于生成键值对的集合。
// WeakMap 可以使用 set 方法添加成员
const wm1 = new WeakMap()
const key = {
foo: 1
}
wm1.set(key, 2)
wm1.get(key) // 2
// WeakMap 也可以接受一个数组,
// 作为构造函数的参数
const k1 = [1, 2, 3]
const k2 = [4, 5, 6]
const wm2 = new WeakMap([
[k1, \'foo\'],
[k2, \'bar\']
])
wm2.get(k2) // "bar"
WeakMap与Map的区别有两点。
WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。
const map = new WeakMap() map.set(1, 2) // TypeError: 1 is not an object! map.set(Symbol(), 2) // TypeError: Invalid value used as weak map key map.set(null, 2) // TypeError: Invalid value used as weak map key
WeakMap的键名所指向的对象,不计入垃圾回收机制。
String
1.Unicode表示法
ES6 加强了对 Unicode 的支持,允许采用\\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode 码点。
"\\u0061"
// "a"
但是,这种表示法只限于码点在\\u0000~\\uFFFF之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。
"\\uD842\\uDFB7"
// "以上是关于ECMAScript2015~2021全特性学习宝典的主要内容,如果未能解决你的问题,请参考以下文章
Javascript ES6 特性概述(即ECMAScript 6和ES2015+)