ES6+新特性基础篇

277 阅读22分钟

ES6介绍

ECMA ( European Computer Manufacturers Association ) 中文名为欧洲计算机制造商协会, 这个组织的目标是评估, 开发和认可电信和计算机标准. 1994年后该组织改名为Ecma国际.

什么是ECMAScript

  • ECMAScript 是由 Ecma 国际通过 ECMA-262标准化的脚本程序设计语言.

什么是 ECMA-262

在线版ECMA文档:tc39.es/ecma262/#se…

ECMAScript发展历史

第 1 版1997 年制定了语言的基本语法
第 2 版1998 年较小改动
第 3 版1999 年引入正则、异常处理、格式化输出等。IE开始支持
第 4 版1999 年过于激进,未发布
第 5 版2009 年引入严格模式、JSON、扩展对象,数组、原型、字符串、日期方法
第 6 版(ES6)2015 年模块化、面向对象语法、Promise、箭头函数、let、const、数组解构赋值等等
第 7 版2016 年幂运算符、数组includes扩展
第 8 版2017 年字符串扩展、Async/await关键字
第 9 版2018 年对象解构赋值、正则扩展
第 10 版2019 年扩展对象、数组、字符串方法
ES.next动态指向下一个版本

注:从ES6开始,每年发布一个版本,版本号比年份最后一位大1。

ES5之后的JS语法统称ES6!!!

谁在维护 ECMA-262

TC39(Technical Committee 39)是推进 ECMAScript 发展的委员会。 其会员都是公司。(其中主要是浏览器厂商,有苹果、谷歌、微软、因特尔等)。TC39定期召开会议,会议由会员公司的代表与特邀专家出席。

会议的内容: 制定ECMAScript标准,标准生成的流程,并实现。

一个提案在成为标准之前会经历下面的步骤:

  • 草案( Sketch )(非正式地:“普通人提案”):提案特性的第一个描述。
  • 提案( Proposal ):如果 TC39 认为某个特性是重要的,那么此特性就上升为官方提案状态。这并不会保证最终会成为标准,但是大大地增加了成为标准的可能性。 ES6 提案的截止日期是2011年5月,在这之后不会考虑重大的新提案。
  • 实现( Implementations ):提案特性必须被实现,在理想情况下,要支持两种 JavaScript 引擎。在提案获得提升的时候,来自于社区的实现和反馈决定了提案的样子。
  • 标准( Standard ):如果提案持续检验自身,并且被 TC39 接受,那么该提案将最终包含进 ECMAScript 标准的一个版本中。此时,就成了一个标准特性。

TC39会议内容: github.com/tc39/notes/…

为什么要学习 ES6

  • ES6 的版本变动内容较多,具有里程碑意义。
  • ES6 加入许多新的语法特性,编程实现更简单、高效
  • ES6 是前端发展趋势,就业必备技能

ES6 兼容性(97%)

kangax.github.io/compat-tabl… 可查看兼容性

caniuse.com/也可以看css的一些兼…

不兼容es6+语法的浏览器,后面会借助babel插件,将其转化es5。

常见的ES6新特性

  • let、const
  • 模板字符串
  • 对象简写
  • 解构赋值
  • 剩余参数&扩展运算符
  • 箭头函数
  • class声明类 、extends来实现继承
  • 数组、字符串、正则扩展方法
  • Object扩展方法
  • Symbol基本数据数据,表示独一无二的唯一值
  • promise
  • async/await
  • ....

JS严格模式

  • 严格模式通过抛出错误来消除了一些原有的静默错误

  • 严格模式修复了一些导致javascript引擎难以执行优化的缺陷,严格模式通常比非严格模式下运行更快

    开启严格模式:

    • 整个脚本开启严格模式
    <script>
    "use strict";
        
    </script>
    
    • 函数内开启
    function foo(){
     "use strict";
    }
    

在严格模式下,常见的变化如下

  • 声明变量不加关键字var或let,会报错

  • 全局中this不是window,而是指向undefined

  • 不能通过delete删除普通变量。注意:但是可以删除对象中的属性

    <script>
       'use strict'
       let a = 1
       var b = 2
       //    c = 3 // 严格模式下不加关键字声明变量会报错
       function fn() {
          console.log(this)
       }
       fn() // 严格模式下,没有调用者的this会指向undefined而不是window
    
       let c = 1
       // delete c  // 不能删除普通变量,但是可以删除对象属性
       const obj = { c: 1, d: 2 }
       delete obj.c
       console.log(obj) // {d:2}
    </script>
    

Symbol基本数据类型

目前基本属性类型:string,number,boolean,undfined,null,symbol。

什么是symbol

symbol是es6中新引出的一种基本数据类型,表示独一无二的值。

