前言
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"
// "𠮷"
"\u20BB7"
// " 7"
上面代码表示,如果直接在\u后面跟上超过0xFFFF的数值(比如\u20BB7),JavaScript 会理解成\u20BB+7。由于\u20BB是一个不可打印字符,所以只会显示一个空格,后面跟着一个7。
ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。
"\u{20BB7}"
// "𠮷"
有了这种表示法之后,JavaScript 共有 6 种方法可以表示一个字符。
'\z' === 'z' // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
2.遍历器接口
ES6 为字符串添加了遍历器接口,详见Iterator一节,使得字符串可以被for...of循环遍历。
for (let item of 'imooc') {
console.log(item)
}
3.模板字符串
String Literals
这个是用来解决字符串拼接问题,从 ES6 开始可以这样定义字符串了。`string text` `string text line 1 string text line 2` `string text ${expression} string text`
在这里你可以任意插入变量或者表达式,只要用 ${} 包起来就好。
Tag Literals
前面的字符串字面量解决了字符串拼接的问题,对于包含复杂逻辑的字符串并不是简单的表达式能搞定的。所以需要另一种解决方案:Tag Literals,例子:var retailPrice = 20 var wholesalePrice = 16 var type = 'retail' var showTxt = '' if (type === 'retail') { showTxt += '您此次的购买单价是:' + retailPrice } else { showTxt += '您此次的批发价是:' + wholesalePrice }
现在可以定义一个 Tag 函数,然后用这个 Tag 函数来充当一个模板引擎:
function Price(strings, type) { let s1 = strings[0] const retailPrice = 20 const wholesalePrice = 16 let txt = '' if (type === 'retail') { txt = `购买单价是:${retailPrice}` } else { txt = `批发价是:${wholesalePrice}` } return `${s1}${txt}` } let showTxt = Price `您此次的${'retail'}` console.log(showTxt) //您此次的购买单价是:20
strings 参数指的是 Tag 函数后面被变量分割开的字符串集合,type 参数是对应第一个变量,Tag 函数可以有多个 type 类似的参数
4.扩展方法
String.prototype.fromCodePoint()
用于从 Unicode 码点返回对应字符,并且可以识别大于0xFFFF的字符。// ES5 console.log(String.fromCharCode(0x20BB7)) // ஷ // ES6 console.log(String.fromCodePoint(0x20BB7)) // 𠮷
String.prototype.includes()
ES5中可以使用indexOf方法来判断一个字符串是否包含在另一个字符串中,indexOf返回出现的下标位置,如果不存在则返回-1。const str = 'isxxx' console.log(str.indexOf('is'))
ES6提供了includes方法来判断一个字符串是否包含在另一个字符串中,返回boolean类型的值。
const str = 'isxxx' console.log(str.includes('xx'))
String.prototype.startsWith()
判断参数字符串是否在原字符串的头部, 返回boolean类型的值。const str = 'isxxx' console.log(str.startsWith('is'))
String.prototype.endsWith()
判断参数字符串是否在原字符串的尾部, 返回boolean类型的值。const str = 'isxxx' console.log(str.endsWith('xxx'))
String.prototype.repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。const str = 'isxxx' const newStr = str.repeat(10) console.log(newStr)
RegExp
1.y修饰符
ES6为正则表达式添加了y修饰符,叫做“粘连”(sticky)修饰符。
y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。
const s = 'aaa_aa_a'
const r1 = /a+/g
const r2 = /a+/y
r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]
r1.exec(s) // ["aa"]
r2.exec(s) // null
上面代码有两个正则表达式,一个使用g修饰符,另一个使用y修饰符。这两个正则表达式各执行了两次,第一次执行的时候,两者行为相同,剩余字符串都是_aa_a。由于g修饰没有位置要求,所以第二次执行会返回结果,而y修饰符要求匹配必须从头部开始,所以返回null。
如果改一下正则表达式,保证每次都能头部匹配,y修饰符就会返回结果了。
const s = 'aaa_aa_a'
const r = /a+_/y
r.exec(s) // ["aaa_"]
r.exec(s) // ["aa_"]
使用lastIndex属性,可以更好地说明y修饰符。
const regexp = /a/g
// 指定从2号位置(y)开始匹配
regexp.lastIndex = 2
// 匹配成功
const match = regexp.exec('xaya')
// 在3号位置匹配成功
console.log(match.index) // 3
// 下一次匹配从4号位开始
console.log(regexp.lastIndex) // 4
// 4号位开始匹配失败
regexp.exec('xaxa') // null
上面代码中,lastIndex属性指定每次搜索的开始位置,g修饰符从这个位置开始向后搜索,直到发现匹配为止。
y修饰符同样遵守lastIndex属性,但是要求必须在lastIndex指定的位置发现匹配。
const regexp = /a/y
// 指定从2号位置开始匹配
regexp.lastIndex = 2
// 不是粘连,匹配失败
regexp.exec('xaya') // null
// 指定从3号位置开始匹配
regexp.lastIndex = 3
// 3号位置是粘连,匹配成功
const match = regexp.exec('xaxa')
console.log(match.index) // 3
console.log(regexp.lastIndex) // 4
进一步说,y修饰符号隐含了头部匹配的标志^。
const reg = /b/y
reg.exec('aba')
// null
console.log(reg.lastIndex)
sticky 模式在正则匹配过程中只会影响两件事:
- 匹配必须从 re.lastIndex 开始(相当于正则表达中的 ^)
如果匹配到会修改 re.lastIndex(相当于 g 模式)
2.u修饰符
ES6为正则表达式添加了u修饰符,含义为“Unicode模式”,用来正确处理大于 \uFFFF 的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。/^\uD83D/u.test('\uD83D\uDC2A') // false /^\uD83D/.test('\uD83D\uDC2A') // true
上面代码中, \uD83D\uDC2A 是一个四个字节的UTF-16编码,代表一个字符 "🐪"。但是,ES5不支持四个字节的UTF-16编码,会将其识别为两个字符,导致第二行代码结果为true。加了u修饰符以后,ES6就会识别其为一个字符,所以第一行代码结果为false。
一旦加上u修饰符号,就会修改下面这些正则表达式的行为。
点字符
点(.)字符在正则表达式中,含义是除了换行符以外的任意单个字符。对于码点大于 0xFFFF 的 Unicode 字符,点字符不能识别,必须加上u修饰符。let s = '𠮷' /^.$/.test(s) // false /^.$/u.test(s) // true
Unicode字符表示法
ES6新增了使用大括号表示Unicode字符,这种表示法在正则表达式中必须加上u修饰符,才能识别。/\u{61}/.test('a') // false /\u{61}/u.test('a') // true /\u{20BB7}/u.test('𠮷') // true
量词
使用u修饰符后,所有量词都会正确识别码点大于0xFFFF的Unicode字符。/a{2}/.test('aa') // true /a{2}/u.test('aa') // true /𠮷{2}/.test('𠮷𠮷') // false /𠮷{2}/u.test('𠮷𠮷') // true
预定义模式
u修饰符也影响到预定义模式,能否正确识别码点大于0xFFFF的Unicode字符。/^\S$/.test('𠮷') // false /^\S$/u.test('𠮷') // true
上面代码的\S是预定义模式,匹配所有不是空格的字符。只有加了u修饰符,它才能正确匹配码点大于0xFFFF的Unicode字符。
利用这一点,可以写出一个正确返回字符串长度的函数。
function codePointLength(text) {
const result = text.match(/[\s\S]/gu)
return result ? result.length : 0
}
const s = '𠮷𠮷'
s.length // 4
codePointLength(s) // 2
i修饰符
有些Unicode字符的编码不同,但是字型很相近,比如,\u004B与\u212A都是大写的K。/[a-z]/i.test('\u212A') // false /[a-z]/iu.test('\u212A') // true
上面代码中,不加u修饰符,就无法识别非规范的K字符。
Number
1.二进制与八进制
JS中如何把十进制转化为二进制?
const a = 5 // 101 console.log(a.toString(2))
如何把八进制转化为二进制?
const b = 101 console.log(parseInt(b, 2))
ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。
const a = 0B0101 console.log(a) const b = 0O777 console.log(b)
2.新增方法
Number.isFinite()
用来检查一个数值是否为有限的(finite),即不是Infinity。Number.isFinite(15) // true Number.isFinite(0.8) // true Number.isFinite(NaN) // false Number.isFinite(Infinity) // false Number.isFinite(-Infinity) // false Number.isFinite('foo') // false Number.isFinite('15') // false Number.isFinite(true) // false
Number.isNaN()
用来检查一个值是否为NaN。Number.isNaN(NaN) // true Number.isNaN(15) // false Number.isNaN('15') // false Number.isNaN(true) // false Number.isNaN(9 / NaN) // true Number.isNaN('true' / 0) // true Number.isNaN('true' / 'true') // true
Number.parseInt()
ES6 将全局方法parseInt()移植到Number对象上面,行为完全保持不变。 这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。// ES5的写法 parseInt('12.34') // 12 // ES6的写法 Number.parseInt('12.34') // 12
Number.parseFloat()
ES6 将全局方法parseFloat()移植到Number对象上面,行为完全保持不变。这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。// ES5的写法 parseFloat('123.45#') // 123.45 // ES6的写法 Number.parseFloat('123.45#') // 123.45
Number.isInteger()
用来判断一个数值是否为整数。Number.isInteger(25) // true Number.isInteger(25.1) // false Number.isInteger() // false Number.isInteger(null) // false Number.isInteger('15') // false Number.isInteger(true) // false
Number.MAX_SAFE_INTEGER
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 // true Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Number.MIN_SAFE_INTEGER
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER // true Number.MIN_SAFE_INTEGER === -9007199254740991 // true
Number.isSafeInteger()
JavaScript 能够准确表示的整数范围在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。Math.pow(2, 53) // 9007199254740992 Math.pow(2, 53) === Math.pow(2, 53) + 1 // true
3.Math扩展
Math.trunc()
方法用于去除一个数的小数部分,返回整数部分。console.log(Math.trunc(5.5)) // 5 console.log(Math.trunc(-5.5)) // -5 console.log(Math.trunc(true)) // 1 console.log(Math.trunc(false)) // 0 console.log(Math.trunc(NaN)) // NaN console.log(Math.trunc(undefined)) // NaN console.log(Math.trunc()) // NaN
- Math.sign()
方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
它会返回五种值。
- 参数为正数,返回+1
- 参数为负数,返回-1
- 参数为 0,返回0
- 参数为-0,返回-0
其他值,返回NaN
console.log(Math.sign(5)) // 1 console.log(Math.sign(-5)) // -1 console.log(Math.sign(0)) // 0 console.log(Math.sign(NaN)) // NaN console.log(Math.sign(true)) // 1 console.log(Math.sign(false)) // 0
Math.cbrt()
方法用于计算一个数的立方根。console.log(Math.cbrt(8)) // 2 console.log(Math.cbrt('xx')) // NaN
Proxy
在 ES6 标准中新增的一个非常强大的功能是 Proxy,它可以自定义一些常用行为如查找、赋值、枚举、函数调用等。通过 Proxy 这个名称也可以看出来它包含了“代理”的含义,只要有“代理”的诉求都可以考虑使用 Proxy 来实现。
1.基本语法
let p = new Proxy(target, handler)
参数 | 含义 | 必选 |
---|---|---|
target | 用 Proxy 包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) | y |
handler | 一个对象,其属性是当执行一个操作时定义代理的行为的函数 | y |
2.常用拦截操作
get
拦截对象属性的读取,比如proxy.foo和proxy['foo']。let arr = [7, 8, 9] arr = new Proxy(arr, { get(target, prop) { // console.log(target, prop) return prop in target ? target[prop] : 'error' } }) console.log(arr[1]) console.log(arr[10])
let dict = { 'hello': '你好', 'world': '世界' } dict = new Proxy(dict, { get(target, prop) { return prop in target ? target[prop] : prop } }) console.log(dict['world']) console.log(dict['imooc'])
set
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。let arr = [] arr = new Proxy(arr, { set(target, prop, val) { if (typeof val === 'number') { target[prop] = val return true } else { return false } } }) arr.push(5) arr.push(6) console.log(arr[0], arr[1], arr.length)
has
拦截propKey in proxy的操作,返回一个布尔值。let range = { start: 1, end: 5 } range = new Proxy(range, { has(target, prop) { return prop >= target.start && prop <= target.end } }) console.log(2 in range) console.log(9 in range)
ownKeys
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。let userinfo = { username: 'xxx', age: 18, _password: '***' } userinfo = new Proxy(userinfo, { ownKeys(target) { return Object.keys(target).filter(key => !key.startsWith('_')) } }) // for (let key in userinfo) { // console.log(key) // } console.log(Object.keys(userinfo))
deleteProperty
拦截delete proxy[propKey]的操作,返回一个布尔值。let user = { name: 'xxx', age: 18, _password: '***' } user = new Proxy(user, { get(target, prop) { if (prop.startsWith('_')) { throw new Error('不可访问') } else { return target[prop] } }, set(target, prop, val) { if (prop.startsWith('_')) { throw new Error('不可访问') } else { target[prop] = val return true } }, deleteProperty(target, prop) { // 拦截删除 if (prop.startsWith('_')) { throw new Error('不可删除') } else { delete target[prop] return true } }, ownKeys(target) { return Object.keys(target).filter(key => !key.startsWith('_')) } }) console.log(user.age) console.log(user._password) user.age = 18 console.log(user.age) try { user._password = 'xxx' } catch (e) { console.log(e.message) } try { // delete user.age delete user._password } catch (e) { console.log(e.message) } console.log(user.age) for (let key in user) { console.log(key) }
apply
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。let sum = (...args) => { let num = 0 args.forEach(item => { num += item }) return num } sum = new Proxy(sum, { apply(target, ctx, args) { return target(...args) * 2 } }) console.log(sum(1, 2)) console.log(sum.call(null, 1, 2, 3)) console.log(sum.apply(null, [1, 2, 3]))
construct
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。let User = class { constructor(name) { this.name = name } } User = new Proxy(User, { construct(target, args, newTarget) { console.log('construct') return new target(...args) } }) console.log(new User('imooc'))
Reflect
Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。
1.设计目的
将Object属于语言内部的方法放到Reflect上
let obj = {} let newVal = '' Reflect.defineProperty(obj, 'name', { get() { return newVal }, set(val) { console.log('set') // this.name = val newVal = val } }) obj.name = 'es' console.log(obj.name)
修改某些Object方法的返回结果,让其变得更合理
// 老写法 try { Object.defineProperty(target, property, attributes) // success } catch (e) { // failure } // 新写法 if (Reflect.defineProperty(target, property, attributes)) { // success } else { // failure }
让Object操作变成函数行为
// 老写法 'assign' in Object // true // 新写法 Reflect.has(Object, 'assign') // true
Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。
Proxy(target, { set: function(target, name, value, receiver) { var success = Reflect.set(target, name, value, receiver) if (success) { console.log('property ' + name + ' on ' + target + ' set to ' + value) } return success } })
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法,这些方法与处理器对象的方法相同。Reflect不是一个函数对象,因此它是不可构造的。
2、常用方法
Reflect.apply()
Reflect.apply(target, thisArgument, argumentsList),target为目标函数;thisArgument为target函数调用时绑定的this对象;argumentsList为target函数调用时传入的实参列表,该参数应该是一个类数组的对象
Reflect.apply(Math.floor, undefined, [1.75])
// 1
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111])
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index
// 4
Reflect.apply(''.charAt, 'ponies', [3])
// "i"
Reflect.construct()
Reflect.construct() 方法的行为有点像 new 操作符 构造函数 , 相当于运行 new target(...args)var d = Reflect.construct(Date, [1776, 6, 4]) d instanceof Date // true d.getFullYear() // 1776
Reflect.defineProperty()
静态方法 Reflect.defineProperty() 基本等同于 Object.defineProperty() 方法,唯一不同是返回 Boolean 值。const student = {} Reflect.defineProperty(student, 'name', { value: 'Mike' }) // true student.name // "Mike"
Reflect.deleteProperty()
Reflect.deleteProperty 允许你删除一个对象上的属性。返回一个 Boolean 值表示该属性是否被成功删除。它几乎与非严格的 delete operator 相同。var obj = { x: 1, y: 2 } Reflect.deleteProperty(obj, "x") // true obj // { y: 2 } var arr = [1, 2, 3, 4, 5] Reflect.deleteProperty(arr, "3") // true arr // [1, 2, 3, , 5] // 如果属性不存在,返回 true Reflect.deleteProperty({}, "foo") // true // 如果属性不可配置,返回 false Reflect.deleteProperty(Object.freeze({ foo: 1 }), "foo") // false
Reflect.get()
Reflect.get() 方法的工作方式,就像从 object (target[propertyKey]) 中获取属性,但它是作为一个函数执行的。// Object var obj = { x: 1, y: 2 } Reflect.get(obj, 'x') // 1 // Array Reflect.get(['zero', 'one'], 1) // "one" // Proxy with a get handler var x = { p: 1 } var obj = new Proxy(x, { get(t, k, r) { return k + 'bar' } }) Reflect.get(obj, 'foo') // "foobar"
Reflect.getOwnPropertyDescriptor()
静态方法 Reflect.getOwnPropertyDescriptor() 与 Object.getOwnPropertyDescriptor() 方法相似。如果在对象中存在,则返回给定的属性的属性描述符,否则返回 undefined。Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'x') // {value: "hello", writable: true, enumerable: true, configurable: true} Reflect.getOwnPropertyDescriptor({ x: 'hello' }, 'y') // undefined Reflect.getOwnPropertyDescriptor([], 'length') // {value: 0, writable: true, enumerable: false, configurable: false}
- 更多方法可以参考Reflect
Promise
1、基本语法
Promise 就是为了解决“回调地狱”问题的,它可以将异步操作的处理变得很优雅。回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象promise可以支持多个并发的请求,获取并发请求中的数据这个promise可以解决异步的问题,本身不能说promise是异步的。
创建Promise实例。
const promise = new Promise(function(resolve, reject) {
// ... some code
if ( /* 异步操作成功 */ ) {
resolve(value)
} else {
reject(error)
}
})
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
- 处理结果正常的话,调用resolve(处理结果值),将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去
处理结果错误的话,调用reject(Error对象),将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。promise.then(function(value) { // success }, function(error) { // failure })
Promise 内部是有状态的(pending、fulfilled、rejected),Promise 对象根据状态来确定执行哪个方法。Promise 在实例化的时候状态是默认 pending 的,当异步操作是完成的,状态会被修改为 fulfilled,如果异步操作遇到异常,状态会被修改为 rejected。
2、Promise.prototype.then()
var promise = new Promise(function(resolve, reject) {
resolve('传递给then的值')
})
promise.then(function(value) {
console.log(value)
}, function(error) {
console.error(error)
})
- 当 handler 返回一个正常值的时候,这个值会传递给 Promise 对象的 onFulfilled 方法。
- 定义的 handler 中产生异常的时候,这个值则会传递给 Promise 对象的 onRejected 方法。
3、Promise.prototype.catch()
捕获异常是程序质量保障最基本的要求,可以使用 Promise 对象的 catch 方法来捕获异步操作过程中出现的任何异常
function test() {
return new Promise((resolve, reject) => {
reject(new Error('es'))
})
}
test().catch((e) => {
console.log(e.message) // es
})
4、Promise.resolve()
一般情况下我们都会使用 new Promise() 来创建 Promise 对象,但是除此之外我们也可以使用其他方法。
在这里,我们将会学习如何使用 Promise.resolve 和 Promise.reject 这两个方法。
静态方法 Promise.resolve(value) 可以认为是 new Promise() 方法的快捷方式。
比如 Promise.resolve(42) 可以认为是以下代码的语法糖。
new Promise(function(resolve) {
resolve(42)
})
方法 Promise.resolve(value) 的返回值也是一个 Promise 对象,所以我们可以像下面那样接着对其返回值进行 .then 调用。
Promise.resolve(42).then(function(value) {
console.log(value)
})
5、Promise.reject()
Promise.reject(error) 是和 Promise.resolve(value) 类似的静态方法,是 new Promise() 方法的快捷方式。
比如 Promise.reject(new Error("出错了")) 就是下面代码的语法糖形式。
new Promise(function(resolve, reject) {
reject(new Error('出错了'))
})
这段代码的功能是调用该Promise 对象通过then指定的 onRejected 函数,并将错误(Error)对象传递给这个 onRejected 函数。
Promise.reject(new Error('BOOM!'))
6、Promise.all()
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.all([p1, p2, p3]).then(function(results) {
console.log(results) // [1, 2, 3]
})
Promise.all 生成并返回一个新的 Promise 对象,所以它可以使用 Promise 实例的所有方法。参数传递promise数组中所有的 Promise 对象都变为resolve的时候,该方法才会返回, 新创建的 Promise 则会使用这些 promise 的值。
如果参数中的任何一个promise为reject的话,则整个Promise.all调用会立即终止,并返回一个reject的新的 Promise 对象。
由于参数数组中的每个元素都是由 Promise.resolve 包装(wrap)的,所以Promise.all 可以处理不同类型的 Promise 对象。
7、Promise.race()
var p1 = Promise.resolve(1)
var p2 = Promise.resolve(2)
var p3 = Promise.resolve(3)
Promise.race([p1, p2, p3]).then(function(value) {
console.log(value) // 1
})
Promise.race 生成并返回一个新的 Promise 对象。
参数 promise 数组中的任何一个 Promise 对象如果变为 resolve 或者 reject 的话, 该函数就会返回,并使用这个 Promise 对象的值进行 resolve 或者 reject。
Generator
Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。如果这句话不好理解,可以看下接下来的示例。
常规循环
for (let i = 0; i < 5; i += 1) { console.log(i) } // this will return immediately 0 -> 1 -> 2 -> 3 -> 4
利用 Generator
function* generatorForLoop() { for (let i = 0; i < 5; i += 1) { yield console.log(i) } } const genForLoop = generatorForLoop() console.log(genForLoop.next()) // first console.log - 0 console.log(genForLoop.next()) // 1 console.log(genForLoop.next()) // 2 console.log(genForLoop.next()) // 3 console.log(genForLoop.next()) // 4
对比下代码,常规的循环只能一次遍历完所有值,Generator 可以通过调用 next 方法拿到依次遍历的值,让遍历的执行变得“可控”。
1、基本语法
function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
// "Generator { }"
这个是 Generator 的定义方法,有几个点值得注意:
- 比普通函数多一个 *
- 函数内部用 yield 来控制程序的执行的“暂停”
- 函数的返回值通过调用 next 来“恢复”程序执行
Generator 函数的定义不能使用箭头函数,否则会触发 SyntaxError 错误
let generator = * () => {} // SyntaxError
let generator = () * => {} // SyntaxError
let generator = ( * ) => {} // SyntaxError
2、yield 表达式
yield 关键字用来暂停和恢复一个生成器函数
- yield 表达式的返回值是 undefined,但是遍历器对象的 next 方法可以修改这个默认值。
Generator 对象的 next 方法,遇到 yield 就暂停,并返回一个对象,这个对象包括两个属性:value 和 done。
function* gen() { let val val = yield 1 console.log( `1:${val}` ) // 1:undefined val = yield 2 console.log( `2:${val}` ) // 2:undefined val = yield 3 console.log( `3:${val}` ) // 3:undefined } var g = gen() console.log(g.next()) // {value: 1, done: false} console.log(g.next()) // {value: 2, done: false} console.log(g.next()) // {value: 3, done: false} console.log(g.next()) // {value: undefined, done: true}
3、方法
Generator 对象有几个方法,next、return、throw。
- next([value])
Generator 对象通过 next 方法来获取每一次遍历的结果,这个方法返回一个对象,这个对象包含两个属性:value 和 done。value 是指当前程序的运行结果,done 表示遍历是否结束。
其实 next 是可以接受参数的,这个参数可以让你在 Generator 外部给内部传递数据,而这个参数就是作为 yield 的返回值。
function* gen() {
var val = 100
while (true) {
console.log( `before ${val}` )
val = yield val
console.log( `return ${val}` )
}
}
var g = gen()
console.log(g.next(20).value)
// before 100
// 100
console.log(g.next(30).value)
// return 30
// before 30
// 30
console.log(g.next(40).value)
// return 40
// before 40
// 40
return()
return 方法可以让 Generator 遍历终止,有点类似 for 循环的 break。function* gen() { yield 1 yield 2 yield 3 } var g = gen() console.log(g.next()) // {value: 1, done: false} console.log(g.return()) // {value: undefined, done: true} console.log(g.next()) // {value: undefined, done: true}
throw()
可以通过 throw 方法在 Generator 外部控制内部执行的“终断”。function* gen() { while (true) { try { yield 42 } catch (e) { console.log(e.message) } } } let g = gen() console.log(g.next()) // { value: 42, done: false } console.log(g.next()) // { value: 42, done: false } console.log(g.next()) // { value: 42, done: false } // 中断操作 g.throw(new Error('break')) console.log(g.next()) // {value: undefined, done: true}
Iterator
处理集合中的每个项是很常见的操作。JavaScript 提供了许多迭代集合的方法,从简单的for循环到map()和filter()。迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义for...of循环的行为。
如果对 MDN 这个描述理解不是很到位的话,可以看下接下来这个小示例:
let authors = {
allAuthors: {
fiction: [
'Agatha Christie',
'J. K. Rowling',
'Dr. Seuss'
],
scienceFiction: [
'Neal Stephenson',
'Arthur Clarke',
'Isaac Asimov',
'Robert Heinlein'
],
fantasy: [
'J. R. R. Tolkien',
'J. K. Rowling',
'Terry Pratchett'
]
}
}
这个数据结构是汇总了所有作者,每个作者按创作性质进行了分类。如果我们想获取所有作者的名单,该怎么做呢?
for (let author of authors) {
console.log(author)
}
你发现这个遍历遇到了报错:Uncaught TypeError: authors is not iterable
1、基本语法
Iterator 就是 ES6 中用来实现自定义遍历的接口,按照上述的示例,我们来实现下这个接口:
authors[Symbol.iterator] = function() {
let allAuthors = this.allAuthors
let keys = Reflect.ownKeys(allAuthors)
let values = []
return {
next() {
if (!values.length) {
if (keys.length) {
values = allAuthors[keys[0]]
keys.shift()
}
}
return {
done: !values.length,
value: values.shift()
}
}
}
}
这个代码在数据结构上部署了 Iterator 接口,我们就可以用 for...of 来遍历代码了:
for (let value of authors) {
console.log( `${value}` )
}
2、可迭代协议和迭代器协议。
- 迭代器协议
这是两个概念:可迭代协议、迭代器协议。通俗的讲,迭代器协议要求符合以下条件:
- 首先,它是一个对象
- 其次,这个对象包含一个无参函数 next
- 最后,next 返回一个对象,对象包含 done 和 value 属性。其中 done 表示遍历是否结束,value 返回当前遍历的值。
- 可迭代协议
可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)。一些内置类型都是内置的可迭代类型并且有默认的迭代行为, 比如 Array or Map, 另一些类型则不是 (比如Object) 。
- 可迭代协议
为了变成可迭代对象, 一个对象必须实现 @@iterator 方法, 意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性。
如果让一个对象是可遍历的,就要遵守可迭代协议,该协议要求对象要部署一个以 Symbol.iterator 为 key 的键值对,而 value 就是一个无参函数,这个函数返回的对象要遵守迭代器协议。
3、Generator
熟悉了 Generator 之后,发现它是天然满足可迭代协议的。上述的代码我们可以用 Generator 来实现:
authors[Symbol.iterator] = function*() {
let allAuthors = this.allAuthors
let keys = Reflect.ownKeys(allAuthors)
let values = []
while (1) {
if (!values.length) {
if (keys.length) {
values = allAuthors[keys[0]]
keys.shift()
yield values.shift()
} else {
return false
}
} else {
yield values.shift()
}
}
}
同一个场景,同一个数据结构,写法确实不同的,利用 Generator 就不再需要显示的写迭代协议了(next方法和包含 done、value 属性的返回对象)。
Module
1、模块化的发展情况
无模块化-->CommonJS规范-->AMD规范-->CMD规范-->ES6模块化
1、CommonJS规范 Node中模块化规范
Commonjs的诞生给js模块化发展有了重要的启发,Commonjs非常受欢迎, 但是局限性很明显:Commonjs基于Node原生api在服务端可以实现模块同步加载, 但是仅仅局限于服务端,客户端如果同步加载依赖的话时间消耗非常大,所以需要一个 在客户端上基于Commonjs但是对于加载模块做改进的方案,于是AMD规范诞生了。
2、AMD规范, 异步模块定义, 允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到所有依赖加载完成之后(前置依赖),这个回调函数才会运行。
3、CMD规范,同样是受到Commonjs的启发,国内(阿里)诞生了一个CMD(Common Module Definition)规范。该规范借鉴了Commonjs的规范与AMD规范,在两者基础上做了改进。
CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。
AMD 推崇依赖前置、提前执行 CMD推崇依赖就近、延迟执行。
4、 到了2015年,ES6规范中,终于将模块化纳入JavaScript标准,从此js模块化被官方扶正,也是未来js的标准. 在ES6中,我们可以使用 import 关键字引入模块,通过 exprot 关键字导出模块,功能较之于前几个方案更为强大,也是我们所推崇的, 但是由于ES6目前无法在浏览器中执行,所以,我们只能通过babel将不被支持的import编译为当前受到广泛支持的 require。
2、export
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
导出变量或者常量
export const name = 'hello' export let addr = 'BeiJing City' export var list = [1, 2, 3]
或者
const name = 'hello' let addr = 'BeiJing City' var list = [1, 2, 3] export { name, addr, list }
导出函数
export function say(content) { console.log(content) } export function run() { console.log('run') }
或者
const say = (content) => { console.log(content) } let run = () => { console.log('run') } export { say, run }
导出 Object
export ({ code: 0, message: 'success' })
或者
let data = { code: 0, message: 'success' } export { data }
导出 Class
class Test { constructor() { this.id = 2 } } export { Test }
或者
export class Test { constructor() { this.id = 2 } }
3、as
如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。
const name = 'hello'
let addr = 'BeiJing City'
var list = [1, 2, 3]
export {
name as cname,
addr as caddr,
list
}
4、export default
使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出。
const name = 'hello'
let addr = 'BeiJing City'
var list = [1, 2, 3]
export {
name as cname,
addr as caddr
}
export default list
5、import
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
直接导入
假设导出模块 A 是这样的:const name = 'hello' let addr = 'BeiJing City' var list = [1, 2, 3] export { name as cname, addr as caddr } export default list
则导入:
import list, { cname, caddr } from A
修改导入名称
import list, { cname as name, caddr } from A
批量导入
import list, * as mod from A console.log(list) console.log(mod.cname) console.log(mod.caddr)
ECMAScript2016(ES7)
Array.prototype.includes()
ES7引入的Array.prototype.includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回false。
基本用法
const arr = ['es6', 'es7', 'es8'] console.log(arr.includes('es6')) // true console.log(arr.includes('es9')) // false
接收俩个参数
要搜索的值和搜索的开始索引。第二个参数可选。从该索引处开始查找 searchElement。如果为负值,const arr = ['es6', 'es7', 'es8'] console.log(arr.includes('es7', 1)) // true console.log(arr.includes('es7', 2)) // false console.log(arr.includes('es7', -1)) // false console.log(arr.includes('es7', -2)) // true
与indexOf()比较
['a', 'b', 'c'].includes('a') //true ['a', 'b', 'c'].indexOf('a') > -1 //true console.log(arr.indexOf('es7')) // 1 console.log(arr.indexOf('es7') > -1) // true
如果只想知道某个值是否在数组中存在,而并不关心它的索引位置,建议使用includes()。如果想获取一个值在数组中的位置,那么只能使用indexOf方法。
幂运算符**
求幂运算
console.log(2 ** 10) // 1024
ECMAScript2017(ES8)
async / await
async 和 await 是一种更加优雅的异步编程解决方案,是Promise 的拓展。
基本语法
前面添加了async的函数在执行后都会自动返回一个Promise对象:async function foo() { return 'xxx' // Promise.resolve('xxx') // let res = Promise.resolve('xxx') // console.log(res) } console.log(foo()) // Promise foo()
await后面需要跟异步操作,不然就没有意义,而且await后面的Promise对象不必写then,因为await的作用之一就是获取后面Promise对象成功状态传递出来的参数。
function timeout() { return new Promise(resolve => { setTimeout(() => { console.log(1) resolve() // resolve('success') }, 1000) }) } // 不加async和await是2、1 加了是1、2 async function foo() { await timeout() // let res = await timeout() res是success console.log(2) } foo()
对于失败的处理
function timeout() { return new Promise((resolve, reject) => { setTimeout(() => { // resolve('success') reject('error') }, 1000) }) } async function foo() { return await timeout() } foo().then(res => { console.log(res) }).catch(err => { console.log(err) })
Object 扩展
Object.values()
Object.values() 返回一个数组,其元素是在对象上找到的可枚举属性值。属性的顺序与通过手动循环对象的属性值所给出的顺序相同(for...in,但是for...in还会遍历原型上的属性值)。const obj = { name: 'baidu', web: 'www.baidu.com', course: 'es' } console.log(Object.values(obj)) // ["baidu", "www.baidu.com", "es"]
Object.entries()
Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致。(区别在于 for-in 循环也枚举原型链中的属性)let grade = { 'lilei': 98, 'hanmei': 87 } for (let [key, value] of grade) { console.log(key, value) // Uncaught TypeError: grade is not iterable }
Object.getOwnPropertyDescriptors()
console.log(Object.getOwnPropertyDescriptor(data, 'Lima')) // {value: "58/40", writable: true, enumerable: false, configurable: true}
String 扩展
ES8 中 String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将空字符串或其他字符串添加到原始字符串的开头或结尾。
String.prototype.padStart()
把指定字符串填充到字符串头部,返回新字符串。const str = 'xxx' console.log(str.padStart(8, 'x')) console.log(str.padEnd(8, 'y')) console.log(str.padStart(8))
场景1:日期格式化
const now = new Date() const year = now.getFullYear() const month = (now.getMonth() + 1).toString().padStart(2, '0') const day = (now.getDate()).toString().padStart(2, '0') console.log(year, month, day) console.log( `${year}-${month}-${day}` )
场景2:数字替换
// 数字替换,比如手机号,身份证号 const tel = '13012345678' const newTel = tel.slice(-4).padStart(tel.length, '*') console.log(newTel) // *******5678
String.prototype.padEnd()
方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充。const str1 = 'I am learning es in imooc' console.log(str1.padEnd(30, '.')) // I am learning es in imooc..... const str2 = '200' console.log(str2.padEnd(5)) // "200 "
场景:时间戳统一长度
// 伪代码 console.log(new Date().getTime()) // 时间戳 13位的 timestamp = +String(timestamp).padEnd(13, '0')
尾逗号 Trailing commas
ES8 允许函数的最后一个参数有尾逗号(Trailing comma)。
function clownsEverywhere( param1, param2, ) { /* ... */ } clownsEverywhere( 'foo', 'bar', )
ECMAScript2018(ES9)
for await of
异步迭代器(for-await-of):循环等待每个Promise对象变为resolved状态才进入下一步。
function Gen(time) { return new Promise(function(resolve, reject) { setTimeout(function() { resolve(time) }, time) }) } async function test() { let arr = [Gen(2000), Gen(100), Gen(3000)] for await (let item of arr) { console.log(Date.now(), item) } } test() // 1560092345730 2000 // 1560092345730 100 // 1560092346336 3000
RegExp Updates
- dotAll 模式
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)。 - U+000A 换行符(\n)
- U+000D 回车符(\r)
- U+2028 行分隔符(line separator)
U+2029 段分隔符(paragraph separator)
如何判断当前正则是否使用了 dotAll 模式呢const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's')` . console.log(re.test('foo\nbar')) // true console.log(re.dotAll) // true console.log(re.flags) // 's'
记住一句话就可以理解 dotAll 模式:它让 . 名副其实。
具名组匹配
我们在写正则表达式的时候,可以把一部分用()包裹起来,被包裹起来的这部分称作“分组捕获”。console.log('2020-05-01'.match(/(\d{4})-(\d{2})-(\d{2})/)) // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: undefined]
这个正则匹配很简单,按照 match 的语法,没有使用 g 标识符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2020, 05, 01)。
此外 match 返回值还有几个属性,分别是 index、input、groups。
- index [匹配的结果的开始位置]
- input [搜索的字符串]
groups [一个捕获组数组 或 undefined(如果没有定义命名捕获组)]
这里提到了命名捕获组的概念,如果没有定义 groups 就是 undefined。很明显,我们上述的返回值就是 undefined 间接说明没有定义命名捕获分组。那什么是命名捕获分组呢?console.log('2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)) // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}]
这段代码的返回值 groups 已经是 Object 了,具体的值是:
groups: { year: "2020", month: "05", day: "01" }
这个 Object 的 key 就是正则表达式中定义的,也就是把捕获分组进行了命名。想获取这些捕获可以这样做:
let t = '2020-05-01'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/) // ["2020-05-01", "2020", "05", "01", index: 0, input: "2020-05-01", groups: {…}] console.log(t.groups.year) // 2020 console.log(t.groups.month) // 05 console.log(t.groups.day) // 01
后行断言
在 ES9 之前 JavaScript 正则只支持先行断言,不支持后行断言。简单复习下先行断言的知识:let test = 'hello world' console.log(test.match(/hello(?=\sworld)/)) // ["hello", index: 0, input: "hello world", groups: undefined]
这段代码要匹配后面是 world 的 hello,但是反过来就不成:
let test = 'world hello' console.log(test.match(/hello(?=\sworld)/)) // null
比如我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:
let test = 'world hello' console.log(test.match(/(?<=world\s)hello/)) // ["hello", index: 6, input: "world hello", groups: undefined]
(?<...)是后行断言的符号,(?...)是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。
Object Rest & Spread
在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = { a: 1, b: 2 } const output = { ...input, c: 3 } console.log(output) // {a: 1, b: 2, c: 3}
我们再来看下 Object rest 的示例:
const input = { a: 1, b: 2, c: 3 } let { a, ...rest } = input console.log(a, rest) // 1 {b: 2, c: 3}
当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其他可选的 key 数据,这在之前是做不到的。
Promise.prototype.finally()
指定不管最后状态如何都会执行的回调函数。
Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
// reject('fail')
}, 1000)
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
}).finally(() => {
console.log('finally')
})
场景1:loading关闭
需要每次发送请求,都会有loading提示,请求发送完毕,就需要关闭loading提示框,不然界面就无法被点击。不管请求成功或是失败,这个loading都需要关闭掉,这时把关闭loading的代码写在finally里再合适不过了。
场景2:数据库断开链接
let connection
db.open()
.then(conn => {
connection = conn
return connection.select({
name: 'Jane'
})
})
.then(result => {
// Process result
// Use `connection` to make more queries
})···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close()
})
字符串扩展
放松对标签模板里字符串转义的限制, 遇到不合法的字符串转义返回undefined,并且从raw上可获取原字符串。
ES9开始,模板字符串允许嵌套支持常见转义序列,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。
// 带标签的模板字符串
const foo = (a, b, c, d) => {
console.log(a)
console.log(b)
console.log(c)
console.log(d)
}
// foo(1, 2, 3, 4)
const name = 'xxx'
const age = 18
foo `这是${name},他的年龄是${age}岁`
ES9 标准移除了对 ECMAScript带标签的模板字符串 中转义序列的语法限制。
function tag(strs) {
console.log(strs)
// strs[0] === undefined
// strs.raw[0] === "\\unicode and \\u{55}"
}
// 在标签函数中使用
tag `\u{61} and \u{62}` //
tag `\u{61} and \unicode` // 结果是 undefined
// 之前的版本会报错:Invalid Unicode escape sequence
// 无效的Unicode转义序列
// 报错:
let bad = `bad escape sequence: \unicode`
ECMAScript2019(ES10)
Object.fromEntries()
Object.fromEntries() 把键值对列表转换为一个对象,这个方法是和 Object.entries() 相对的。
Object.fromEntries([
['foo', 1],
['bar', 2]
])
// {foo: 1, bar: 2}
String 扩展
String.prototype.trimStart()
trimStart() 方法从字符串的开头删除空格,trimLeft()是此方法的别名。
let str = ' foo ' console.log(str.length) // 8 str = str.trimStart() console.log(str.length) // 5
String.prototype.trimEnd()
trimEnd() 方法从一个字符串的右端移除空白字符,trimRight 是 trimEnd 的别名。
let str = ' foo ' console.log(str.length) // 8 str = str.trimEnd() console.log(str.length) // 6
Array 扩展
Array.prototype.flat()
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。
const numbers = [1, 2, [3, 4, [5, 6]]] console.log(numbers.flat()) // [1, 2, 3, 4, [5, 6]]
此时 flat 的参数没有设置,取默认值 1,也就是说只扁平化向下一级,遇到 [3, 4, [5, 6]] 这个数组会扁平会处理,不会再继续遍历内部的元素是否还有数组
const numbers = [1, 2, [3, 4, [5, 6]]] console.log(numbers.flat(2)) // [1, 2, 3, 4, 5, 6]
当 flat 的参数大于等于 2,返回值就是 [1, 2, 3, 4, 5, 6] 了。
Array.prototype.flatMap()
flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。从方法的名字上也可以看出来它包含两部分功能一个是 map,一个是 flat(深度为1)。
const numbers = [1, 2, 3] numbers.map(x => [x * 2]) // [[2], [4], [6]] numbers.flatMap(x => [x * 2]) // [2, 4, 6]
这个示例可以简单对比下 map 和 flatMap 的区别。当然还可以看下下面的示例:
let arr = ['今天天气不错', '', '早上好'] arr.map(s => s.split('')) // [["今", "天", "天", "气", "不", "错"],[""],["早", "上", "好"]] arr.flatMap(s => s.split('')) // ["今", "天", "天", "气", "不", "错", "", "早", "上", "好"]
修订 Function.prototype.toString()
函数是对象,并且每个对象都有一个 .toString() 方法,因为它最初存在于Object.prototype.toString() 上。所有对象(包括函数)都是通过基于原型的类继承从它继承的。这意味着我们以前已经有 funcion.toString() 方法了。
Function.prototype.toString() 方法返回一个表示当前函数源代码的字符串。
这意味着还将返回注释、空格和语法详细信息。
function foo() {
// es10新特性
console.log('xxxx')
}
console.log(foo.toString())
// 直接在方法名toString()
console.log(Number.parseInt.toString())
可选的Catch Binding
在 ES10 之前我们都是这样捕获异常的:
try {
// tryCode
} catch (err) {
// catchCode
}
在这里 err 是必须的参数,在 ES10 可以省略这个参数:
try {
console.log('Foobar')
} catch {
console.error('Bar')
}
JSON扩展
JSON superset
什么是 JSON 超集?,简而言之就是让 ECMAScript 兼容所有JSON支持的文本。 ECMAScript 曾在标准 JSON.parse 部分阐明 JSON 确为其一个子集,但由于 JSON 内容可以正常包含 U+2028行分隔符 与 U+2029段分隔符,而ECMAScript 却不行。
JSON.stringify() 增强能力
JSON.stringify 在 ES10 修复了对于一些超出范围的 Unicode 展示错误的问题。因为 JSON 都是被编码成 UTF-8,所以遇到 0xD800–0xDFFF 之内的字符会因为无法编码成 UTF-8 进而导致显示错误。在 ES10 它会用转义字符的方式来处理这部分字符而非编码的方式,这样就会正常显示了。
// \uD83D\uDE0E emoji 多字节的一个字符 console.log(JSON.stringify('\uD83D\uDE0E')) // 笑脸 // 如果我们只去其中的一部分 \uD83D 这其实是个无效的字符串 // 之前的版本 ,这些字符将替换为特殊字符,而现在将未配对的代理代码点表示为JSON转义序列 console.log(JSON.stringify('\uD83D')) // "\ud83d"
Symbol 扩展
Symbol.prototype.description
只读属性,返回 Symbol 对象的可选描述的字符串。
我们知道,Symbol 的描述只被存储在内部的 Description ,没有直接对外暴露,我们只有调用 Symbol 的 toString() 时才可以读取这个属性:
const name = Symbol('es')
console.log(name.toString()) // Symbol(es)
console.log(name) // Symbol(es)
console.log(name === 'Symbol(es)') // false
console.log(name.toString() === 'Symbol(es)') // true
现在可以通过 description 方法获取 Symbol 的描述:
const name = Symbol('es')
console.log(name.description) // es
console.log(name.description === 'es') // true
ECMAScript2020(ES11)
String 扩展
String.prototype.matchAll()
matchAll() 方法返回一个包含所有匹配正则表达式及分组捕获结果的迭代器
const str = ` <html> <body> <div>第一个div</div> <p>这是一个p</p> <span>span</span> <div>第二个div</div> <body> </html> `
请找出所有的div元素。
function selectDiv(regExp, str) { let matches = [] for (let match of str.matchAll(regExp)) { matches.push(match[1]) } return matches } const res = selectDiv(regExp, str) console.log(res)
Dynamic Import
按需 import 提案几年前就已提出,如今终于能进入ES正式规范。这里个人理解成“按需”更为贴切。现代前端打包资源越来越大,打包成几M的JS资源已成常态,而往往前端应用初始化时根本不需要全量加载逻辑资源,为了首屏渲染速度更快,很多时候都是按需加载,比如懒加载图片等。而这些按需执行逻辑资源都体现在某一个事件回调中去加载。
页面上有一个按钮,点击按钮才去加载ajax模块。
const oBtn = document.querySelector('#btn')
oBtn.addEventListener('click', () => {
import('./ajax').then(mod => {
// console.log(mod)
mod.default('static/a.json', res => {
console.log(res)
})
})
})
当然,webpack目前已很好的支持了该特性。
BigInt
在 ES10 增加了新的原始数据类型:BigInt,表示一个任意精度的整数,可以表示超长数据,可以超出2的53次方。
Js 中 Number类型只能安全的表示-(2^53-1)至 2^53-1 范的值
console.log(2 ** 53) // es7 幂运算符
console.log(Number.MAX_SAFE_INTEGER) // 最大值-1
使用 BigInt 有两种方式:
方式一:数字后面增加n
const bigInt = 9007199254740993n console.log(bigInt) console.log(typeof bigInt) // bigint console.log(1n == 1) // true console.log(1n === 1) // false
方式二:使用 BigInt 函数
const bigIntNum = BigInt(9007199254740993n) console.log(bigIntNum)
Promise.allSettled()
学习了ES新特性,我们都知道 Promise.all() 具有并发执行异步任务的能力。但它的最大问题就是如果其中某个任务出现异常(reject),所有任务都会挂掉,Promise直接进入reject 状态。而 Promise.allSettled 返回一个在所有给定的promise已被决议或被拒绝后决议的promise,并带有一个对象数组,每个对象表示对应的promise结果。
Promise.allSettled([ Promise.reject({ code: 500, msg: '服务异常' }), Promise.resolve({ code: 200, data: ['1', '2', '3'] }), Promise.resolve({ code: 200, data: ['4', '5', '6'] }) ]).then(res => { console.log(res) // console.log('成功') const data = res.filter(item => item.status === 'fulfilled') console.log(data) }).catch(err => { console.log(err) console.log('失败') })
globalThis
Javascript 在不同的环境获取全局对象有不通的方式:
- node 中通过 global
web 中通过 window, self 等.
self:打开任何一个网页,浏览器会首先创建一个窗口,这个窗口就是一个window对象,也是js运行所依附的全局环境对象和全局作用域对象。self 指窗口本身,它返回的对象跟window对象是一模一样的。也正因为如此,window对象的常用方法和函数都可以用self代替window。
globalThis 提供了一个标准的方式来获取不同环境下的全局 this 对象(也就是全局对象自身)。不像 window 或者 self 这些属性,它确保可以在有无窗口的各种环境下正常工作。所以,你可以安心的使用 globalThis,不必担心它的运行环境。为便于记忆,你只需要记住,全局作用域中的 this 就是 globalThis。
console.log(globalThis)
可选链 Optional chaining
可让我们在查询具有多层级的对象时,不再需要进行冗余的各种前置校验。
const user = {
address: {
street: 'xx街道',
getNum() {
return '80号'
}
}
}
在之前的语法中,想获取到深层属性或方法,不得不做的前置校验,否则很容易命中 Uncaught TypeError: Cannot read property... 这种错误,这极有可能让你整个应用挂掉。
const street = user && user.address && user.address.street
const num = user && user.address && user.address.getNum && user.address.getNum()
console.log(street, num)
用了 Optional Chaining ,上面代码会变成
const street2 = user?.address?.street
const num2 = user?.address?.getNum?.()
console.log(street2, num2)
可选链中的 ? 表示如果问号左边表达式有值, 就会继续查询问号后面的字段。根据上面可以看出,用可选链可以大量简化类似繁琐的前置校验操作,而且更安全。
空值合并运算符(Nullish coalescing Operator)
空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
当我们查询某个属性时,经常会遇到,如果没有该属性就会设置一个默认的值。
const b = 0 // 或者null undefined false
const a = b || 5
console.log(a)
空值合并运算符 ?? 我们仅在第一项为 null 或 undefined 时设置默认值
// false 0 无效
const a = b ?? 123
console.log(a)
ECMAScript2021(ES12)
replaceAll
返回一个全新的字符串,所有符合匹配规则的字符都将被替换掉
const str = 'hello world';
str.replaceAll('l', ''); // "heo word"
Promise.any
Promise.any() 接收一个Promise可迭代对象,只要其中的一个 promise 成功,就返回那个已经成功的 promise 。如果可迭代对象中没有一个 promise 成功(即所有的 promises 都失败/拒绝),就返回一个失败的 promise。
const promise1 = new Promise((resolve, reject) => reject('我是失败的Promise_1'));
const promise2 = new Promise((resolve, reject) => reject('我是失败的Promise_2'));
const promiseList = [promise1, promise2];
Promise.any(promiseList)
.then(values=>{
console.log(values);
})
.catch(e=>{
console.log(e);
});
WeakRefs
WeakRefs的Class类创建对对象的弱引用(对对象的弱引用是指当该对象应该被GC回收时不会阻止GC的回收行为)
当我们通过(const、let、var)创建一个变量时,垃圾收集器GC将永远不会从内存中删除该变量,只要它的引用仍然存在可访问。WeakRef对象包含对对象的弱引用。对对象的弱引用是不会阻止垃圾收集器GC恢复该对象的引用,则GC可以在任何时候删除它。
WeakRefs在很多情况下都很有用,比如使用Map对象来实现具有很多需要大量内存的键值缓存,在这种情况下最方便的就是尽快释放键值对占用的内存。
目前,可以通过WeakMap()或者WeakSet()来使用WeakRefs
我想要跟踪特定的对象调用某一特定方法的次数,超过1000条则做对应提示
let map = new Map()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = map.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
map.set(obj, called)
}
如上虽然可以实现我们的功能,但是会发生内存溢出,因为传递给doSomething函数的每个对象都永久保存在map中,并且不会被GC回收,因此我们可以使用WeakMap
let wmap = new WeakMap()
function doSomething(obj){
...
}
function useObject(obj){
doSomething(obj)
let called = wmap.get(obj) || 0
called ++
if(called>1000){
console.log('当前调用次数已经超过1000次了,over')
}
wmap.set(obj, called)
}
逻辑运算符和赋值表达式
运算符和赋值表达式,新特性结合了逻辑运算符(&&,||,??)和赋值表达式而JavaScript已存在的 复合赋值运算符有如下
a ||= b
//等价于
a = a || (a = b)
a &&= b
//等价于
a = a && (a = b)
a ??= b
//等价于
a = a ?? (a = b)
数字分隔符
数字分隔符,可以在数字之间创建可视化分隔符,通过_下划线来分割数字,使数字更具可读性
const money = 1_000_000_000;
//等价于
const money = 1000000000;
1_000_000_000 === 1000000000; // true
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。