关于es6的新增语法,你知道哪些?

184 阅读5分钟

1.类

  • 使用class关键字
  • 构造函数,原型链的语法糖

定义

class Person {

}

构造方法

  • constructor
  • 只能有一个
class Person {
  // 构造方法,只能有一个
  constructor (name, age) {
    this.name = name
    this.age = age
  }
}

var p1 = new Person('zsf', 18)

console.log(p1)

其它方法

  • 实例方法
  • 访问器
  • 静态方法
class Person {
  // 构造方法,只能有一个
  constructor (name, age) {
    this.name = name
    this.age = age
    this._address = '广州'
  }

  eating () {
    console.log('eat')
  }
  // 类的访问器
  get_address () {
    return this._address
  }

  // 类的静态方法
  static createPerson () {
    console.log('静态方法')
  }
}

var p1 = new Person('zsf', 18)

console.log(p1)
Person.createPerson()

继承

extends

super

在子类构造方法中使用this或者return默认对象之前,必须通过super调用父类的构造方法

super作用:

  • 使用父类构造方法
  • 继承式重写父类方法(复用父类逻辑)
class Person {
  // 构造方法,只能有一个
  constructor (name, age) {
    this.name = name
    this.age = age
  }
  eating () {
    console.log(1)
  }
}

class Student extends Person {
  constructor (name, age, sno) {
    // 使用父类构造方法
    super(name, age)
    this.sno = sno
  }
  // 可以重写父类方法
  eating () {
    super.eating()
    console.log(2)
  }
}

var stu = new Student('zsf', 18, 102)

stu.eating()// 1 2

继承内置类

class MyArray extends Array {
  fiestItem () {
    return this[0]
  }
}

var arr = new MyArray(1,2,3)
console.log(arr.fiestItem())

混入mixin

js中只能单继承,但又想复用除父类外其它类代码,怎么办?

  • 方式1-借助函数(混入属性、和带参不好做)
  • react高阶组件
class Runner {
  running () {

  }
}
function mixinRunner(BaseClass) {
  class NewClass extends BaseClass {
    running () {
      console.log('run')
    }
  }

  return NewClass
}

var NewStudent = mixinRunner(Student)
var ns = new NewStudent()
ns.running()

2.多态

传统意义多态条件:

  • 有继承
  • 有重写
  • 有父类引用指向子类对象

但js多态条件

  • 不同的数据类型
  • 进行同一操作
  • 表现出不同的行为
function calcArea(foo) {
 console.log(foo.getArea())
}

var obj1 = {
  name: 'zsf',
  getArea: function () {
    return 1000
  }
}

class Person {
  getArea () {
    return 100
  }
}

var p = new Person()

calcArea(obj1)
calcArea(p)

这也是js中多态的体现

function sum(m, n) {
  return m + n
}

sum(20, 30)
sum('ab', 'c')

3.es6新增

对象的字面量增强写法

  • 属性简写
  • 方法简写
  • 计算属性
var name1 = 'zsf'
var age = 18
var address = '广州市'

var obj = {
  // 属性的简写
  name1,
  age,
  address,
  // 方法的简写
  bar () {

  },

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

  [name1 + 123]: 'hhh'
}

解构

  • 数组的解构
  • 对象的解构

数组

var names = [1, 2, 3]
var [item1, item2, item3] = names
console.log(item1, item2, item3)// 1 2 3

对象

var obj = {
  name1: 'zsf',
  age: '18',
  height: 1.88
}

var {name1, age, height} = obj
console.log(name1, age, height)// zsf 18 1.88
// 改名
var { age: newAge } = obj
console.log(newAge)// 18

应用场景

对函数的参数解构

var obj = {
  name1: 'zsf',
  age: '18'
}

function foo({name1, age}) {
  console.log(name1, age)
}

foo(obj)// zsf 18

let/const

注意事项

注意1

const声明的变量的值不能修改

要是传递的是引用类型内存地址,比如对象),可以通过引用找到对象,去修改对象内部的属性,这是可以

const obj = {
  name1: 'zsf',
  age: '18'
}

obj = {}// 报错
obj.name1 = 'hhh' // 可以

console.log(obj.name1) // hhh

注意2:

let/const 不能定义同一变量,var可以

不能作用域提升

如果使用let/const声明的变量,在声明之前访问就会报错

console.log(foo)
let foo = 'foo'

那么是不是意味着foo变量只有在代码执行阶段才会创建呢?

不是。已经创建了,但是不能访问

一个变量声明之前能被访问,我们称之为作用域提升

所以let/const不能作用域提升

与window之间的关系

  • 在全局通过var 来声明一个变量,事实上会在window上添加一个属性
  • 但是let/const不会给window上添加任何属性的