每一次通过Symbol()返回的symbol值都是唯一的。

let p1 = Symbol('张三')
let p2 = Symbol('张三')
console.log(p1 === p2) // false

Symbol.for(),可以创建共享的Symbol,有全局缓存的特点

// 使用Symbol.for() 创建共享的Symbol,有全局缓存特点。如果已经定义过,就会返回原来的值
let person = Symbol.for('李四')
let person1 = Symbol.for('李四')
console.log(person === person1) // true

symbol类型应用场景

最合理的用法就是用Symbol创建的值作为对象的key,防止命名冲突。

<script>
   /* let users = {
      张三: { age: 18, sex: '男' },
      张三: { age: 20, sex: '女' },
   }
   console.log(users) // 产生命名冲突问题,同名的张三,后面的会覆盖前面的,只有一个 */

   // 最合理的使用Symbol创建的值作为对象的key,防止命名冲突
   let key1 = Symbol('张三')
   let key2 = Symbol('张三')
   console.log(key1) // Symbol(张三)
   console.log(key2) // Symbol(张三)
   let users = {
      [key1]: { age: 18, sex: '男' },
      [key2]: { age: 18, sex: '女' },
   }
   console.log(users) // 两个key都是(Symbol(张三)),各自拥有自己的值
   console.log(users[key2]) // { age: 18, sex: '女' }
</script>

symbol特点

  • Symbol值不能和其他数据进行运算

    let a = Symbol(1)
    console.log(a + 1) // 报错
    
  • Symbol函数不能使用new,因为Symbol不是一个构造器

    // 报错:Uncaught TypeError: Symbol is not a constructor
    new Symbol() 
    
  • Symbol创建的属性名无法被枚举出来,即无法使用常规遍历出属性名。可以使用Object.getOwnPropertySymbols()进行遍历,会返回所有Symbol属性的数组

    // Symbol创建的属性名无法被枚举,即无法被普通的遍历出来,只能通过Object.getOwnPropertySymbols()进行遍历
       const obj = {
          a: 1,
          b: 2,
          [Symbol('c')]: 3,
          [Symbol('d')]: 4,
       }
       console.log(obj)
       // 都只能遍历自身的属性名,不能遍历Symbol创建的属性名
       for (var k in obj) {
          console.log(k) // a b
       }
       console.log(Object.getOwnPropertyNames(obj)) // 返回数组['a','b']
       console.log(Object.keys(obj)) // 返回数组['a','b']
    
       // 通过Object.getOwnPropertySymbols()遍历,返回所有Symbol属性的数组
       console.log(Object.getOwnPropertySymbols(obj)) // ['Symbol(c),Symbol(d)']
    

let、const关键字

let的特点

  • 声明的变量属于块级作用域(block scope)
  • 没有变量提升(存在TDZ),Temporal Dead Zone(暂时性死区)

暂时性死区TDZ(Temporal Dead Zone): 起始于函数的开头,终止与相关变量声明的一行。在这个范围内无法访问let或const声明的变量。这块死区就是TDZ。

  • 不能声明相同的变量
  • 声明的全局变量和函数不会成为全局对象window的属性

const特点

  • 声明的量属于常量,常量也属于块级作用域,必须要赋予初始值,且后续不能更改

  • 常量的名字一般约定为全部大写

    // 常量的特点:1.必须有初始值。2.不能修改常量的值。3.属性块级作用域
    // 常量名全大写
    if (true) {
        const PI = 3.14
        console.log(PI)
    }
    //    console.log(PI) // 块级作用域报错
    

注意:修改对象属性和数组元素不会发出const错误

const obj = {
    name: '张三',
    age: 20,
}
const fruits = ['苹果', '梨子', '榴莲']
//  obj = 3 // 报错,常量const声明的不能修改覆盖,但是可以修改属性
obj.age = 22
console.log(obj)
//  fruits = [] // 报错
fruits[2] = '西瓜'
console.log(fruits) // ’['苹果', '梨子', '西瓜']‘

let与const的使用场景

  • 对于需要保护(防止被覆盖)的变量使用const

  • 只有确定要改变变量的值的时候用let

    因为不停变化的变量值就是很多bug的源头

  • 声明对象类型使用const,非对象类型使用let。

模板字符串

用一对反引号包裹起来的部分就是模板字符

`${表达式}`
let name = '张三'
let age = 18
function hello(name) {
    return '你好:' + name
}

let obj = {
    sex: '男',
}
let info = `my name is ${name},age is ${age},性别:${obj.sex}
欢迎语:${hello(name)}
`
console.log(info) 

作用

用于字符串和变量的拼接,且支持换行操作

表达式的特点:一定会产生一个值

