es6中不常用的语法总结

274 阅读23分钟

codePointAt

获取字符串中指定位置字符的码点

什么是码点?

早期js中,Unicode使用16位二进制进行存储字符,我们把用一个16位二进制编码叫一个码元;能够存储的字符数量为2的16次方也就是65536个字符,存不下中国汉字,因此将某些文字使用32位二进制进行存储,某些文字就占据两个码元,字符对应的二进制数字就是码点;码点是用十进制表示的;

const text = '𠮷'
text.length // 2
/^.$/.test(text) // false

'𠮷'的长度为2,是因为它使用32位二进制进行表示,占据两个码元,js中以一个码元为一个长度,因此长度就是2;
获取码元:

// es5的方法 charCodeAt(index)
const text = '𠮷'
text.charCodeAt(0) // 55362 
text.charCodeAt(1) // 57271
const text2 = '级'
text2.charCodeAt(0) // 32423
text2.charCodeAt(1) // NaN

获取码点:

codePointAt(index)
const text = '𠮷'
text.codePointAt(0) // 134071
text.codePointAt(1) // 57271
const text2 = '级'
text2.codePointAt(0) // 32423
text2.codePointAt(1) // undefined

'𠮷'的Unicode编码为 text2.charCodeAt(0).toString(16) 和 text2.charCodeAt(1).toString(16) 也就是 /d842 /dfb7;
当使用codePointAt(0)或codePointAt()获取其码点的时候,js默认会把这个文字对应的所有码元(/d842 和/dfb7)的码点一起返回,因此得到134071;
当使用codePointAt(1)进行获取码点的时候就是获取/dfb7的码点因此得到的就是57271; 应用:
判断一个文字是否是32位?

function is32Bit(text){
    // 如果对应的码点超过了最大的16位二进制这是32位
   // return text.codePointAt() > 0XFFF
    // 或通过第二个码点是否有值来判断
    return !!text.codePointAt(1)
}
console.log(is32Bit('𠮷')) // true
console.log(is32Bit('额')) // false

es6为正则添加了u表示匹配码点
/^.$/u.test(text) // true
总结:
一个文字可能占据一个码元或两个码元,而一个文字对应一个码点,因为码点计算了一个文字中所有码元的二进制数字;

String.raw``

\不进行转义

const str = String.raw`123\n456`
console.log(str) // '123\n456'

String.raw可以当作函数来调用
String.raw({raw:[第1项, 第2项]}, 外参1, 外参2) 第一个参数为对象,raw为数组; 执行之后的值为'第1项外参第2项'

String.raw({raw:['a', 'b','c','d']}, 1, 2) // 'a1b2cd'

``放在函数名后面相当于执行这个函数,并且把其中的内容作为参数传递给函数,第一个参数为${}分割的字符串数组,第二个参数为${}的值

function a(a,b){
    console.log(a,b) // ['12','34',raw: Array(2)] '哈哈'
}
const name = '哈哈'
a`12${name}34`

函数参数设置默认值arguments失效

函数的参数设置了默认值,并且在调用该函数的时候没有传参,函数内部使用默认值,那么函数内部的arguments是取不到默认值,但是可以取到传递进来的值

function a(name='哈哈'){
    console.log(arguments.length) // 0
}
a()
function b(name='哈哈'){
    console.log(arguments.length) // 1
}
b('bbb')

new.target

如果该函数被new调用,那么得到的就是函数本身,否则就是undefined,一般用于判断函数是否new调用的

function A(){
    return new.target
}
A() // undefined
new A() // function A(){}

箭头函数总结

  • 箭头函数中没有this,arguments,new.target,如果在箭头函数中使用了它们,那么它们的取值会在箭头函数所在的环境中查找
function a(){
    const arg = arguments
    return () => {
        return arg === arguments
    }
}
a(1,2)() // true
  • 箭头函数没有原型,因此它不能作为构造函数被new调用,否则会报错;
  • 箭头函数中不能使用yield

Object新增的API

Object.is(a,b)

判断两个字段是否相等,基本和===一致,但是需要注意以下两点
Object.is(NaN,NaN)是相等的,但是使用===是不等的,Object.is(+0,-0)是不等的,但是使用===是相等的

  • Object.assign(obj1,obj2...) 合并多个对象到obj1上,并且改变了obj1,并且返回一个对象为obj1
const obj1 = {}
const obj2 = {b:2}
const a = Object.assign(obj1,obj2)
a === obj1 // true
obj1 // {b:2}
Object.getOwnPropertyNames(obj)

获取指定对象的自有属性名,如果属性名有数字,那么就按升序的形式返回,其他的类型按照书写顺序

const obj = {
    a:1,
    b:2,
    1:11,
    3:33,
    2:22
}
Object.getOwnPropertyNames(obj) // ['1', '2', '3', 'a', 'b']
Object.setPrototypeOf(obj1,obj2)

