跳到主要内容

ES6+

ES6 版本算是 JavaScript 的一个分水岭,目前虽然在生产实践中绝大部分最终都会转换为 ES5 版本的代码,但是在开发实践中,ES6+ 几乎才是开发人员的标配,故这边截取一些常用的做个汇总吧。

截止目前其实主要就是围绕着 ES6~12 这些版本的新特性,简要的做一些说明。

字面量的增强

ES6 中对 对象字面量 进行了增强,称之为 Enhanced object literals(增强对象字面量)。字面量的增强主要包括下面几部分:

  • 属性的简写:Property Shorthand
  • 方法的简写:Method Shorthand
  • 计算属性名:Computed Property Names
var name = "js";
var obj = {
// 1.property shorthand(属性的简写)
name,

// 2.method shorthand(方法的简写)
foo: function () {
console.log(this);
},
bar() {
console.log(this);
},

baz: () => {
console.log(this);
},

// 3.computed property name(计算属性名)
[name + 123]: "javascript",
};

解构 Destructuring

ES6 中新增了一个从数组或对象中方便获取数据的方法,称之为解构 Destructuring。

我们可以划分为:数组的解构和对象的解构。

数组的解构:

基本解构过程

  • 顺序解构
  • 解构出数组
  • 默认值
var names = ["abc", "cba", "nba"];
// var item1 = names[0]
// var item2 = names[1]
// var item3 = names[2]

// 对数组的解构: []
var [item1, item2, item3] = names;
console.log(item1, item2, item3);

// 解构后面的元素
var [, , itemz] = names;
console.log(itemz);

// 解构出一个元素,后面的元素放到一个新数组中
var [itemx, ...newNames] = names;
console.log(itemx, newNames);

// 解构的默认值
var [itema, itemb, itemc, itemd = "aaa"] = names;
console.log(itemd);

对象的解构:

  • 基本解构过程
  • 任意顺序
  • 重命名
  • 默认值
var obj = {
name: "js",
age: 18,
height: 1.88,
};

// 对象的解构: {}
var { name, age, height } = obj;
console.log(name, age, height);

var { age } = obj;
console.log(age);

var { name: newName } = obj;
console.log(newName);

var { address: newAddress = "厦门" } = obj;
console.log(newAddress);

function foo(info) {
console.log(info.name, info.age);
}

foo(obj);

function bar({ name, age }) {
console.log(name, age);
}

bar(obj);

let/const 基本使用

ES6 中新增了块级作用域,最直接的表现就是新增的 let 关键词,使用 let 关键词定义的变量只能在块级作用域中被访问,有“暂时性死区”的特点,也就是说这个变量在定义之前是不能被使用的。

详细查看:声明、变量及赋值

字符串模板

在 ES6 之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly)。ES6 允许我们使用字符串模板来嵌入 JS 的变量或者表达式来进行拼接:

  • 首先,我们会使用 `` 符号来编写字符串,称之为模板字符串;
  • 其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;
// ES6之前拼接字符串和其他标识符
const name = "js";
const age = 18;
const height = 1.88;

console.log("my name is " + name + ", age is " + age + ", height is " + height);

// ES6提供模板字符串 ``
const message = `my name is ${name}, age is ${age}, height is ${height}`;
console.log(message);
function foo(m, n, x) {
console.log(m, n, x);
}

// foo("Hello", "World")
// 另外调用函数的方式: 标签模块字符串
// foo``

// foo`Hello World`
const name = "js";
const age = 18;
// ['Hello', 'Wo', 'rld']
foo`Hello${name}Wo${age}rld`;

类(class)

class Man {
constructor(name) {
this.name = '前端';
}
console() {
console.log(this.name);
}
}
const man = new Man('前端');
man.console(); // 前端

箭头(Arrow)函数

const func = (a, b) => a + b;
func(1, 2); // 3

函数的默认参数

在 ES6 之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求:

  • 传入了参数,那么使用传入的参数;
  • 没有传入参数,那么使用一个默认值;

而在 ES6 中,我们允许给函数一个默认值:

// 1.ES6可以给函数参数提供默认值
function foo(m = "aaa", n = "bbb") {
console.log(m, n);
}

// foo()
foo(0, "");

// 2.对象参数和默认值以及解构
function printInfo({ name, age } = { name: "js", age: 18 }) {
console.log(name, age);
}

printInfo({ name: "JavaScript", age: 40 });

// 另外一种写法
function printInfo1({ name = "js", age = 18 } = {}) {
console.log(name, age);
}

printInfo1();

// 3.有默认值的形参最好放到最后
function bar(x, y, z = 30) {
console.log(x, y, z);
}

// bar(10, 20)
bar(undefined, 10, 20);

// 4.有默认值的函数的length属性
function baz(x, y, z, m, n = 30) {
console.log(x, y, z, m, n);
}

console.log(baz.length); // 4

async/await

async getData(){
const res = await api.getData(); // await 异步任务
// do something
}

Spread operator 延展操作符 展开运算符

const names = ["abc", "cba", "nba"]
const name = "js"
const info = {name: "js", age: 18}

// 1.函数调用时
function foo(x, y, z) {
console.log(x, y, z)
}

// foo.apply(null, names)
foo(...names)
foo(...name)

// 2.构造数组时
const newNames = [...names, ...name]
console.log(newNames)

// 3.构建对象字面量时ES2018(ES9)
const obj = { ...info, address: "厦门", ...names }
console.log(obj)

展开语法可以浅拷贝。

数值表示

const num1 = 100 // 十进制

// b -> binary
const num2 = 0b100 // 二进制
// o -> octonary
const num3 = 0o100 // 八进制
// x -> hexadecimal
const num4 = 0x100 // 十六进制

console.log(num1, num2, num3, num4)

// 大的数值的连接符(ES2021 ES12)
const num = 10_000_000_000_000_000
console.log(num)

Symbol

Symbol是什么呢?Symbol是ES6中新增的一个基本数据类型,翻译为符号。

那么为什么需要Symbol呢?

  • 在ES6之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;
  • 比如原来有一个对象,我们希望在其中添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很容易造成冲突,从而覆盖掉它内部的某个属性;
  • 比如我们前面在讲apply、call、bind实现时,我们有给其中添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
  • 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;

Symbol就是为了解决上面的问题,用来生成一个独一无二的值。

  • Symbol值是通过Symbol函数来生成的,生成后可以作为属性名;
  • 也就是在ES6中,对象的属性名可以使用字符串,也可以使用Symbol值;

Symbol即使多次创建值,它们也是不同的:Symbol函数执行后每次创建出来的值都是独一无二的; n 我们也可以在创建Symbol值的时候传入一个描述description:这个是ES2019(ES10)新增的特性;

// 1.ES6之前, 对象的属性名(key)
// var obj = {
// name: "js",
// friend: { name: "JavaScript" },
// age: 18
// }


// obj['newName'] = "james"
// console.log(obj)


// 2.ES6中Symbol的基本使用
const s1 = Symbol()
const s2 = Symbol()

console.log(s1 === s2)

// ES2019(ES10)中, Symbol还有一个描述(description)
const s3 = Symbol("aaa")
console.log(s3.description)


// 3.Symbol值作为key
// 3.1.在定义对象字面量时使用
const obj = {
[s1]: "abc",
[s2]: "cba"
}

// 3.2.新增属性
obj[s3] = "nba"

// 3.3.Object.defineProperty方式
const s4 = Symbol()
Object.defineProperty(obj, s4, {
enumerable: true,
configurable: true,
writable: true,
value: "mba"
})

console.log(obj[s1], obj[s2], obj[s3], obj[s4])
// 注意: 不能通过.语法获取
// console.log(obj.s1)

// 4.使用Symbol作为key的属性名,在遍历/Object.keys等中是获取不到这些Symbol值
// 需要Object.getOwnPropertySymbols来获取所有Symbol的key
console.log(Object.keys(obj))
console.log(Object.getOwnPropertyNames(obj))
console.log(Object.getOwnPropertySymbols(obj))
const sKeys = Object.getOwnPropertySymbols(obj)
for (const sKey of sKeys) {
console.log(obj[sKey])
}