其表达式的值可以是变量、属性调用、函数调用、三元运算符等。

对象简写

可以进行属性简写,方法简写

当属性的key与value相同时,可以进行简写。方法也可以省略function

建议简写的属性定义在前面

let name = '张三'
let age = 18
let user1 = {
    name: name,
    age: age,
    getName: function () {
        return `my name is ${this.name}`
    },
}
console.log(user1.getName())

// 上面user1等价于user2:
let user2 = {
    name,
    age,
    getName() {
        return `my name is ${this.name}`
    },
}
console.log(user2.getName())

解构赋值

定义:可以对某个数据结构进行解开再把值赋给其他变量

对象的解构赋值

语法:解构时,可以设置别名和默认值,语法:

  • 属性名:别名,给属性名设置别名,后续引用只能用别名
  • 属性名 = 默认值,给属性设置默认值,当没有定义值会使用默认值,定义了值就使用定义的值。
let {属性名,属性名:别名,属性名=默认值} = 对象

eg:

const obj = {
    name: '张三',
    age: 18,
    email: 'zs@qq.com',
    // hobby: '打篮球',
}
// 这里可以有别名也可以有默认值
let { name: myName, age, email, hobby = '唱跳rap' } = obj
let info = `my name is ${myName},my age is ${age},my email is ${email},my hobby is ${hobby}`
console.log(info)

对象的参数进行解构

// 对对象形式参数进行解构赋值
function test({ name: myName, email, age, hobby = '跳舞' }) {
    console.log(`${myName}-${age}-${email}-${hobby}`)
}

const obj = {
    name: '张三',
    age: 18,
    email: 'zs@qq.com',
    hobby: '唱歌',
}
test(obj)

数组的解构赋值

// 解构赋值,a接受数组第一个元素,b接受数组第二个元素
const arr = [1, 2]
let [a, b] = arr
console.log(a, b)

// 省略前几个数组元素
const user = ['张三', '李四', '王五']
//  const [zs, ls, ww] = user
//  console.log(zs, ls, ww) // '张三' '李四' '王五'
const [, , ww] = user
console.log(ww)
const [zs, ,] = user
console.log(zs)

// 交换两个变量的值
let num1 = 10
let num2 = 20
// 这里需要加要给分号!防止浏览器解析导致错误
;[num1, num2] = [num2, num1]
console.log(num1, num2) // 20  10

扩展(剩余)运算符

它的作用是将一个数组或对象展开,将其中的元素或属性逐一赋值给新的变量。

const arr = [1,2,3]
const arr2 = [...arr]
console.log(arr2) // [1,2,3]

... 三个点加参数名。作用:

  • 获取函数所有的实参或部分实参,可以替换arguments
function sum(...args) {
    console.log(args) // [12,11,13]
    console.log(arguments) // Arguments(3)[12,11,13]
}
sum(12, 11, 13)
  • 合并对象或数组
// 2 合并对象或数组
const obj = {
    name: 'zs',
    age: 18,
    address: { city: 'sz', area: '宝安' },
}
const obj2 = {
    hobby: '唱歌',
    say: function () {
        console.log('hello')
    },
    ...obj,
}
console.log(obj2)
//    const mergeObj = { ...obj, ...obj2 }
//    console.log(mergeObj)

const arr1 = [1, 2, 3]
const arr2 = [...arr1, 4, 5, 6]
//    const mergeArr = [...arr1, ...arr2]
//    console.log(mergeArr)
console.log(arr2)
  • 接收数组或对象剩余元素
 // 3 获取剩余元素
const family = ['dad', 'mom', 'son1', 'son2', 'son3']

let [dad, mom, ...sons] = family
console.log(dad, mom, sons) // 'dad' 'mom' ['son1','son2','son3']

const user = {
    name: '张三',
    age: 19,
    hobby1: '唱歌',
    hobby2: '跳舞',
    hobby3: 'rap',
    hobby4: '篮球',
}
let { name, age, ...hobby } = user
console.log(name, age, hobby) // ['张三',19,Array(4)]

箭头函数

cosnt fn = ()=>{} 等价于const fn = function(){}

箭头函数和普通函数的区别

  • 箭头函数中没有this指向,不会绑定this的指向,this还是保留上一层作用域中的指向

     // 1. 无绑定的this指向,箭头函数中的this仍是上一层作用域的this指向,箭头函数不会更改this指向
    var age = 30
    const obj = {
        myName: '张三',
        age: 19,
        getAge: function () {
            console.log('age is ', this.age)
        },
        getAge2: () => {
            console.log('age is ', this.age)
        },
        getName: function () {
            console.log('name is ', this.myName)
        },
        getName2: () => {
            console.log('name is ', this.myName)
        },
    }
    obj.getAge() // age is 19 ,this为obj
    obj.getAge2() // age is 30 ,this为window
    obj.getName() // name is 张三 ,this为obj
    obj.getName2() // name is undefined ,this为window
    
  • 箭头函数中没有arguments,若要获取全部实参可以使用扩展运算符(...args)