那let/const声明的变量保存在哪里呢?

放在一个variableMap的数据结构里,已经不完全等于window

var、let、const的选择

varletconst
作用域提升明确知道变量后续会被赋值在使用优先使用
没有块级作用域保证数据的安全性
window全局对象
不要使用了

块级作用域

注意:只对let、const、class、function声明的类型有效

{
  var v1 = 'v1'
  let foo = 'foo'
  console.log(foo) // foo
}
console.log(v1) //v1
console.log(foo) //找不到

但是function特殊,不同浏览器有不同实现(大部分浏览器为了兼容以前的代码,让function是没有块级作用域的,但要是有浏览器只支持es6,那function也是有块级作用域的

常见块级作用域

  • for () {}
  • if () {}
  • switch

应用

没有块级作用域之前

<button>1</button>
  <button>2</button>
  <button>3</button>
  <button>4</button>
  <button>5</button>
const btns = document.getElementsByTagName('button')

for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log('第' + i + '个被点击')
  }
}

不论点击哪个按钮,都是打印“第5个被点击”

因为点击处理函数的作用域没有i,他要去上层作用域找i,也就是去全局作用域里找,而全局作用域的i在不断地进行++的时候已经变成5

没有块级作用域之前,是这样解决的

const btns = document.getElementsByTagName('button')

for (var i = 0; i < btns.length; i++) {
  (function (n) {
    btns[i].onclick = function () {
      console.log('第' + n + '个被点击')
    }
  })(i)
}

通过函数多形成了一层作用域暂存了i的值,就不用去全局作用域里找了

块级作用域出来之后

const btns = document.getElementsByTagName('button')

for (let i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log('第' + i + '个被点击')
  }
}

let使for具有块级作用域,不会去全局作用域里找i

暂时性死区

使用let、const声明的变量。在声明之前,变量都是不可以访问的,这种现象叫暂时性死区

模板字符串

es6之前,变量和字符串拼接时,书写不方便可读性差

  • 可以计算
  • 可以放函数
const name1 = 'zsf'
const age = 18
function doubleAge() {
  return age * 2
}
const msg = `我是 ${ name1 }, ${ age + 2 } 岁, ${ doubleAge() }` 
console.log(msg)

另外的用法

标签模板字符串

  • 第一个参数是字符串中整个字符串(不包含模板字符串的内容),只是被切成多块,放到数组
  • 第二参数是字符串中,第一个${}
function foo(m, n) {
  console.log(m, n)
}

const name1 = 'zsf'
const age = 18
// 标签模板字符串
foo`hello ${ name1 } hh ${ age } h`

应用场景:

我们自己是很少这样写,但是在react中,遵循all(html css) in js, 有个styled-component库,会用到标签模板字符串

函数

参数的默认值

es6之前,参数的默认值是这样的

function foo(m, n) {
  m = m || 'zsf'
  n = n || 'hhh'
  console.log(m, n)
}

foo(0, '')// zsf hhh

缺点:

  • 可读性差
  • 有bug,要是传0和空字符串

es6后可以这样写

function foo(m = 'zsf', n = 'hhh') {
  console.log(m, n)
}

foo(0, '')// 0 ''

注意:

函数有默认值的形参,不算入length属性

对象解构也可以当默认值

function foo({ name1, age } = { name1: 'zsf', age: 18 }) {
  console.log(name1, age)
}

foo()

剩余参数

rest参数必须放参数最后

function foo(m, n, ...args) {
  console.log(m, n)
  console.log(args)
}

foo(1, 2, 3, 4)// 1 2 [3, 4]

与arguments的区别

  • arguments对象包含了所有实参
  • arguments对象是个伪数组rest参数是一个真数组
  • es6就希望rest参数来替代arguments

箭头函数

箭头函数是没有显式原型的,不能作为构造函数

建议

有默认值的形参最后放到最后

展开语法

使用场景:

  • 函数调用时使用
  • 数组构造时使用
  • 构建对象字面量时使用es9新增
const nums = [1, 2, 3]
const name1 = 'zsf'


function foo(x, y, z) {
  console.log(x, y, z)
}
// 函数调用时
foo(...nums)// 1 2 3
foo(...name1)// z s f

// 构造数组时
const newNums = [...nums]
console.log(newNums)// 1 2 3

// es9构建对象字面量时
const info = {name: 'zsf', age: 18}
const obj = {...info, address: '广州市'}
console.log(obj)// {name: 'zsf', age: 18, address: '广州市'}


本质

展开运算符进行的是浅拷贝

const info = {
  name: 'why',
  friend: {name: 'kobe'}
}