// 5.Symbol.for(key)/Symbol.keyFor(symbol)
const sa = Symbol.for("aaa")
const sb = Symbol.for("aaa")
console.log(sa === sb)

const key = Symbol.keyFor(sa)
console.log(key)
const sc = Symbol.for(key)
console.log(sa === sc)

Set、WeakSet

Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式)。

Set常见的属性:

size:返回Set中元素的个数;

Set常用的方法:

  • add(value):添加某个元素,返回Set对象本身;
  • delete(value):从set中删除和这个值相等的元素,返回boolean类型;
  • has(value):判断set中是否存在某个元素,返回boolean类型;
  • clear():清空set中所有的元素,没有返回值;
  • forEach(callback, [, thisArg]):通过forEach遍历set;
  • 另外Set是支持for of的遍历的。
// 10, 20, 40, 333
// 1.创建Set结构
const set = new Set()
set.add(10)
set.add(20)
set.add(40)
set.add(333)

set.add(10)

// 2.添加对象时特别注意:
set.add({})
set.add({})

const obj = {}
set.add(obj)
set.add(obj)

// console.log(set)

// 3.对数组去重(去除重复的元素)
const arr = [33, 10, 26, 30, 33, 26]
// const newArr = []
// for (const item of arr) {
// if (newArr.indexOf(item) !== -1) {
// newArr.push(item)
// }
// }

const arrSet = new Set(arr)
// const newArr = Array.from(arrSet)
// const newArr = [...arrSet]
// console.log(newArr)

// 4.size属性
console.log(arrSet.size)

// 5.Set的方法
// add
arrSet.add(100)
console.log(arrSet)

// delete
arrSet.delete(33)
console.log(arrSet)

// has
console.log(arrSet.has(100))

// clear
// arrSet.clear()
console.log(arrSet)

// 6.对Set进行遍历
arrSet.forEach(item => {
console.log(item)
})

for (const item of arrSet) {
console.log(item)
}

WeakSet和Set有什么区别呢?

  • 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
  • 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么GC可以对该对象进行回收;

WeakSet常见的方法:

  • add(value):添加某个元素,返回WeakSet对象本身;
  • delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;
  • has(value):判断WeakSet中是否存在某个元素,返回boolean类型;

注意:WeakSet不能遍历

  • 因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
  • 所以存储到WeakSet中的对象是没办法获取的;
const weakSet = new WeakSet()

// 1.区别一:只能存放对象类型
// TypeError:Invalid value used in weak set
// weakSet.add(10)

// 强引用和弱引用的概念(看图)

// 2.区别二:对对象是一个弱引用
let obj = {
name: "js"
}

// weakSet.add(obj)

const set = new Set()
// 建立的是强引用
set.add(obj)

// 建立的是弱引用
weakSet.add(obj)

// 3.WeakSet的应用场景
const personSet = new WeakSet()
class Person {
constructor() {
personSet.add(this)
}

running() {
if (!personSet.has(this)) {
throw new Error("不能通过非构造方法创建出来的对象调用running方法")
}
console.log("running~", this)
}
}

let p = new Person()
p.running()
p = null

p.running.call({name: "js"})

Map、WeakMap

数据结构Map,用于存储映射关系。

Map常见的属性:

size:返回Map中元素的个数;

Map常见的方法:

  • set(key, value):在Map中添加key、value,并且返回整个Map对象;
  • get(key):根据key获取Map中的value;
  • has(key):判断是否包括某一个key,返回Boolean类型;
  • delete(key):根据key删除一个键值对,返回Boolean类型;
  • clear():清空所有的元素;
  • forEach(callback, [, thisArg]):通过forEach遍历Map;
  • Map也可以通过for of进行遍历。
// 1.JavaScript中对象中是不能使用对象来作为key的
const obj1 = { name: "js" }
const obj2 = { name: "JavaScript" }

const info = {
[obj1]: "aaa",
[obj2]: "bbb"
}

console.log(info)