const fn1 = function () {
    console.log(arguments)
}
fn1(1, 2, 3) // Arguments(3)

/* const fn2 = () => {
      console.log(arguments)
   }
   fn2(1, 2, 3) // 报错 arguments is not defined */

const fn3 = (...args) => {
    console.log(args)
}
fn3(1, 2, 3) // [1,2,3]
  • 箭头函数没有构造器constructor,不能使用new操作符

    const Person = function (name, age) {
        this.name = name
        this.age = age
    }
    console.log(new Person('张三', 20)) // Person{name:'张三',age:20}
    
    /* const User = () => {}
       console.log(new User()) // 报错 User is not a constructor */
    

可选链运算符

可选链运算符?. 可以读取对象深层的属性值,可以省去多余的判断步骤

const obj = {
    name: '张三',
    age: 22,
    address: {
        city: '深圳',
        area: '宝安',
    },
}
const obj2 = {
    name: '李四',
    age: 20,
    city: '深圳',
    area: '宝安',
}
console.log(obj.address.area) // '宝安'
// console.log(obj2.address.area) // 报错 Cannot read properties of undefined (reading 'area')

console.log(obj?.address?.area) // '宝安'
console.log(obj2?.address?.area) // undefined ,不会报错

class声明类

js中没有class类的概念,es5中只能通过构造函数和原型对象结合的方式来模拟类的效果,es6中可以直接通过class来定义一个类

  • es5实现构造函数
// es5实现构造器
function Person(name, age) {
    this.name = name
    this.age = age
}
Person.prototype.getName = function () {
    console.log('my name is', this.name)
}
Person.prototype.setName = function (newName) {
    this.name = newName
}

const p1 = new Person('zs', 18)
console.log(p1)
p1.getName() // 'zs'
p1.setName('李四')
p1.getName() // '李四'
  • es6通过class来模拟一个类(构造函数)
// es6实现构造器
class Person1 {
    // 构造函数,用于初始化操作,new的时候会立即执行
    constructor(name, age) {
        this.name = name
        this.age = age
    }
    getName() {
        console.log('my name is', this.name)
    }
    setName(newName) {
        this.name = newName
    }
}
console.log(Person1.prototype.constructor === Person1) // true
const p2 = new Person1('zs', 18)
console.log(p2)
p2.getName() // 'zs'
p2.setName('李四')
p2.getName() // '李四'

extends继承

// 父类
class Animal {
    constructor(name) {
        this.name = name
    }

    drink() {
        console.log(this.name + '在喝水')
    }
}

// 子类 extends 父类
class Dog extends Animal {
    constructor(name, age) {
        // 必须执行父类的构造函数
        super(name)
        this.age = age
    }
    eat() {
        console.log(this.name + '吃骨头')
    }
}
const dog = new Dog('大黄', 5)
console.log(dog) // Dog {name: '大黄', age: 5}
dog.drink() // 大黄在喝水
dog.eat() // 大黄吃骨头

静态属性与静态方法

  • 成员(实例)属性:可以通过对象直接访问到的属性,称之为实例属性
  • 静态属性/方法:由类名设置的属性和方法。作用:一般用来实现一些辅助功能

静态属性/方法不属于对象,不能通过对象操作,仅能用类名(构造函数名)操作

es5中的静态属性和静态方法

 // Person是构造函数的名字
function Person(name){
    this.name = name;
}
// 添加静态属性
Person.sex = '男';
// 添加静态方法
Person.say = function(){
    console.log('我是静态方法say')
}
// 静态的属性或方法只能用构造函数名去调用
console.log(Person.sex) // 男
Person.say() // 我是静态方法say
const p1 = new Person('zs')
console.log(p1.sex) // undefined
// p1.say()  // 报错 p1.say is not a function

es6中的静态属性和静态方法

// 静态属性只能由类名去访问或设置,不能通过对象去操作
class Person {
    constructor(name) {
        this.name = name
    }
    sex = '男'
	static hobby = '唱歌'
	say() {
    	console.log('hello')
	}
	static eat() {
    	console.log('按时吃饭')
	}
}
const p1 = new Person('张三')
console.log(p1)
p1.say() // hello
console.log(p1.sex) // 男
console.log(Person.hobby) // 唱歌
console.log(p1.hobby) // undefined
Person.eat() // 按时吃饭
// p1.eat()  // 报错 p1.eat is not a function