const obj = {...info, name: 'coderwhy'}
obj.friend.name = 'james'

console.log(info.friend.name)// james

改的是obj的,但是info的也被改了

浅拷贝内存图

image-20220307094108106.png

深拷贝会重新开辟一块新内存,防止原数据被修改

image-20220307095033334.png

数值

  • ob 2进制
  • 0o 8进制
  • ox 16进制
  • 大数值连接符_(100_000_000)

Symbol

没有Symbol的弊端

为什么需要Symbol呢?

  • es6之前,对象的属性名都是字符串形式,容易造成属性名的冲突
  • 当我们希望给一个对象添加一个新属性和值的时候,但是不确定它原来内部有什么内容的情况下,很容易造成属性名冲突,从而覆盖掉它内部的某个属性

新增数据类型,es6之后,对象的属性名可以是字符串,也可以是Symbol值

基本使用

它是一个函数,用于生成唯一的值

const s1 = Symbol()
const s2 = Symbol()

console.log(s1 === s2)// false

作为对象的key

  • 定义对象字面量时使用
  • 新增属性
// 写法1 定义对象字面量时使用
const obj = {
  [s1]: 'zsf',
  [s2]: 'hhh'
}

//写法2 新增属性

obj[s3] = 'nba'
const s4 = Symbol()
Object.defineProperty(obj, s4, {
  enumerable: true,
  configurable: true,
  writable: true,
  value: 'cba'
})
// 获取 不能通过.语法获取
console.log(obj[s1], obj[s2], obj[s3], obj[s4])

遍历所有Symbol

  • 使用Symbol作为key的属性名,在遍历Object.keys等中是获取不到这些Symbol值的
  • 可以使用Object.getOwnPropertySymbols()获取所有的Symbol值

补充

Symbol也可以生成相同的key

// Symbol也可以生成相同的key
const sa = Symbol.for('aaa') 
const sb = Symbol.for('aaa')
console.log(sa === sb) // true

Set

es6之前,js存储数据的结构主要有2种:数组、对象

es6新增了2种数据结构:Set、Map

另外形式WeakSet、WeakMap

基本使用

Set类似于数组,但元素不能重复

通过构造函数Set创建

const set = new Set()
set.add(10)
set.add(11)
set.add(12)

set.add(10)
// 地址不一样,也是不同元素,注意
set.add({})
set.add({})

console.log(set)// 10 11 12 {} {}

应用场景

  • 不希望有重复数据
  • 数组去重
const arr = [33, 10, 26, 30, 33, 26]

const arrSet = new Set(arr)
// const newArr = Array.from(arrSet)
// 或者,Set也支持展开运算符
const newArr = [...arrSet]
console.log(newArr)

常见属性、方法

  • size 返回元素个数
  • add() 添加某个元素
  • delete() 删除元素
  • has() 是否包含某元素
  • clear() 清空
  • forEach() 遍历

WeakSet

