ES6~ES13新特性(二)
解构赋值
var names = ["abc", "cba", undefined, "nba", "mba"]
// 1.数组的解构
var name1 = names[0]
var name2 = names[1]
var name3 = names[2]
// 1.1. 基本使用
var [name1, name2, name3] = names
console.log(name1, name2, name3)
// 1.2. 顺序问题: 严格的顺序
var [name1, , name3] = names
console.log(name1, name3)
// 1.3. 解构出数组
var [name1, name2, ...newNames] = names
console.log(name1, name2, newNames)
// 1.4. 解构的默认值
var [name1, name2, name3 = "default"] = names
console.log(name1, name2, name3)
// 2.对象的解构
var obj = { name: "why", age: 18, height: 1.88 }
var name = obj.name
var age = obj.age
var height = obj.height
// 2.1. 基本使用
var { name, age, height } = obj
console.log(name, age, height)
// 2.2. 顺序问题: 对象的解构是没有顺序, 根据key解构
var { height, name, age } = obj
console.log(name, age, height)
// 2.3. 对变量进行重命名
var { height: wHeight, name: wName, age: wAge } = obj
console.log(wName, wAge, wHeight)
// 2.4. 默认值
var {
height: wHeight,
name: wName,
age: wAge,
address: wAddress = "中国"
} = obj
console.log(wName, wAge, wHeight, wAddress)
// 2.5. 对象的剩余内容
var {
name,
age,
...newObj
} = obj
console.log(newObj)
// 应用: 在函数中(其他类似的地方)
function getPosition({ x, y }) {
console.log(x, y)
}
getPosition({ x: 10, y: 20 })
getPosition({ x: 25, y: 35 })
function foo(num) {}
foo(123)
字符串模板基本使用
在ES6之前,如果我们想要将字符串和一些动态的变量(标识符)拼接到一起,是非常麻烦和丑陋的(ugly )。
ES6允许我们使用字符串模板来嵌入 JS的变量或者表达式来进行拼接:
- 首先,我们会使用``符号来编写字符串,称之为模板字符串 ;
其次,在模板字符串中,我们可以通过 ${expression} 来嵌入动态的内容;
标签模板字符串使用
- 模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals )。
- 我们一起来看一个普通的 JavaScript的函数:
- 如果我们使用标签模板字符串,并且在调用的时候插入其他的变量:
- 模板字符串被拆分了;
- 第一个元素是数组,是被模块字符串拆分的字符串组合;
- 后面的元素是一个个模块字符串传入的内容;
标签模板字符串的应用场景
- 比如React的styled-components 库,就是使用标签模板字符串,并且可以传入js组件的变量。
函数的默认参数
在ES6之前,我们编写的函数参数是没有默认值的,所以我们在编写函数时,如果有下面的需求:
- 传入了参数,那么使用传入的参数;
- 没有传入参数,那么使用一个默认值;
而在ES6中,我们允许给函数一个默认值:
- 当传入的参数不为
undefined
(注意,可以为null)时,赋值为默认参数。
- 当传入的参数不为
// 注意: 默认参数是不会对null进行处理的
function foo(arg1 = "我是默认值", arg2 = "我也是默认值") {
// 1.两种写法不严谨,当arg1为false,0,""时会有默认值
// 默认值写法一:
// arg1 = arg1 ? arg1: "我是默认值"
// 默认值写法二:
// arg1 = arg1 || "我是默认值"
// 2.严谨的写法
// 三元运算符
// arg1 = (arg1 === undefined ) ? "我是默认值": arg1
// ES6之后新增语法: ??
// arg1 = arg1 ?? "我是默认值"
// 3.简便的写法: 默认参数
console.log(arg1)
}
foo(123, 321)
foo()
foo(0)
foo("")
foo(false)
foo(null)
foo(undefined)
- 默认值也可以和解构一起来使用:
// 2.函数的默认值是一个对象
function foo(obj = { name: "why", age: 18 }) {
console.log(obj.name, obj.age)
}
//function foo({ name, age } = { name: "why", age: 18 }) {
//console.log(name, age)
//}
function foo({ name = "why", age = 18 } = {}) {
console.log(name, age)
}
foo()
另外参数的默认值我们通常会将其放到最后(在很多语言中,如果不放到最后其实会报错的):
- 但是JavaScript 允许不将其放到最后,但是意味着还是会按照顺序来匹配;
另外默认值会改变函数的 length的个数,默认值以及后面的参数都不计算在length 之内了。
// 1.注意一: 有默认参数的形参尽量写到后面
// 2.有默认参数的形参, 是不会计算在length之内(并且后面所有的参数都不会计算在length之内)
// 3.剩余参数也是放到后面(默认参数放到剩余参数的前面)
function foo(age, name = "why", ...args) {
console.log(name, age, args)
}
foo(18, "abc", "cba", "nba")
console.log(foo.length)
函数的剩余参数
- ES6中引用了rest parameter,可以将不定数量的参数放入到一个数组中:
- 如果最后一个参数是 ... 为前缀的,那么它会将剩余的参数放到该参数中,并且作为一个数组;
那么剩余参数和arguments 有什么区别呢?
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参 ;
- arguments对象不是一个真正的数组,而 rest参数是一个真正的数组,可以进行数组的所有操作;
- arguments是早期的ECMAScript 中为了方便去获取所有的参数提供的一个数据结构,而rest 参数是ES6 中提供 并且希望以此 来替代arguments 的;
注意:剩余参数必须放到最后一个位置,否则会报错。
函数箭头函数
- 在前面我们已经学习了箭头函数的用法,这里进行一些补充:
- 箭头函数是没有显式原型 prototype的,所以不能作为构造函数,使用 new来创建对象;
- 箭头函数也不绑定this 、arguments 、super 参数;
展开语法
展开语法(Spread syntax):
- 可以在函数调用/数组构造时,将数组表达式或者string 在语法层面展开;
- 还可以在构造字面量对象时, 将对象表达式按key -value的方式展开;
展开语法的场景:
在函数调用时使用;
- 在函数调用,使用展开对象时,对象必须是一个可迭代对象,否则会报错。
- 可迭代对象: 数组/string/arguments
在数组构造时使用;
在构建对象字面量时,也可以使用展开运算符,这个是在ES2018 ( ES9)中添加的新特性;
- 这里不要求对象是一个可迭代对象
注意:展开运算符其实是一种浅拷贝;
// 1.基本演练
// ES6
const names = ["abc", "cba", "nba", "mba"]
const str = "Hello"
//在数组构造时使用;
const newNames = [...names, "aaa", "bbb"]
console.log(newNames)
function foo(name1, name2, ...args) {
console.log(name1, name2, args)
}
//在函数调用时使用
foo(...names)
foo(...str)
// 不可以这样来使用
// foo(...obj) // 在函数的调用时, 用展开运算符, 将对应的展开数据, 进行迭代
// 可迭代对象: 数组/string/arguments
// ES9(ES2018)
const obj = {
name: "why",
age: 18
}
const info = {
...obj,
height: 1.88,
address: "广州市"
}
console.log(info)
浅拷贝和深拷贝的区别
- 浅拷贝
- 深拷贝
数值的表示
- 在ES6 中规范了二进制和八进制的写法:
- 另外在ES2021 新增特性:数字过长时,可以使用_ 作为连接符
Symbol的基本使用
Symbol是什么呢?
- Symbol 是ES6 中新增的一个基本数据类型,翻译为符号。
那么为什么需要Symbol 呢?
- 在ES6 之前,对象的属性名都是字符串形式,那么很容易造成属性名的冲突;即使是其他类型,也会转成字符串模式。
- 比如原来有一个对象,我们希望在其中 添加一个新的属性和值,但是我们在不确定它原来内部有什么内容的情况下,很易 造成冲突,从而覆盖掉它内部的某个属性;
- 比如我们前面在讲apply 、call 、bind 实现时,我们有给其中 添加一个fn属性,那么如果它内部原来已经有了fn属性了呢?
- 比如开发中我们使用混入,那么混入中出现了同名的属性,必然有一个会被覆盖掉;
Symbol就是为了解决上面的问题,用来生成一个独一无二的值。
- Symbol值是通过Symbol 函数来生成的,生成后可以作为属性名 ;
- 也就是在ES6 中,对象的属性名可以使用字符串 ,也可以使用Symbol 值 ;
Symbol即使多次创建值,它们也是不同的:Symbol 函数执行后每次创建出来的值都是独一无二的;
我们也可以在创建Symbol 值的时候传入一个描述 description:这个是ES2019(ES10 )新增的特性;
Symbol是独一无二的
// 3.1.Symbol函数直接生成的值, 都是独一无二
const s3 = Symbol("ccc")
console.log(s3.description) //ccc
const s4 = Symbol(s3.description)
console.log(s3 === s4) //false
Symbol作为属性名
- 我们通常会使用Symbol 在对象中表示唯一的属性名:
const s1 = Symbol() // aaa
const s2 = Symbol() // bbb
// 1.使用方法一
const obj = {
name: "why",
age: 18,
[s1]: "aaa",
[s2]: "bbb"
}
// 2.使用方法二
const obj1 = {}
obj1[s1] = "aaa"
obj2[s2] = "bbb"
// 2.使用方法三
const obj2 = {}
Object.defineProperty(obj, s1, {
value: "aaa"
})
获取symbol对应的key
- 无法通过
Object.keys
获取symbol
类型的key,只能通过Object.getOwnPropertySymbols()
获取
const s1 = Symbol() // aaa
const s2 = Symbol() // bbb
// 1.加入对象中
const obj = {
name: "why",
age: 18,
[s1]: "aaa",
[s2]: "bbb"
}
// 2.获取symbol对应的key
//console.log(Object.keys(obj)) //无法获取
console.log(Object.getOwnPropertySymbols(obj))
const symbolKeys = Object.getOwnPropertySymbols(obj)
for (const key of symbolKeys) {
console.log(obj[key])
}
相同值的Symbol
- 前面我们讲Symbol 的目的是为了创建一个独一无二的值,那么如果我们现在就是想创建相同的Symbol 应该怎么来做呢?
- 我们可以使用Symbol.for 方法 来做到这一点;
- 并且我们可以通过Symbol.keyFor 方法来获取对应的 key;
// 3.2. 如果相同的key, 通过Symbol.for可以生成相同的Symbol值
const s5 = Symbol.for("ddd")
const s6 = Symbol.for("ddd")
console.log(s5 === s6)
// 获取传入的key
console.log(Symbol.keyFor(s5))
Set的基本使用
在ES6之前,我们存储数据的结构主要有两种:数组、对象 。
- 在ES6中新增了另外两种数据结构: Set、Map ,以及它们的另外形式WeakSet 、WeakMap 。
Set是一个新增的数据结构,可以用来保存数据,类似于数组,
- 但是和数组的区别是 元素不能重复 。
创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式):
我们可以发现Set中存放的元素是不会重复的,那么 Set有一个非常常用的功能就是给数组去重 。
// 1.创建Set
const set = new Set()
console.log(set)
// 2.添加元素
set.add(10)
set.add(22)
set.add(35)
set.add(22)
console.log(set)
const info = {}
const obj = {name: "obj"}
set.add(info)
set.add(obj)
set.add(obj)
console.log(set)
// 3.应用场景: 数组的去重
const names = ["abc", "cba", "nba", "cba", "nba"]
// const newNames = []
// for (const item of names) {
// if (!newNames.includes(item)) {
// newNames.push(item)
// }
// }
// console.log(newNames)
const newNamesSet = new Set(names)
const newNames = Array.from(newNamesSet)
console.log(newNames)
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的遍历的。
// 1.创建Set
const set = new Set()
const info = {}
const obj = {name: "obj"}
set.add(info)
set.add(obj)
// 4.Set的其他属性和方法
// 属性
console.log(set.size)
// 方法
// 4.1. add方法
set.add(100)
console.log(set)
// 4.2. delete方法
set.delete(obj)
console.log(set)
// 4.3. has方法
console.log(set.has(info))
// 4.4. clear方法
// set.clear()
// console.log(set)
// 4.5. forEach
set.forEach(item => console.log(item))
// 5.set支持for...of
for (const item of set) {
console.log(item)
}
WeakSet使用
- 和Set 类似的另外一个数据结构称之为WeakSet ,也是内部元素不能重复的数据结构。
- 那么和Set 有什么区别呢?
- 区别一:WeakSet中只能存放对象类型,不能存放基本数据类型;
- 区别二:WeakSet对元素的引用是弱引用,如果没有其他引用对某个对象进行引用,那么 GC可以对该对象进行回收;
// 2.WeakSet的用法
// 2.1.和Set的区别一: 只能存放对象类型
const weakSet = new WeakSet()
weakSet.add(obj1)
weakSet.add(obj2)
weakSet.add(obj3)
// 2.2.和Set的区别二: 对对象的引用都是弱引用
WeakSet常见的方法:
- add(value):添加某个元素,返回WeakSet对象本身;
- delete(value):从WeakSet 中删除和这个值相等的元素,返回boolean 类型;
- has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
WeakSet的应用
注意:WeakSet 不能遍历
- 因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁。
- 所以存储到WeakSet中的对象是没办法获取的;
那么这个东西有什么用呢?
- 事实上这个问题并不好回答,我们来使用一个 Stack Overflow上的答案;
- 使用弱引用WeakSet来添加Person的实例,如果发现不是通过new Person调用的,则报错
- 因为使用了弱引用,当Person的实例销毁时,这里的WeakSet里的引用也会自动销毁。
Map的基本使用
另外一个新增的数据结构是Map ,用于 存储映射关系 。
但是我们可能会想,在之前我们可以使用对象来存储映射关系,他们有什么区别 呢?
- 事实上我们对象存储映射关系只能用字符串( ES6新增了 Symbol)作为属性名(key );
- 某些情况下我们可能希望通过其他类型作为key ,比如对象,这个时候 会自动将对象转成字符串来作为key ;
**那么我们就可以使用Map
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进行遍历。
const info = { name: "why" }
const info2 = { age: 18 }
// 1.对象类型的局限性: 不可以使用复杂类型作为key
// const obj = {
// address: "北京市",
// [info]: "哈哈哈",
// [info2]: "呵呵呵"
// }
// console.log(obj)
// 2.Map映射类型
const map = new Map()
map.set(info, "aaaa")
map.set(info2, "bbbb")
console.log(map)
// 3.Map的常见属性和方法
// console.log(map.size)
// 3.1. set方法, 设置内容
map.set(info, "cccc")
console.log(map)
// 3.2. get方法, 获取内容
// console.log(map.get(info))
// 3.3. delete方法, 删除内容
// map.delete(info)
// console.log(map)
// 3.4. has方法, 判断内容
// console.log(map.has(info2))
// 3.5. clear方法, 清空内容
// map.clear()
// console.log(map)
// 3.6. forEach方法
// map.forEach(item => console.log(item))
// 4.for...of遍历
for (const item of map) {
const [key, value] = item
console.log(key, value)
}
WeakMap的使用
- 和Map 类型的另外一个数据结构称之为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的应用
注意:WeakMap 也是不能遍历的
- 没有forEach 方法,也不支持通过 for of的方式进行遍历;
那么我们的WeakMap 有什么作用呢?(后续专门讲解)
ES6其他知识点说明
- 事实上ES6(ES2015 )是一次非常大的版本更新,所以里面重要的特性非常多:
- 除了前面讲到的特性外还有很多其他特性;
- Proxy、Reflect ,我们会在后续专门进行学习。
- 并且会利用Proxy、Reflect来讲解Vue3 的响应式原理;
- Promise,用于处理异步的解决方案
- 后续会详细学习;
- 并且会学习如何手写Promise;
- ES Module模块化开发:
- 从ES6 开发,JavaScript 可以进行原生的模块化开发;
- 这部分内容会在工程化部分学习;
- 包括其他模块化方案:CommonJS 、AMD 、CMD 等方案;