Array的静态方法

  • Array.from(): 将伪数组转化为真数组
  • Array.isArray():判断某个变量是否是数组

Object常用的静态方法

Object.keys()和Object.values()

  • Object.keys(),常用:返回对象的所有的key到一个数组中。但不含symbol的key
  • Object.values():返回对象的所有value到一个数组中
const obj = {
    name: '张三',
    age: 19,
    sex: '男',
}
// Object.keys() 与 Object.values()
console.log(Object.keys(obj)) // ['name', 'age', 'sex']
console.log(Object.values(obj)) // ['张三', 19, '男']

Object.assign()

  • Object.assign(target,source):将源对象身上中的属性复制到目标对象中,若有同名属性,将会覆盖目标对象。

    此方法返回修改后的目标对象

// Object.assign(target,source)
const target = { a: 1, b: 2 }
const source = { b: 3, d: 4 }

Object.assign(target, source)
console.log(target) // {a:1,b:3,d:4}
  • 作用:可以合并默认参数。(即给一个默认的值,当自身有该值时则覆盖,没有时则使用默认的值)
// 可以合并默认参数
// 对指定的url地址发起get或post请求
function request(data) {
    const defaultParams = {
        type: 'get',
    }
    // 将data与默认参数合并
    const params = Object.assign(defaultParams, data)

    console.log(params)
}
request({ type: 'post', url: '1.1.1.1' })
request({ type: 'get', url: '1.1.1.2' })
request({ url: '1.1.1.3' }) // {type:'get',url:'1.1.1.3'}

Object.create()

Object.create()方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)

// Object.create(), 创建一个新对象,并将现有的对象作为新创建对象的原型
const Person = {
    name: '张三',
    getName() {
        console.log('my name is ' + this.name)
    },
}
const obj1 = Object.create(Person)
console.log(obj1.__proto__ === Person) // true
  • 可以利用Object.create(null)创建一个没有任何原型的对象,即非常干净的对象
//创建一个没有任何原型的对象,即非常干净的对象
const myObj = Object.create(null)
console.log(myObj) // {} No properties
  • 使用es5实现Object.create(null)源码
cosnt obj = {}
// obj.__proto__ = null // 可以实现,但不建议使用
Object.setPrototypeOf(obj,null) // 推荐

Object.is()

Object.is()判断两个值是否为同一个值,可以判断NaN等于NaN

console.log(+0 == -0) // true
console.log(+0 === -0) // false

console.log(null == undefined) // true
console.log(null === undefined) // false

console.log(NaN == NaN) // false

// Object.is() 判断两个值是否为同一个值,可以判断NaN
console.log(Object.is(+0, -0)) // false
console.log(Object.is(null, undefined)) // false
console.log(Object.is(NaN, NaN)) // true

Object.defineProperty()

Object.defineProperty(对象名,'属性名',{属性描述符})给对象添加或修改属性,对属性有更加细粒度的控制。可以控制属性是否可以被枚举、删除、修改等。

返回修改后的原对象

属性描述符

  • 数据描述符

    • value:属性值

    • enumerable: 是否可枚举,默认为false

    • writable: 是否可被修改,默认为false

    • configurable: 是否可以被删除,默认为false

      const obj = {
        name: '张三',
        age: 18,
      }
      // 使用Object.defineProperty()添加属性
      Object.defineProperty(obj, 'hobby', {
        value: '唱歌',
        enumerable: false,
        writable: true,
        configurable: true,
      })
      
      // 使用Object.defineProperty()修改属性,修改属性时,属性描述符的默认值不会生效,保持原属性的状态,但可以通过设置来改变。
      Object.defineProperty(obj, 'age', {
          value: 28,
          configurable:false,
      })
      obj.hobby = '跳舞'
      obj.age = 22
      console.log(Object.keys(obj)) // name age 
      // delete obj.hobby
      delete obj.age
      console.log(obj) // {name: '张三', age: 22, hobby: '跳舞'}
      
  • 存储描述符

    • get:读取数据时拦截

    • set:修改数据时拦截

    const obj = {
        name: '张三',
    }
    let age = 18
    Object.defineProperty(obj, 'age', {
        get() {
            console.log('getter')
            return age
        },
        set(newAge) {
            console.log('setter触发了')
            age = newAge
        },
    })
    console.log(obj)
    
    obj.age = 22 // setter触发了
    console.log(obj.age) // getter触发了 22
    

注意,上面对属性设置的描述可以分为两大类:

configurableenumerablevaluewritablegetset
数据描述符可以可以可以可以不可以不可以
存取描述符可以可以不可以不可以可以可以

通过defineProperty设置symbol属性同样也是无法被枚举的,只能通过Object.getOwnPropertySymbols去获取

迭代器iterator