与Set区别:

  • 只能存放对象类型
  • 对元素的引用是弱引用,若没有其它引用对该对象进行引用,那么GC可以对该对象进行回收(GC不认识弱引用,当没有强引用指向某个对象时,就算有弱引用指向该对象,GC也会回收该对象

set对象对某个对象的引用是强引用

let obj = {name: 'why'}

const set = new Set()
set.add(obj)

image-20220307111319128.png

当执行obj = null,obj对象不会被销毁掉,因为set对象中还有属性对obj强引用

常见方法

  • add()
  • delete()
  • has()

WeakSet不能遍历!

应用场景

不能通过非构造方法创建出来的对象调用类的方法

const personSet = new WeakSet()

class Person{
  constructor () {
    personSet.add(this)
  }

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

const p = new Person()
p.running()

p.running.call({name: 'why'})// 抛出异常

Map

补充:

js中是不能使用对象作为key的,使用对象作为key,会将object转化为字符串,即使多个不同对象,但都会转换成object这一个字符串

Map与对象的区别:

  • Map可以使用对象类型或者其它类型作为key

使用

使用更多的,还是使用对象类型作为key

const obj1 = {name: 'zsf'}
const obj2 = {name: 'hhh'}

const map = new Map()

map.set(obj1, 'aaa')
map.set(obj2, 'bbb')
map.set(1, 'ccc')


console.log(map)

常见属性、方法

  • size
  • set()
  • get()
  • has()
  • delete()
  • clear()
  • forEach()

WeakMap

类似于WeakSet

  • 只能是对象类型作为key
  • 弱引用

常见方法

  • get()
  • set()
  • has()
  • delete()

应用场景

vue3响应式原理

监听对象的改变,并且有某些函数做出对应的响应

如何将函数和对象的属性关联在一起呢?

image-20220307162539840.png

  1. 将obj1的name属性和name属性对应的响应函数(多个放数组)用Map关联起来,map保存
  2. 然后将obj1和这个mapWeakMap关联起来,weakmap保存
  3. obj1.name发生改变时,用weakMap.get(obj1)获取map,再用map.get(name)获取到对应的响应函数数组,然挨个执行
  4. 其它对象属性同理

为什么第五行使用weakMap不用map?

要是哪一天obj1 = null了,weakmap是弱引用,该对象会自动销毁,用map的话,强引用obj1不会被销毁

4.es6转es5

在线转网站:babeljs.io/

阅读源码技巧

  • 代码中写debugger断点,开发者工具一步步执行
  • Bookmarks插件,标记(Ctrl+Alt+k)
  • 注明参数含义

5.es7

Array Includes

es7之前,想判断一个数组中是否包含某个元素,需要通过indexOf获取结果,并且判断是否为-1.

const arr = [1, 2, 3, 4]
if (arr.indexOf(1) !== -1) {
  console.log('包含1')
}

es7使用includes()

第2个参数还可以设置从第几个开始查找

// es7
if (arr.includes(1)) {
  console.log('包含1')
}

与indexOf的区别

对NaN的判断,indexOf判断不出

const arr = [1, 2, 3, 4, NaN]
if (arr.indexOf(NaN) !== -1) {
  console.log('包含NaN')
} else {
  console.log('找不到')// 打印找不到
  
}
// es7
if (arr.includes(NaN)) {
  console.log('包含NaN')// 包含NaN
}

指数运算

const result1 = Math.pow(2, 3)
console.log(result1)// 8

// es7

const result2 = 2 ** 3
console.log(result2) // 8

6.es8

Object.values

es5之前可以通过Object.keys获取一个对象所有的key,在es8中,提供了Object.values来获取所有的value值

const obj = {
  name: 'zsf',
  age: 18
}
console.log(Object.values(obj))

Object.entries

可以获取到一个数组,数组中会存放可枚举属性键值对数组

const obj = {
  name: 'zsf',
  age: 18
}
console.log(Object.entries(obj))// [['name', 'zsf'], ['age', 18]]

String padding

某些字符串需要对其前后进行填充,来实现某种格式化效果

  • padStart
  • padEnd
const msg = 'hello world'
const newMsg =  msg.padStart(15, '*').padEnd(20, '-')
console.log(newMsg)// ****hello world-----

7.es10

flat

数组降维

const nums = [10, 20, [2, 8], [[30, 40], [20,45]], 78]
const newNums= nums.flat()
console.log(newNums)
// [10, 20, 2, 8, Array(2), Array(2), 78]

flatMap()

按照可指定的深度递归遍历数组,并将所有元素遍历所有元素到子数组中的元素合并为一个新数组

  • 先是map操作,再做flat操作
  • 相当于深度为1
const msg = ['hello world', '你好啊,李银河', 'my name is coderwhy']
const words = msg.flatMap(item => {
  return item.split(' ')
})
console.log(words)
// ['hello', 'world', '你好啊,李银河', 'my', 'name', 'is', 'coderwhy']

8.es11

可选链

es11之前,防止某些属性是undefined阻止后续代码的执行,一般是这样做的

const info = {
  name: 'zsf'
}

if (info && info.friend && info.friend.girlFriend) {
  console.log(info.friend.girlFriend)
  
}

console.log('其它代码逻辑') // 不会前面阻止这里的打印


有了可选链,代码更加严谨,不会因为某个属性是undefined而影响后面代码的执行

const info = {
  name: 'zsf'
}

console.log(info.friend?.girlFriend?.name)// undefined


console.log('其它代码逻辑') // 不会前面阻止这里的打印


全局对象

globalThis

在浏览器或者node环境下获取全局对象

console.log(globalThis)// 浏览器: window node: global

9.es12

FinalizationRegistry

需要监听对象被销毁并回调一个函数时,可以用FinalizationRegistery这构造方法(类)

let info = {
  name: 'zsf'
}
const finalRegistry = new FinalizationRegistry(() =>{
  console.log('注册在finalRegistery的对象,被销毁了')
  
})

finalRegistry.register(info)

info = null

WeakRef

要是想用WeakRef创建出来的弱引用去拿到原来对象的属性要用deref()

const obj = {
    name: 'zsf'
}
let info = new WeakRef(obj)
obj = null
// 直接info.name拿不到
console.log(info.deref().name)// zsf

WeakRef(obj)的作用下,info是弱引用obj不久就会被销毁