Skip to main content

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)

浅拷贝和深拷贝的区别

  • 浅拷贝

03_浅拷贝-复杂类型

  • 深拷贝

04_深拷贝-JSON做法

数值的表示

  • 在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 等方案;