迭代器(Iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要实现了 Iterator 接口,就可以通过for-of实现遍历操作。

一句话:可以让不支持遍历的数据结构 -》 可遍历

原生具备iterator接口的数据(即都可用for of遍历)

  • Array
  • Arguments
  • Set
  • Map
  • String
  • NodeList
  • ....
let str = 'abc'
for (let item of str) {
    console.log(item) // a b c
}
const arr = ['red', 'green', 'blue']
for (let item of arr) {
    console.log(item) // red green blue
}
function sum() {
    console.log(arguments)
    for (let item of arguments) {
        console.log(item) // 1 2 3
    }
}
sum(1, 2, 3)

// 通过迭代器对象next方法,每次获取单个值。
const iterator = arr[Symbol.iterator]()
console.log(iterator.next())	// {value: 'red', done: false}
console.log(iterator.next())	// {value: 'green', done: false}
console.log(iterator.next())	// {value: 'blue', done: false}
console.log(iterator.next())	// {value: undefined, done: true}

注意next一次仅能获取一次值。要获取所有可以用for-of来进行迭代

迭代器工作原理

  1. 创建一个指针对象,指向当前数据结构的起始位置
  2. 第一次调用对象的next方法,指针自动指向数据结构的第一个成员
  3. 接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
  4. 每调用next方法返回一个包含value和done属性的对象

实现迭代器需要的协议

  • 可迭代协议: 含有Symbol.iterator属性。

  • 迭代器协议:

    - 必须返回一个对象
    - 返回的对象要有next方法
    - next方法也要返回一个对象,并且有value 和 done两个属性
    
    return {
        next(){
            return {
                value:xxx,	// value当前迭代的值
                done:boolean // true 遍历完毕, false 没有遍历完毕,可继续遍历
            }
        }
    }
    

汇总常用的循环技巧

  1. for/while/do-while循环: 作用:循环数组、字符串
  2. for-in: 循环对象可枚举的属性和原型链上的属性和方法,不包含Symbol。得到Symbol类型的属性用Object.getOwnPropertySymbols()
  3. forEach: 循环数组和部分伪数组(querySelectorAll)
  4. map/filter/reduce/some/every/find/findIndex:用于数组
  5. for-of: 用来迭代具有实现迭代器(Symbol.iterator)接口的对象

伪数组和真数组

  • 伪数组(likeArray):和真数组差不多,可以通过下标索引取值,也有length属性,但不能调用真数组的方法如push。如 argumentsgetElementsByTagNamequerySelectorAll等返回都是伪数组。

    const obj = {
        0: 'red',
        1: 'green',
        2: 'blue',
        length: 3,
    }
    for (let i = 0; i < obj.length; i++) {
        console.log(obj[i])
    }
    //    obj.push('yellow') // obj.push is not a function 伪数组不是数组
    

伪数组转真数组

  • [...arrayLike],伪数组转成真数组(要求伪数组具有迭代器接口(Symbol.iterator))
  • Array.from()
  • Array.prototype.slice.call()
function sum() {
    console.log(arguments)
    // 方式1:伪数组转成真数组(要求伪数组具有迭代器接口(Symbol.iterator))
    // const args = [...arguments]
    // 方式2:
    // const args = Array.from(arguments)
    // 方式3:
    // const args = Array.prototype.slice.call(arguments)
    const args = [].slice.call(arguments)
    args.push(4)
    console.log(args)
}
sum(1, 2, 3)

Set集合

Set集合,es6提供的一个新数据结构,类似于数组,但成员的值都是唯一的,同时实现了iterator接口,所以可以使用for..of进行遍历

Set中的元素是唯一的!

let set = new Set([1,2,2,3,3,4,4,5])
console.log(set) // Set(5){1,2,3,4,5}

常用的set集合API

  • add 添加元素,支持链式操作

    set.add(6).add(7).add(8)
    
  • delete 删除

    set.delete(5)
    
  • clear 清空

    set.clear()
    
  • size 返回集合中唯一元素的个数

    set.size
    
  • has 检查集合中是否包含某个元素,返回boolean

    set.has(2);
    
  • set 的遍历

    // forEach
    set.forEach(item=>{
        console.log(item);
    })
    
    // 实现了iterator接口 可以使用for of进行遍历
    for(let item of set){
        console.log(item);
    }
    

set应用场景

  • 将集合set转为数组

    Array.from(set)
    // 或者
    [...set]
    
  • 实现数组去重

    // 实现数组去重
    const arr = [1, 2, 3, 2, 3, 1]
    // 思路:1.转成集合 2. 在转回数组
    const newArr = [...new Set(arr)]
    console.log(newArr)
    

Set和数组区别

  1. 重复值:Set中不允许有重复的值,而数组中可以有重复的值。
  2. 检索:Set没有提供像数组一样的索引访问方式,它只能通过迭代器或转换为数组后进行访问。而数组可以通过索引访问任何一个元素。
  3. 功能:Set是一种集合类型,主要用于判断值是否在集合中存在。而数组提供了一系列的操作,例如增加、删除、查找、排序、过滤等

以下是一些常用操作的比较:

操作Set数组
创建new Set()[] 或 Array()
添加元素set.add()arr.push()
删除元素set.delete()arr.splice()
检查元素是否存在set.has()arr.includes()
元素个数set.sizearr.length
转换为数组Array.from(set) 或 [...set]

Map映射

介绍:

  • ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。
  • Map中的key可以是任意数据类型,而对象Object中的key只能是字符串或Symbol类型。
  • Map也实现了iterator接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。

Map常用的API

  • size    返回Map的元素个数
  • set     增加一个新元素,返回当前Map
  • get     返回键名对象的键值
  • has     检测Map中是否包含某个元素,返回boolean值
  • delete 函数对应key
  • clear   清空集合
let obj = {a:3}
myMap.set('a', 'a1')
myMap.set(1, '2').set(2, '3')
myMap.set(obj, '4')
console.log(myMap.get('a')) // 'a1'
console.log(myMap.has(obj)) // true
console.log(myMap.size) // 3
console.log(myMap) // Map(4) {'a' => 'a1', 1 => '2', 2 => '3', {…} => '4'}
myMap.clear()
console.log(myMap) // Map(0) {}

map的遍历

const map1 = new Map()
map1.set('a', 'a1')
map1.set('b', 'b2')

// for..of来迭代
for (var item of map1) {
  console.log(item) // ['a','a1'] , ['b','b2']
}
// 或
console.log([...map1]) // [ [ 'a', 'a1' ], [ 'b', 'b2' ] ]

// 解构赋值
for (var [k, v] of map1) {
  console.log(k, v) // a  a1 , b b2
}

Map 和 Object区别

  • Map可以以任意类型数据作为key,而Object只能是string类型或Symbol类型。
  • Map可以迭代(for-of),object不可以
  • map是有序的,Object不能保证顺序
  • 常用API操作
操作对象Map
创建{} 或 new Object()new Map()
添加键值对obj[key] = valuemap.set(key, value)
删除键值对delete obj[key]map.delete(key)
检查键是否存在key in objmap.has(key)
获取值obj[key] 或 obj.keymap.get(key)
键值对数量Object.keys(obj).lengthmap.size
迭代器Object.keys(obj)[...map]

扩展的字符串函数

  • str.padStart(length,val) 开头填充字符串,第一个参数为需要填充的后的总长度,第二个参数为填充的值

  • str.padEnd(length,val) 末尾填充字符串

    console.log( "5".padStart(5,0) ) // '00005'
    console.log( "5".padEnd(3,0) ) // '500'
    console.log( "5".padEnd(3,'abc') ) // '5ab'
    
  • str.startsWith 判断一个字符串是否以特定字符串开头,满足返回true,否则返回false

  • str.endsWith 判断一个字符串是否以特定字符串结尾,满足返回true,否则返回false

    console.log( 'v-model'.startsWith('v-') ); // true
    console.log( 'modelhtmltext'.startsWith('model') ); // true
    console.log( 'modelhtmltext'.startsWith('text') ); // true
    
  • str.trim 去除两边连续的空格

  • str.trimStart去除开头连续的空格

  • str.trimEnd去除结尾连续的空格

    console.log( '  abc  ' ); // '  abc  '
    console.log( '  abc  '.trim() ); // 'abc'
    console.log( '  abc  '.trimStart() ); // 'abc  '
    console.log( '  abc  '.trimEnd() ); // '  abc'
    

扩展的数值函数

  • Math.pow(n,m): 返回以n为底的m次方。

    或 n**m

  • Number.parseInt():返回转换值的整数部分

  • Number.parseFloat():返回转换值的浮点数部分

  • Number.isNaN():是否为NaN

  • Number.isInteger():是否为整数

  • Math.trunc():返回数值整数部分

扩展的数组函数[重要]

常用的有: forEach、map、filter、every, some, find, findIndex,reduce。

上面方法特点:

  1. 都是不可变(非破坏性)方法
  2. 除了forEach函数没有返回值,其他函数都有返回值。

每个函数的作用:

  • forEach: 对数组中每个元素进行遍历,此方法没有返回值
  • map: 对数组中每个元素进行加工处理,返回一个加工后的新数组
  • filter: 对数组中的元素进行筛选,返回一个筛选后的新数组
  • find: 返回数组中第一个满足指定条件的元素
  • findIndex: 返回数组中第一个满足指定条件的元素下标
  • every: 若数组中每个元素都满足指定的条件,才返回true,否则返回false
  • some: 若数组中只要有一个元素满足指定的条件,就返回true,否则返回false
  • reduce: 连续操作器

reduce函数(连续操作器)

语法:

arr.reduce(callback(accumulator,value,[index],[originArr]),[initValue])

callback四个参数:

  • accumulator 累计器
  • value 当前值
  • index 可选,当前索引
  • originArr 可选,原数组

initialValue可选,累加器函数初始值

reduce连续操作器特点

  • 若传了初始化值initValue,则从数组下标0 开始循环
  • 若没传初始化值initValue,则将数组下标为0的值作为初始值,从下标1开始循环
  • callback函数返回的累计器的值会作为下一次循环累计器的值,reduce函数最终会得到最后一次累计器的值
const arr = [100, 200, 300]
// 不设置默认值,会将下标为0的值作为初始值,从下标1开始循环
let res = arr.reduce((accumulator, value, index, originArr) => {
    // 1 200 100   2 300 300
    console.log(index, value, accumulator)
    return accumulator + value
})
// reduce函数最终返回最后一次累计器的值 (即 300+300)
console.log(res) // 600
const arr = [100, 200, 300]
// 设置默认值,会将默认值作为初始值,从下标0开始循环
let res = arr.reduce((accumulator, value, index, originArr) => {
    // 0 100 1000	1 200 1100	2 300 1300
    console.log(index, value, accumulator)
    return accumulator + value
}, 1000)
// reduce函数最终返回最后一次累计器的值 (即 1300+300)
console.log(res) // 1600

flat扁平化数组

flat() :实现数组扁平化。即将多维数组变为一维数组。

flat(depth) // depth深度默认1, Infinity为任意深度

作用:扁平化嵌套数组

var arr1 = [1, 2, [3, 4]];
arr1.flat();
// [1, 2, 3, 4]

var arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]