把obj1的原型设置为obj2,返回obj1

const obj2 = { a: 1}
const obj1 = {}
const c = Object.setPrototypeOf(obj1,obj2)
c === obj1 // true
obj1.a // 1

class类

创建方式
1.直接声明式

class 类名{
    // 有值的属性可以直接放在类中,此属性是被定义在实例对象上
    属性 = 值
    // 构造函数
    constructor (属性) {
        // 属性是被定义在实例对象上
        this.属性 = 属性
    }
    // 方法 是被定义在构造函数的原型上
    方法名 () {}
    
    // 动态方法 是被定义在构造函数的原型上
    [参数] () {}
    
    // getter 获取属性的时候执行 被定义在实例对象上
    get 属性 () {}
    
    // setter 设置属性的时候执行 被定义在实例对象上
    set 属性 () {}
    
    // 静态方法 被挂载构造函数上
    static 方法名 () {}
    // 静态属性 被定义在构造函数上
    static 属性 = 值
}
  1. 表达式式
const 名称 = class {
    constructor(){}
}

注意点:

  • 类的声明不会被提升,和const,let一样,提前使用会出现暂时性死区
  • 类中的所有代码都是按照严格模式
  • 类中所有的方法都是不可枚举的(不可for in遍历)
  • 类中所有的方法都不可当作构造函数使用
  • 类必须通过new来调用
  • 类中的所有非静态方法和非使用箭头函数的方法都是被定义在构造函数的原型上
  • 类中所有的非静态属性都是被定义在实例对象上
  • 类中的静态属性或方法被定义在函数上
  • 类中的方法使用了箭头函数,那么这个方法就被定义在实例对象上 类的继承extends
    super() 继承父类的属性,必须写在构造器的第一行,可以使用super.方法名()来调用父类中的方法,父类不应该被new实例化,因为父类是这一类的统称无需实例化,可以通过new.target进行限制;
class Father{
    constructor(name,age){
        this.name = name
        this.age = age
        if (new.target === 'Father') {
            throw new Error('Father不能被实例化')
        }
    }
    getName () {
       return this.name
    }
}
// 子类
class Child extends Father {
    // 如果子类中不写构造器,那么会默认写成下面的形式
    constructor(name,age){
        super(name,age) 
    }
    getName () {
        super.getName()
    }
}

扩展
es7中可以实现装饰器

// 实现一个只能传递字符串的装饰器
function strings(target, methodName, desc){
    const oldFn = desc.value
    desc.value = function (...args){
        if(typeof args[0] !== 'string'){
            throw new Error('参数必须是字符串类型')
        }
        oldFn.apply(this, args)
    }
}
// 实现一个类
class Animate{
    // 实现一个方面使用装饰器
    @strings
    name(name){
       return name
    }
}
const ani = new Animate()
ani.name(123) // 报错 参数必须是字符串类型

Symbol符号

Symbol
Symbol是es6新增的一种基本数据类型,js中的基本数据类型有String,Number,Boolean,undefined,nullSymbol;  
基本使用:  
Symbol(符号描述)  
Symbol和对象的引用一样,创建之后都是独一无二的,即使符号的描述是一样的,但是多个Symbol相同的符号描述也是不相等的
```js
const a = Symbol(1)
const b = Symbol(1)
a === b //false
```
特点:
 - Symbol属于基本类型,得到的类型就是symbol
 - Symbol类型可以作为对象的属性,称之为符号属性,js对象之前只有字符串属性,即使传递的是其他类型的属性,默认都会被转成字符串类型
 - 对象的Symbol属性不可枚举,因此不能使用for inObject.keys获取到,虽然Object.getOwnPropertyNames方法可以获取到对象身上可枚举的属性,但是也无法获取到Symbol属性;
 - 获取对象身上的Symbol属性只能通过Object.getOwnPropertySymbols()方法
 - Symbol没字面量的形式,只能通过函数的方式进行定义
 - Symbol设计的初衷是给对象添加私有属性,只有在对象内部访问,外部无法访问
 - Symbol无法进行隐私的转换类型,因此无法用于运算操作和字符串的拼接,但是可以进行显示的转换