// 2.Map就是允许我们对象类型来作为key的
// 构造方法的使用
const map = new Map()
map.set(obj1, "aaa")
map.set(obj2, "bbb")
map.set(1, "ccc")
console.log(map)

const map2 = new Map([[obj1, "aaa"], [obj2, "bbb"], [2, "ddd"]])
console.log(map2)

// 3.常见的属性和方法
console.log(map2.size)

// set
map2.set("js", "eee")
console.log(map2)

// get(key)
console.log(map2.get("js"))

// has(key)
console.log(map2.has("js"))

// delete(key)
map2.delete("js")
console.log(map2)

// clear
// map2.clear()
// console.log(map2)

// 4.遍历map
map2.forEach((item, key) => {
console.log(item, key)
})

for (const item of map2) {
console.log(item[0], item[1])
}

for (const [key, value] of map2) {
console.log(key, value)
}

WeakMap和Map有什么区别呢?

  • 区别一:WeakMap的key只能使用对象,不接受其他的类型作为key;
  • 区别二:WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;

WeakMap常见的方法有四个:

  • set(key, value):在Map中添加key、value,并且返回整个Map对象;
  • get(key):根据key获取Map中的value;
  • has(key):判断是否包括某一个key,返回Boolean类型;
  • delete(key):根据key删除一个键值对,返回Boolean类型;
  • 注意:WeakMap也是不能遍历的。
const obj = {name: "obj1"}
// 1.WeakMap和Map的区别二:
const map = new Map()
map.set(obj, "aaa")

const weakMap = new WeakMap()
weakMap.set(obj, "aaa")

// 2.区别一: 不能使用基本数据类型
// weakMap.set(1, "ccc")

// 3.常见方法
// get方法
console.log(weakMap.get(obj))

// has方法
console.log(weakMap.has(obj))

// delete方法
console.log(weakMap.delete(obj))
// WeakMap { <items unknown> }
console.log(weakMap)
// 应用场景(vue3响应式原理)
const obj1 = {
name: "js",
age: 18
}

function obj1NameFn1() {
console.log("obj1NameFn1被执行")
}

function obj1NameFn2() {
console.log("obj1NameFn2被执行")
}

function obj1AgeFn1() {
console.log("obj1AgeFn1")
}

function obj1AgeFn2() {
console.log("obj1AgeFn2")
}

const obj2 = {
name: "JavaScript",
height: 1.88,
address: "厦门"
}

function obj2NameFn1() {
console.log("obj1NameFn1被执行")
}

function obj2NameFn2() {
console.log("obj1NameFn2被执行")
}

// 1.创建WeakMap
const weakMap = new WeakMap()

// 2.收集依赖结构
// 2.1.对obj1收集的数据结构
const obj1Map = new Map()
obj1Map.set("name", [obj1NameFn1, obj1NameFn2])
obj1Map.set("age", [obj1AgeFn1, obj1AgeFn2])
weakMap.set(obj1, obj1Map)

// 2.2.对obj2收集的数据结构
const obj2Map = new Map()
obj2Map.set("name", [obj2NameFn1, obj2NameFn2])
weakMap.set(obj2, obj2Map)

// 3.如果obj1.name发生了改变
// Proxy/Object.defineProperty
obj1.name = "james"
const targetMap = weakMap.get(obj1)
const fns = targetMap.get("name")
fns.forEach(item => item())

Array Includes

const names = ["abc", "cba", "nba", "mba", NaN]

if (names.indexOf("cba") !== -1) {
console.log("包含abc元素")
}

// ES7 ES2016
if (names.includes("cba")) {
console.log("包含abc元素")
}

if (names.indexOf(NaN) !== -1) {
console.log("包含NaN")
}

if (names.includes(NaN)) {
console.log("包含NaN")
}

运算符扩展

Nullish coalescing Operator(空值处理)

表达式在 ?? 的左侧,它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。

??本质上是逻辑运算,它与其他两个逻辑运算符&&||有一个优先级问题,同时使用时需要加入括号以表明优先级。