var arr3 = [1, 2, [3, 4, [5, 6]]];
arr3.flat(2);
// [1, 2, 3, 4, 5, 6]

//使用 Infinity,可展开任意深度的嵌套数组
var arr4 = [1, 2, [3, 4, [5, 6, [7, 8, [9, 10]]]]];
arr4.flat(Infinity);
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

扩充的正则功能

字符串matchAll函数

matchAll:返回正则匹配的所有结果及捕获组的迭代器

matchAll要求正则必须有标识符g

捕获组命名:给捕获组内容起别名,方便引用

语法:(?<别名>reg)

let reg = /(?<first>1[3-9]\d\)d{8}/
let str = 'aaa13811112222sbb18733334444rqe'
let reg = /(?<first>1[3-9]\d)\d{4}(?<second>\d{4})/g

console.log(str.match(reg))
let res = str.matchAll(reg)
console.log(res)
//    for (let item of res) {
//       console.log(item)
//    }

console.log([...res])

解决深拷贝中的互相引用问题

互相引用:一个或多个对象中某个属性互相引用

  • 对象自身引用

    const obj = {a:1}
    obj.b = obj
    
  • 对象互相引用

    const obj1 = {a:1}
    const obj2 = {b:2}
    obj1.c = obj2
    obj2.d = obj1
    