```js
const a = Symbol(1)
typeof a // symbol
const obj = {
    [a]: 22
}
obj // Symbol(1): 22
obj[a] // 22
Object.keys(obj) // []
Object.getOwnPropertyNames(obj) // []
Object.getOwnPropertySymbols(obj) // [Symbol(1)]
Object.getOwnPropertySymbols(obj)[0] === a // true
// 只能通过函数的方式定义
const b = Symbol 
b // f Symbol(){}
// 不能进行隐私的转换
let str = a + 'str' //  Cannot convert a Symbol value to a string
// 显示的转换为字符串
str = String(a) // 'Symbol(1)'
```
Symbol.for共享符号
共享符号就是传递同名的符号名或不传递符号名就是同一个符号  
使用:
```js 
const a = Symbol.for('1')
const b = Symbol.for('1')
a === b // true
const obj = {
    [a]: 1111
}
obj[b] // 1111
Object.keys(obj) // []
```
特点:  
除了可以通过符号名来设置相同的Symbol之后,其他的和Symbol一样
```js
// 实现一个Symbol.for
const SymbolFor = (() => {
    const obj = {}
    return function (key) {
        if (obj[key]) {
            return obj[key]
        } else {
            obj[key] = Symbol(key)
        }
    }
})()
```
知名(公共,具名)符号
知名符号就是一些具有特殊含义的共享符号,通过Symbol静态属性得到  
作用:  
暴露某些js方法的内部实现  
- Symbol.hasInstance
用于判断某对象是否为某构造器的实例,和instanceof一样,重写类的Symbol.hasInstance静态方法可以直接影响instanceof的判断
```js
class A{}
const a = new A()
A[Symbol.hasInstance](a) // true
a instanceof A // true
// 重写A这个类身上的Symbol.hasInstance静态方法
class A{
    static [Symbol.hasInstance] () {
        return false
    }
}
A[Symbol.hasInstance](a) // false
a instanceof A // false
```
- Symbol.isConcatSpreadable
用于修改数组的concat方法的连接否否展开,默认是展开,设置为false表示不展开
```js
const arr1 = [1,2]
const arr2 = [3,4]
arr1.concat(arr2) // [1,2,3,4]
arr1[Symbol.isConcatSpreadable] = false // arr1数组不展开
arr1.concat(arr2) // [[1,2],3,4]
```
Symbol.toPrimitive
对象在转换为基本类型的时候会调用这个方法
```js
let obj = {
    a:1
}
boj[Symbol.toPrimitive] = () => {
    return 2
}
obj++ // 3
```
Symbol.toStringTag
影响toString方法,我们自定义的类是无法通过toString获取到具体的类型,因此可以在类中重写Symbol.toStringTag属性,让toString能够准备的获得类型
```js
class Person{}
Object.prototype.toString.call(new Person()) // '[object Object]'
Person.prototype[Symbol.toStringTag] = 'Person' // 重写这个属性
Object.prototype.toString.call(new Person()) // '[object Person]'
```

async和await

async是一个语法糖,解决了回调地狱的问题,加了async的函数返回一个Promise对象; 基本使用:

    async function a(){}
    a() // Promise

实现一个async:

function async (fn) {
    try{
        const result = fn()
        if (result instanceof Promise) {
            return result
        } else {
            return Promise.resolve(result)
        }
    }catch(err){
        return Promise.reject(err)
    }
}

await
等待某个Promise执行完毕,必须放在函数中和async一起使用,如果await后面等待的不是一个promise对象,那么默认会被包装成Promise

// await 1 相当于 await Promise.resolve(1)

async function a(){
    const err = await b.a  // Promise reject
    console.log(err) // 这里不会被执行
}
a()

await后面若跟着Promise的rejection状态,那么需要try catch或catch捕获错误,否则会直接抛出错误,不再继续往下执行

function err () {
    return Promise.reject('出错了')
}
async function a(){
    const result = await err()
    console.log(result) // 这里不会执行
}
a()
// 捕获错误,防止中断代码的执行
async function a(){
    const result = await err().catch(e => e)
    console.log(result) // 出错了
}
a()

迭代器(iterator)

一个具有next方法的对象,并且next方法返回一个具有value和是否完成迭代的done的对象
作用:为不同的数据提供统一的访问机制

const obj = {
    next: function () {
        return {
            value: XXX,
            done: true/false
        }
    }
}
迭代器创建函数

一个返回具有迭代器的函数

function createIterator(obj){
    let i = 0
    const keys = Object.keys(obj)
    return {
        next: function () {
            return {
                value: obj[keys[i]],
                done: i > keys.length
            }
        }
    }
}
// 给对象设置迭代器
const obj1 = { a:1 }
obj1[Symbol.iterator] = createIterator(obj1)
// 具有迭代器的可以使用for in进行遍历
for (const key in obj1) {
    console.log(key) // a
}

面试题:
创建一个无限的锲波拉奇数列 // 1 1 2 3 5 8 13 21 ...

    function qiebola(){
        let a = 1
        let b = 0 
        return {
            next(){
                const result = {
                    value: a + b,
                    done: false
                }
                b = a
                a = result.value
                return result
            }
        }
    }
    const iterator = qiebola()
    iterator.next() // 1
    iterator.next() // 2
    iterator.next() // 3
可迭代协议

es6规定,一个对象具有Symbol.iterator知名符号的属性,并且这个属性的值为迭代器创建函数,那么这个对象就是具有可迭代协议,此对象就是可迭代对象;此类对象可以通过for of进行遍历,可以通过扩展运算符进行扩展和收缩;
可迭代对象有:数组,字符串,伪数组;