const user = {
u1: 0,
u2: false,
u3: null,
u4: undefined,
u5: '',
}
const u1 = user.u1 ?? '用户1' // 0
const u2 = user.u2 ?? '用户2' // false
const u3 = user.u3 ?? '用户3' // 用户3
const u4 = user.u4 ?? '用户4' // 用户4
const u5 = user.u5 ?? '用户5' // ''

optional chaining(可选链)

article?.userId // 等效于 article && article.userIde

// 静态属性
const c = foo?.bar?.c;
// 动态属性,数组属性
const a = obj?.[key].a;
// 方法调用
obj.fn?.(); // 如果fn不是函数,那么也是会报错

以下写法是禁止的,会报错。

// 构造函数
new a?.()
new a?.b()

// 链判断运算符的右侧有模板字符串
a?.`{b}`
a?.b`{c}`

// 链判断运算符的左侧是 super
super?.()
super?.foo

// 链运算符用于赋值运算符左侧
a?.b = c

右侧不得为十进制数值。如果?.后面紧跟一个十进制数字,那么?.不再被看成是一个完整的运算符,而会按照三元运算符进行处理。

logical assignment operators 逻辑赋值运算符

// 或赋值运算符
x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

它们的一个用途是,为变量或属性设置默认值。

// 老的写法
user.id = user.id || 1;

// 新的写法
user.id ||= 1;

指数(乘方) exponentiation运算符

const result1 = Math.pow(3, 3)
// ES7: **
const result2 = 3 ** 3
console.log(result1, result2)

Object values和entries

const obj = {
name: "js",
age: 18
}

console.log(Object.keys(obj))
console.log(Object.values(obj))

console.log(Object.values(["abc", "cba", "nba"]))
console.log(Object.values("abc"))

// [ 'name', 'age' ]
// [ 'js', 18 ]
// [ 'abc', 'cba', 'nba' ]
// [ 'a', 'b', 'c' ]
const obj = {
name: "js",
age: 18
}

console.log(Object.entries(obj))
const objEntries = Object.entries(obj)
objEntries.forEach(item => {
console.log(item[0], item[1])
})

console.log(Object.entries(["abc", "cba", "nba"]))
console.log(Object.entries("abc"))

// [ [ 'name', 'js' ], [ 'age', 18 ] ]
// name js
// age 18
// [ [ '0', 'abc' ], [ '1', 'cba' ], [ '2', 'nba' ] ]
// [ [ '0', 'a' ], [ '1', 'b' ], [ '2', 'c' ] ]

String Padding

某些字符串我们需要对其进行前后的填充,来实现某种格式化效果,ES8中增加了 padStart 和 padEnd 方法,分 别是对字符串的首尾进行填充的。

const message = "Hello World"

const newMessage = message.padStart(15, "*").padEnd(20, "-")
console.log(newMessage)

// 案例
const cardNumber = "3502033213543564542554"
const lastFourCard = cardNumber.slice(-4)
const finalCard = lastFourCard.padStart(cardNumber.length, "*")
console.log(finalCard)

flat flatMap

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。

flatMap() 方法首先使用映射函数映射每个元素,然后将结果压缩成一个新数组。

  • 注意一:flatMap是先进行map操作,再做flat的操作;
  • 注意二:flatMap中的flat相当于深度为1;
// 1.flat的使用
const nums = [10, 20, [2, 9], [[30, 40], [10, 45]], 78, [55, 88]]
const newNums = nums.flat()
console.log(newNums)

const newNums2 = nums.flat(2)
console.log(newNums2)

// 2.flatMap的使用
const nums2 = [10, 20, 30]
const newNums3 = nums2.flatMap(item => {
return item * 2
})
const newNums4 = nums2.map(item => {
return item * 2
})

console.log(newNums3)
console.log(newNums4)

// 3.flatMap的应用场景
const messages = ["Hello World", "hello js", "my name is javascript"]
const words = messages.flatMap(item => {
return item.split(" ")
})

console.log(words)

trimStart trimEnd

去除一个字符串首尾的空格,我们可以通过trim方法,如果单独去除前面或者后面呢? ES10中给我们提供了trimStart和trimEnd;

const message = "    Hello World    "

console.log(message.trim())
console.log(message.trimStart())
console.log(message.trimEnd())