在递归深拷贝中,如果对象中存在互相引用的问题,会导致递归无法停止,从而导致调用栈溢出。

报错信息:Maximum call stack size exceeded 超过调用栈大小

解决思路:

  • 创建一个map容器作为缓存容器,每次递归时将对象存储其中
  • 在开始递归之前,先判断缓存中是否存有相同的对象,如果有则直接返回而不会继续执行递归
function deepCopy(target, cacheMap = new Map()) {
    // 如果是基本类型则直接返回
    if (typeof target !== 'object') {
        return target
    }
    
    // 如果之前map容器中有相同的key则直接返回对应的value而不继续拷贝
    if (cacheMap.has(target)) {
        return cacheMap.get(target)
    }
    const data = Array.isArray(target) ? [] : {}
    
    // 将target与data存储在一个map容器中,target作为key,data为value
    cacheMap.set(target, data)
    
    if (Array.isArray(target)) {
        target.forEach((value) => {
            data.push(deepCopy(value, cacheMap))
        })
    } else {
        for (let k in target) {
            data[k] = deepCopy(target[k], cacheMap)
        }
    }
    return data
}

es6常用操作

  • let const
  • 解构赋值
  • 扩展运算符
  • 箭头函数
  • 数组方法:forEach/map/filter/find/findIndex/every/some/reduce
  • Object.keys(),Object.values()
  • 集合Set:元素一定是唯一的
  • 映射Map:可以以任意数据类型当作key