相关面试题:
  1. 把一个对象转换为具有可迭代协议的对象(让一个对象能够像数组一样使用扩展运算符)
   /*
   eg:
       const obj = { name: 1 }
       const arr = [...obj]
       arr // [1]
   */
   const obj = {
       name: 1
   }
   // 添加迭代协议
   obj[Symbol.iterator] = function () {
       const _self = this
       const keys = Object.keys(_self)
       let index = 0
       return {
           next(){
               return {
                   value: _self[keys[index++]],
                   done: index > keys.length,
               }
           }
       }
   }
   const arr = [...obj]
   arr // [1]
  1. 怎么遍历一个可迭代对象(实现一个for of循环)?
    function forOf (obj) {
        if (typeof obj[Symbol.iterator] !== 'function') {
            throw new TypeError(obj + 'is not iterable')
        }
       const iterator = obj[Symbol.iterator]()
       let result = iterator.next()
       while(!result.done){
           console.log(result.value) // 1,2,3,4
           result = iterator.next()
       }
    }
    const arr = [1,2,3,4]
    forOf(arr)
    
    // 如果遍历一个没有可迭代协议的对象会报错TypeError: obj is not iterable
    const obj = { a: 1} 
    for (const k of obj) { // TypeError: obj is not iterable
        console.log(k)
    }

总结:
一个具有next属性的,并且这个属性是一个函数,这个函数返回一个具有valye和done的对象,具有这个next属性的对象就是迭代器;一个可以创建一个迭代器的函数就是迭代器创建函数;一个对象如果具有可迭代协议那么这个对象就是可迭代对象;可迭代协议就是一个具有Symbol.iterator属性并且这个属性就是迭代器创建函数;可迭代对象可以使用for of和扩展运算符;

generator生成器

一个由Generator构造器创建的对象就是生成器,生成器具有next方法,这个对象既是迭代器也是迭代对象;

生成器函数:

function和函数名之间具有*符号的函数就是生成器函数,生成器函数执行返回的对象就是生成器;

// 生成器函数
function *a(){}
// 生成器
const gen = a()
yield关键字

yield关键字指定了生成器每次调用next方法执行的截止位置,并且把yield后面的值作为next方法返回的迭代值的value值

生成器函数运行机制:

每次生成器调用next方法,将导致生成器函数内部从开始执行位置一直执行到yield关键字的位置,并且把这个位置的值作为迭代值通过next返回

    function *a(){
       const b = 2
       yield b // 每次调用next都会执行到yield的位置
    }
    const gen = a()
    gen.next() // { value: 2, done: false }
next方法

next是生成器上的方法,用来执行生成器函数内部代码到yield的方法,可以传递参数,这个参数会作为yield执行之后的返回值,可以通过变量进行接收,第一次调用next方法并且传递参数,这个参数是无意义的,因为第一次执行不会执行yield关键字;

    function d(v){ 
        setTimeout(() => {
            console.log(v)
        },2000)
        return 6 + v
    }
    function *a(){
        const b = yield 2
        const c = yield d(b)
        return c
    }
    const gen = a()
    gen.next(1) // { value: 2, done: false } 第一次传递参数无意义,因为从{执行到2不会执行yield
    gen.next(3) // { value: 9,done: false }
    gen.nexts(4) // { value: 4, done: true } 
return方法

终止生成器的执行,接收一个参数,这个参数作为return执行之后返回的迭代值的value的值,执行这个方法之后,后续再执行next方法,得到的done都是true

function *a(){
    yield 1
    yield 2
    yield 3
}
const gen = a()
gen.next() // { value:1, done: false }
gen.return(4) // { value:4,done:true }
gen.next() // { value: undefined, done: true }
throw方法

该方法执行抛出一个错误,后续的next都是done为true

function *a(){
    yield 1
    yield 2
    yield 3
}
const gen = a()
gen.next() // { value:1, done: false }
gen.throw(4) // 报错 Uncaught4
gen.next() // { value: undefined, done: true }
调用其他生成器

在生成器函数内部可以调用其他生成器函数,但是必须在yield和其他生成器函数名之间加上*,否则会被作为普通函数调用

    function *b(){
        yield 2
        yield 3
    }
    function *a(){
        yield 1
        yield* b()
        yield 4
    }
    const gen = a()
    gen.next() // {value: 1, done: false}
    gen.next() // {value: 2, done: false}
    gen.next() // {value: 3, done: false}
    gen.next() // {value: 4, done: false}

注意点:
生成器函数可以有返回值,这个返回值作为第一个(生成器的next方法返回的done为true的)迭代值的value属性的值

function *a(){
    yield 2
    return 3
}
const gen = a()
gen.next() // { value: 2, done: false }
gen.next() // { value: 3, done: true } // 最为第一个done为true的value的值
作用

解决生成器繁琐的写法,方便使用
通过生成器遍历数组,相比迭代器简单很多

// 迭代器实现遍历数据
function forArr1(arr){
    let index = 0
    return {
        next(){
            return {
                value: arr[index++],
                done: index > arr.length,
            }
        }
    }
}
const iterator = forArr1([1,2,3])
iterator.next()  { value:1, done: false }

// 生成器实现遍历数组
 function *forArr(arr){
     for (let val of arr) {
         yield val
     }
 }
 const gen = forArr([1,2,3])
 gen.next() // { value:1, done: false }
 
 // 通过生成器实现一个斐波拉契数列
 function *qiebola(){
     let a = 1
     let b = 0
     while(true){
         const result = a + b
         b = a
         a = result
         yield result
     }
 }
 const gen = qiebola()
 gen.next() //{ value:1, done: false }
应用

实现一个执行生成器的函数

// 模拟请求
function b (val) {
    return new Promise(res => {
        setTimeout(() => {
            res('成功'+ val)
        }, 2000)
    })
}
// 处理请求结果
function deal(val){
    return '处理完成'
}
// 创建一个生成器函数
function *a(){
    const params = yield {id: 11}
    console.log('params', params)
    const result = yield b(params)
    console.log('result', result)
    const v = deal(result)
    console.log('v', v)
    return deal(result)
// 创建一个执行生成器的函数
function run(genFn){
    const gen = genFn()
    if (typeof gen.next !== 'function') {
        throw new TypeErr('必须传递一个生成器函数')
    }
    let result = gen.next()
    inner()
    function inner(){
        if (result.done){
            return
        }
        // 是promise
        if (result.value && typeof result.value.then === 'function') {
            result.value.then(res=>{
                result = gen.next(res)
                inner()
            },err=>{
                result = gen.next(err)
                inner()
            })
        } else {
            result = gen.next(result.value)
            inner()
        }
    }
}
run(a) // params {id: 11}
       // result 成功[object Object]
       // v 处理完成

总结:
生成器是由生成器函数执行之后得到的一个对象,生成器是可迭代对象并且也是迭代器,在function和函数名之间加那么这个函数就是生成器函数;在生成器函数内部可以使用yield关键字,用来指定next执行的终止位置,并且yield关键字后面的值可以作为next方法执行之后返回的迭代值中的value的值;生成器函数可以指定返回值,如果指定了返回值,那么这个值会作为第一个(next执行之后)具有done为true的value的值;生成器具有next方法,next方法就是用来执行生成器内部的代码,并且可以传递参数,这个参数会作为yield执行之后返回;next方法返回一个迭代值;生成器有return方法,这个方法用来终止生成器函数内部代码的执行,当执行这个方法之后,再执行next方法得到的迭代值的done都是true,表示执行完毕;生成器具有throw方法,可以抛出一个错误,后续再次执行next都是done为true;生成器内部也可以执行其他生成器,但是其他生成器必须在函数名和yield之间有符号,否则会被作为普通函数执行;生成器的作用就是简化迭代器的书写;

Set集合

Set集合和数组一样是用来存储数据的一种方式,Set集合是用来存储不重复的数据,Set对象是可迭代对象,因此具有可迭代对象的特性; 基本使用:
通过构造函数进行创建,并且可以传递初始值。传递的参数必须是可迭代对象,否则报错;

 // 通过构造函数进行创建,传递一个参数
 const set = new Set('12312')
 set // {'1', '2', '3'}
 // 传递一个不可迭代的对象作为参数会报错
const set2 = new Set({a:1}) // TypeError: object is not iterable
Set对象的方法
  1. add(str)
    给集合添加数据,只接收一个参数,如果添加已有的数据那么就不进行任何操作; add内部使用了Object.is进行判断,但是对于-0和+0做了单独处理,它认为它们是一样的都是0
const set = new Set('123')
set.add(4) // {'1', '2', '3', 4}
set.add(-0) // {'1', '2', '3', 4, 0)
set.add(+0) // {'1', '2', '3', 4, 0)
  1. has(str) 判断集合中是否有指定的参数,有就返回true否则false
const set = new Set('123')
set.has('2') // true
  1. delete(str) 删除集合中指定的数据,删除成功返回true
const set = new Set('123')
set.delete('2') // true
set.delete('4') // false
  1. clear() 清空集合
  2. forEach()
    遍历集合
Set对象的属性
  1. size 只读属性,得到集合的长度
遍历Set集合
  1. for of遍历,因为Set是可迭代对象
  2. forEach遍历,因为集合中没有下标,因为第一个和第二个参数都是一样的表示值
注意:

集合中不存在下标

Set集合和数组之间的相互转换
  1. 数组转集合
const arr = [1,2,3]
// 数组是可迭代对象,因此可以作为参数直接传递进去
const set = new Set(arr)
set // { 1, 2, 3 }
  1. 集合转数组
const set = new Set([1,2,3])
// 因为集合是可迭代对象,因此可以通过扩展运算符转换为数组
const arr = [...set] 
arr // [1,2,3]
相关面试题
  1. 给数组或字符串去重
/*
    1. 给字符串去重
    字符串是可迭代的,因此可以直接传递给Set集合进行去重,
    去重之后转换成数组,再join组合成字符串
*/
const str = '123123'
cosnt set = new Set(str)
const str2 = [...set].join('')
 str2 // '123'
/*
    2. 给数组去重
    因为数组是可迭代对象,因为直接作为参数传递给Set
*/
const arr = [1,2,3,3,2,1]
const arr2 = [...new Set(arr)]
arr2 //[1,2,3]
  1. 获取两个数组的并集
/*
    const arr1 = [1,2,3]
    const arr2 = [3,4,5]
    得到 [1,2,3,4,5]
*/
const arr1 = [1,2,3]
const arr2 = [3,4,5]
const arr3 = [...new Set([...arr1,...arr2])]
arr3 // [1,2,3,4,5]
  1. 获取两个数组的交集
/*
    const arr1 = [1,2,3]
    const arr2 = [3,4,5]
    得到 [3]
*/
// 方法一通过Set集合的has方法进行判断
const set = new Set(arr1)
const arr3 = [...new Set(arr2)].filter(item => set.has(item))
arr3 // [3]

// 方法2 通过数组的indexOf进行判断
const arr3 = [...new Set(arr2)].filter(item => arr1.indexOf(item) !== -1)
arr3 // [3]
  1. 获取两个数组的差集
/*
    const arr1 = [1,2,3]
    const arr2 = [3,4,5]
    得到 [1,2,4,5]
*/
// 先获取到交集,再获取差集
const arr3 = [...new Set(arr2)].filter(item => arr1.indexOf(item) !== -1)
const set = new Set([...arr1,...arr2])
const arr4 = [...set].filter(item => arr3.indexOF(item) === -1)
模拟实现Set
class MySet{
    constructor(arr){
        // 不是可迭代对象直接报错
        if (typeof arr[Symbol.iterator] !== 'function') {
            throw new TypeError(arr + 'is not iterator')
        }
        this._data = []
        // 存储初始值
        for (const val of arr) {
            this.add(val)
        }
    }
    // add方法
    add(str){
        // 存在就不做任何操作,否则就添加
        if (this.has(str)) {
            return this._data
        }
        this._data.push(str)
        return this._data
    }
    // has方法
    has(str){
        for (const val of this._data) {
            if (this.isEqual(val, str)) {
                return true
            }
        }
        return false
    }
    // delete方法
    delete(str){
        const index = this._data.indexOf(str)
        if (index >= 0) {
            this._data.splice(index,1)
            return true
        }
        return false
    }
    // clear方法
    clear(){
        this._data.length = 0
    }
    // size属性
    get size(){
        return this._data.length
    }
    // 判断相等的方法
    isEqual(a,b){
        // 单独处理+0,-0
        if (a === 0 && b === 0){
            return true
        }
        return Object.is(a,b)
    }
    // 转成可迭代对象
    // 方法1
    /* [Symbol.iterator](){
        let index = 0
        return {
            next(){
                return {
                    value: this._data[index++],
                    done: index > this._data.length,
                }
            }
        }
    } */
    // 方法2
    *[Symbol.iterator](){
        for (const val of this._data) {
            yield val
        }
    }
    // 实现forEach方法
    forEach(callBack){
        for (const k of this._data) {
            callBack && callBack(k,k,this._data)
        }
    }
}
const s = new MySet([1,2,3,1,2])
s // [1,2,3]
s.size // 3
s.add(4) // [1,2,3,4]

Map集合

Map集合也是一种存储数据的结构,它可以存储键值对数据,并且键是唯一的,键可以是任何类型的数据;可以说它是增强版的对象存储;Map对象也是可迭代对象;

和对象存储的区别
  1. Map的键可以是任何类型的数据,对象的键只有字符串或Symbol
  2. Map可以通过属性获取它的长度,对象没有直接获取长度的属性 基本使用:
    通过构造函数创建Map,并且可以传递参数,这个参数必须是可迭代对象,并且对象的每一项都是一个长度为2的数组;
const m = new Map([[1,2]])
m //  {1 => 2}

// 试试添加一个Set集合
const s = new Set([3,4])
const m2 = new Map([[1,2], s])
m2 // {1 => 2, undefined => undefined} 虽然添加进去了,但是键和值都是undefined

Map的方法
  1. set(key,value) 给map集合添加数据,如果键不存在就添加,键已经存在就修改
const obj = { a: 1 }
const m = new Map([[1,2],[a, 'b']])
m.set(2,2) //  {1 => 2, ƒ => 'b', 2 => 2 }
// 存在直接替换
m.set(obj, 'c') //  {1 => 2, ƒ => 'b', 2 => 2, { a: 1 } => 'c'}
  1. get(键) 获取集合中指定键的值
const m = new Map([[1,2],[a, 'b']])
m.get(1) //  2
  1. delete(键) 删除指定的键

  2. has(键) 判断集合中是否有指定的键

  3. clear() 清空集合

Map属性
  1. size
    只读,获取集合的长度
Map和数组之间的相互转换

因为数组和Map都是可迭代的对象,因此他们彼此之间可以相互转换

  1. 数组转集合
const arr = [[1,2],[3,4]]
const m = new Map(arr)
  1. 集合转数组
const m = new Map([[1,2]])
const arr = [...m]
arr // [[1,2]]
遍历Map
  1. for of遍历集合获得的值都是长度为2的数组
  2. forEach方法参数1为值,参数2为键,参数3为集合本身
注意

map中认为-0和+0和0都是相等的

模拟实现Map
// 存储的数据格式为 [{key,value}]的形式进行模拟
class MyMap{
    constructor(arr){
        // 不是可迭代对象就报错
        if (typeof arr[Symbol.iterator] !== 'function') {
            throw new TypeError(arr + 'is not iterator Object')
        }
        this._data = []
        // 遍历初始值进行添加
        for (const val of arr) {
            // 不是可迭代对象就报错
            if (typeof val[Symbol.iterator] !== 'function') {
                throw new TypeError(val + 'is not iterator Object')
            }
            // 进行添加
            // 获取到迭代器
            const iterator = val[Symbol.iterator]()
            // 获取键
            const key = iterator.next().value
            // 获取值
            const value = iterator.next().value
            this.set(key,value)
        }
    }
    // set
    set(key, value){
        // 判断是否存在
        cosnt index = this.getItem(key)
        if (index !== false) { // 存在就替换
            this._data[index] = { key,value }
        } else {
            this._data.push({ key,value })
        }
        return this._data
    }
    // get
    get (key) {
        cosnt index = this.getItem(key)
        return this._data[index].value
    }
    // has
    has (key) {
        cosnt index = this.getItem(key)
        return index !== false
    }
    clear(){
        this._data.length = 0
    }
    delete(key){
        cosnt index = this.getItem(key)
        if (index === false) {
            return false
        }
        this._data.splice(index,1)
        return true
    }
    // 查找指定项
    getItem (key) {
        for (let i = 0; i < this._data.length; i++) {
            if (key === this._data[i].key) {
                return i
            }
        }
        return false
    }
    get size(){
        this._data.length
    }
    forEach(callBack){
        for (const item of this._data) {
            const iterator = item[Symbol.iterator]()
            const key = iterator.next().value
            const value = iterator.next().value
            callBack && callBack(value, key, this._data)
        }
    }
    *[Symbol.iterator](){
        for (const item of this._data) {
            yield item
        }
    }
}
const s = new MyMap([[1,2],[3,4]])
s.set(3,6) // [{key: 1, value: 2},{key: 3, value: 6}]

WeakSet集合

WeakSet和Set一样,但是WeakSet只能存储对象,WeakSet中的对象的引用都是弱引用,如果其他变量对这个对象没有引用,那么这个对象就会被垃圾回收机制回收掉,不管它在WeakSet中被引用;
基本使用
接收一个可迭代对象作为参数,这个可迭代对象的每一项都是对象

let obj = { a:1 }
const ws = new WeakSet([obj])
// 释放这个对象
obj = null
ws.has(obj) // false
注意

WeakSet对象不是可迭代的,因此不能for of遍历并且没有size和forEach
应用:
可以调试一个对象是否被完全释放;

WeakMap集合

和Map集合一样,但是 WeakMap中的键只能是对象, WeakMap中的键都是弱引用类型,如果其他变量对这个对象不再引用,那么就会被回收掉;

注意

WeakMap是不可迭代对象,没有size和forEach方法,不能进行for of遍历

Reflect反射

Reflect是一个内置的js对象,这个对象暴露了一些可以访问底层的api;

作用
  1. 有助于实现函数式编程,无需在调用new,=等语法进行操作,而是直接通过.的形式调用
  2. 有助于了解Proxy代理对象中能够代理的方法 常用API
Reflect.set(obj, key, value)

给对象设置属性

const obj = {
 a: 1
}
Reflect.set(对象, 'b', 333)
obj // { a: 1, b: 333 }
Reflect.get(对象,key)

获取对象的属性

const obj = {
    a: 1
}
Reflect.get(obj, 'a') // 1
Reflect.apply(fn, 对象,arguments)

指定对象执行函数

const obj = { a: 2 } 
function b(){ return this.a }
Reflect.apply(b, obj, []) // 2
Reflect.deleteProperty(对象,key)

删除对象身上的属性,和delete一样

const obj = { a: 1 }
Reflect.deleteProperty(obj, 'a') // true
Reflect.defineProperty(obj, key, 配置对象)

给对象添加描述符

const obj = {
    a: 1
}
Reflect.defineProperty(obj, 'b', {
    get(){
        return 8
    }
})
obj.b // 8
Reflect.construct(fn, arguments)

通过构造函数的形式创建对象

function a(v){ this.v = v }
const aa = Reflect.construct(a, [2])
aa.v // 2
Reflect.has(对象, key)

判断一个对象身上是否有指定的属性,和in是一样

const obj = { a: 1} 
Reflect.has(obj, 'a') // true

Proxy代理

创建一个代理对象,用于实现基本的拦截和自定义功能;可代理的方法为Reflect的api;后续操作都使用Proxy代理对象;
基本使用:
通过构造函数进行创建,第一个参数为要代理的对象,第二个参数为一个对象,属性为要代理的方法;

const obj = {
    a: 1
}
const pObj = new Proxy(obj, {
    // 代理修改
    set(target, key, value){
        console.log('修改了属性')
        target[key] = value
    },
    // 代理删除
    deleteProperty(target, key){
        console.log('删除了属性')
        Reflect.deleteProperty(target, key)
    }
})
pObj.a = 2 // 修改了属性
delete pObj.a // 删除了属性

应用:
实现一个观察者模式

function observe(target){
    const pObj = new Proxy(target, {
        set(target, key, value){
            Reflect.set(target, key, value)
            // 渲染模板
            render()
        },
        get(){
            return Reflect.get(target, key)
        }
    })
    // 渲染模板
    function render(){}
    return pObj
}

和Object.defineProperty的区别?
defineProperty只能代理读取和写;Proxy可以代理Reflect对象上的所有方法;

数组中新增的方法

Array.of(值)

创建一个指定值的数组

const arr = Array.of(1,2,3)
arr // [1,2,3]

和new Array()创建数组的区别?
new Array()如果只有一个参数,那么它会被作为创建数组的长度

const arr = new Array(2)
arr // [空属性 × 2]
const arr1 = Array.of(2)
arr1 // [2]
Array.from(可迭代对象)

把一个可迭代的对象转换为数组

const arr = Array.from('123')
arr // ['1','2','3']
find()方法

查找第一个返回为true的项,并且返回这一项

const arr = [1,2,3]
const val = arr.find(item => item >= 2)
val // 2

和filter的区别?
filter过滤出来的是所有符合条件的项,返回一个数组;
find查找的符合条件的第一项,返回这一项;

fill(值)

给数组每一项填充指定的值

const arr = [1,2,3]
arr.fill(6)
arr // [6,6,6]
copyWithin()

copyWithin(开始覆盖位置,开始复制的位置,结束复制的位置)复制指定位置区域的数组中的一段进行覆盖指定位置的值

includes(值) es7方法

查找数组中是否有指定的值

类型数组

和数组一样用来存储数据,但是数组中每一项都是以64位二进制进行存储的,而类型数组可以选择指定位数的二进制进行存储,这样可以根据不同的数据选择不同的位数二进制进行存储而节约内存;

  1. int8Array: 每位数据使用8位二进制进行存储,存储范围为-2^8 - 2^8-1
  2. Unint8Array: 没有符号的,每位数据使用8位二进制进行存储,存储范围为0 - 2^8-1
  3. int16Array:每位数据使用16位二进制进行存储,存储范围为-2^16 - 2^16-1
  4. Unint16Array:没有符号的,每位数据使用16位二进制进行存储,存储范围为0 - 2^16-1
  5. int32Array:每位数据使用32位二进制进行存储,存储范围为-2^32 - 2^32-1
  6. Unint32Array:每位数据使用32位二进制进行存储,存储范围为0 - 2^32-1
  7. int64Array:每位数据使用64位二进制进行存储,存储范围为-2^64 - 2^64-1
  8. Unint64Array:每位数据使用32位二进制进行存储,存储范围为0 - 2^64-1 应用:
    一张大小为100117的图片,总共需要占用11700个像素,每个像素都需要四个基本元素(红,绿,蓝,透明度)进行绘制,因此需要一个大小为117004=46800的普通数组来进行存储,需要的二进制大小为4680064=2995200,占据的字节数为2995200/8=374400,占据的kb为374400/1024=365kb; 如果使用类型数组,每个像速需要四个元素(红,绿,蓝,透明度),每个元素的范围为0-255,因此可以选择Unint8Array进行存储,它需要的二进制数468008=374400,需要的总字节数为374400/8=46800,占据的kb为46800/1024=45kb;