likes
comments
collection
share

ES6 | 一文扫清知识盲点

作者站长头像
站长
· 阅读数 54

var、let和const区别

var是es6之前用来声明变量的关键字,let和const是es6新增的声明变量的关键字。

var声明的变量属于函数作用域,而let和const声明的变量属于块级作用域

var存在变量提升,而let和const不存在,变量提升就是可以在声明前访问它。

var在全局作用域下声明的变量会被挂载到window对象上,而let和const不会。

var可以重复声明,而let 重复声明会报错

var和let声明时可以不赋值,但是const声明的是一个常量,必须赋值,且初始值不可以改变。

const如果初始值是一个引用类型,那边非第一层级的属性值依旧可以改变,因为const强调得是指针地址的引用,所以它只能保证当前引用的变量不变。如果想定义一个完全不可更改得对象,可以使用Object.freeze()递归遍历对象属性。

对象新增扩展:

  1. 属性简写,当对象键名和对应值名相等时,可以进行简写
  1. 扩展运算符=>解构赋值
  1. 属性遍历

    1. for...in遍历对象自身和原型上可枚举的属性
    2. Object.keys(obj) 遍历对象自身的可枚举属性
    3. Object.getOwnPropertyNames(obj) 遍历对象自身属性(含可枚举、不含Symbol)
    4. Object.getOwnPropertyNames(obj)对象自身所有Symbol属性的键名
    5. Reflect.ownKeys(obj)遍历对象自身所有属性(含可枚举、Symbol)
  1. 新增的方法

    1. Object.is()
    2. Object.assign()
    3. Object.getOwnPropertyDescriptors()返回对象自身所有自身属性(非继承)的描述对象
    4. Object.setPrototypeOf()设置对象原型对象、Object.getPrototypeOf()获取对象原型对象
    5. Object.keys()、Object.values()、Object.entries()

**数组**新增扩展:

  1. 扩展运算符
  1. 构造函数Array.from 和 Array.of
  1. 实例新增方法

    1. copyWithin(target,start,end) [1,2,3,4,5].copyWithin(0,3)=>[4,5,3,4,5]将index=3位置到结束复制到index=0,覆盖原来得1,2
    2. find(cb(v,i,arr))返回第一个满足条件的数组成员
    3. findIndex(cb(v,i,arr))返回第一个满足条件的数组成员下标
    4. fill(v,start,end)填充数组,不提供第二、三参数,填充整个数组
    5. entries()、keys()、values()
    6. includes(v)
    7. flat()
    8. flatMap(cb)对原数组的每个成员执行回调函数,相当于Array.prototype.map(),再对数组进行flat(),返回新数组

**函数**新增扩展:

  1. 参数:es6允许为函数的参数设置默认值,注意:函数的形参不能使用let和const再次声明,否则报错
  1. 属性

    1. length属性:(function(a,b,c=2){}).length,结果2,返回没有指定默认值的参数个数(必须是尾参数,比如(function(a=2,b,c){}).length),结果0
    2. name属性
    3. var f = funtion(){}
      f.name//"f"
      
      var f = function n(){}
      f.name//"n"
      
      (new Function()).name//'anonymous'
      
      (function x(){}).bind({}).name//"bound x" bind都加上"bound前缀"
      
  1. 函数设置默认值时出现暂时作作用域
let x = 1;
function f(y=x){
    //等同于let y = x;
    let x = 3;
    console.log(y)//1
}
f()
  1. 开启严格模式, 函数使用了默认值解构赋值\或者扩展运算符,都会报错
  1. 箭头函数(箭头函数和普通函数的区别)

    1. this指向上一层级的作用域, 在声明时作用域已经确定
    2. 不能使用new操作符,即不能做构造函数
    3. 函数内没有arguments参数
    4. 不能使用yield命令,不能作为Generator函数

**Set Map**两种数据结构如何理解?

Set是es6新增的数据结构, 类似于数组,是一种集合的数据结构,但是set中的成员必须是唯一的,无序无关联的

Set本身是一个构造函数,实例方法有: add() delete() has() clear() 遍历器keys() values() entries() forEach()

应用: 求交集 并集和差集

let a = new Set([1,2,3])
let b = new Set([4,3,2])

//并集
let union = new Set([...a,...b])
//交集
let intersect = new Set([...a].filter(v=>b.has(v)));
//差集
let difference = new Set([...a].filter(v=>!b.has(v)));

Map是键值对的有序列表, 类似于对象,是一种字典的数据结构,其键值可以是任意类型

Map本身是构造方法, 实例方法有: size set() get() has() delete() clear() 遍历器 keys() values() entries() forEach()

Map和Object的区别: Map的key可以是任意类型,而Object的key只能是字符串类型或Symbol类型

WeakSet和WeakMap相对于Set Map的区别:

WeakSet:

  • WeakSet中的成员必须是一个具有Iterable接口的对象,即只能是引用类型,不能是其他类型
  • WeakSet中对对象的引用是弱引用,也就是当其他对象不再有对该对象的引用,垃圾回收机制就会销毁并会后该对象占有的内存 , 不会考虑它在WeakMap中的引用.
  • 所以WeakMap没有遍历操作的API,也没有size属性

WeakMap:

  • WeakMap中的键必须是对象,不能是其他类型
  • WeackMap中对对象的引用是弱引用
  • 没有遍历操作,也没有clear操作方法

手写Set

方法: has clear add delete values

class Set{
    constructor(){
        this.list = {}
        this.size = 0;
    }
    has(value){
        return value in this.list
    }
    add(value){
        if(!this.has(value)){
            this.list[value] = value;
            this.size++;
        }
        return this;
    }
    delete(value){
        if(this.has(value)){
            delete this.list[value];
            this.size--;
        }
        return this;
    }
    clear(){
        this.list = {};
        this.size = 0;
    }
    values(){
        let result = []
        for(let key in this.list){
           if(this.list.hasOwnProperty(key)){
                result.push(key);
           }
        }
       return result;
    }
}

手写map

set() get() delete() clear() keys() values()

function defaultToString(key){//对key的类型处理
    if(key === null){
        return 'NULL'
    }else if(key === undefined){
        return 'UNDEFINED'
    }else if(Object.prototype.toString.call(key) === '[object Object]' || Object.prototype.toString.call(key)==='[object Array]')
    {
        return JSON.stringify(key);
    }
    return key.toString()
}

class Map(){
    constructor(){
        this.list = {};
        this.size = 0;
    }
    has(key){
        return this.list[defaultToString(key)] !=== undefined;
    }
    
    set(key,value){
        if(!this.has(key)){
            this.list[defaultToString(key)] = value;
            this.size++;
        }
        return this;
    }
    get(key){
        return this.list[defaultToString(key)]
    }
    detele(key){
        if(this.has(key)){
            delete this.list[defaultToString(key)];
        }
        return this;
    }
   clear(){
       this.list = {};
       this.size();
   }
   keys(){
       let result = [];
       for(let key in this.list){
           if(this.has(key)){
               result.push(key)
           }
       }
       return result;
   }
   values(){
       let result = [];
       for(let key in this.list){
           if(this.has(key)){
               result.push(this.list[key])
           }
       }
       return result;
   }
}

**Promise**理解与应用场景

Promise译为"承诺",是异步编程的一种解决方案, 它的提出是为了解决传统请求存在的"回调地狱"问题, Promise通过链式调用降低了代码的层级编写(难度),代码的可读性更高.

Promise有三种状态:

  • pending(进行中)
  • fullfilled(已成功)
  • rejected(已失败)

特点:

  • 对象状态不受外界的影响, 只有异步操作的结果,可以决定当前是哪种状态
  • 一旦状态改变(penging变成fullfilledpending变成rejected), 就不会发生改变

实例方法

  • then(resolved=>{},rejected=>{})
  • catch发生错误返回 , Promise的错误具有"冒泡"性质,一直向后传递,知道被捕获为止
  • finnally 不管Promise对象最后状态如何,都会执行的操作.

构造函数

  • all() 传入一个由promise请求构成的数组, 当所有的promise都resolve才返回最终结果, 如果其中任何一个promise发生了rejected,那么就返回的结果就直接rejected
  • race()传入一个由promise请求构成的数组,将第一个返回值作为返回值, 其他的promise依旧会进行,没有被打断
  • allSettled() 传入一个由promise请求构成的数组, 只有等到所有promise参数实例都返回结果, 不管是rejected还是fullfilled, 才会返回对应的结果数组
  • resolve()将传入的参数封装成Promise对象返回,状态为 rejected
  • reject()将传入的参数封装成Promise对象返回,状态为fullfilled

手写一个简易的Promise

const PENDING = "pending";
const REJECTED = "rejected";
const FULLFILLED ="fullfilled";
class MyPromise{//https://zhuanlan.zhihu.com/p/183801144
    constructor(executor){
        this.status = PENDING;
        this.value = null;
        this.reason = null;
        this.resolvedCallbacks = [];
        this.rejectedCallbacks = [];
        let resolve = (value)=>{
            if(this.status === PENDING){
                this.status = FULLFILLED;
                this.value = value;
                this.resolvedCallbacks.forEach(fn=>fn())
            }
        }
       let reject = (reason)=>{
           if(this.status == PENDING){
               this.status = REJECTED;
               this.reason = reason;
               this.rejectedCallbacks.forEach(fn=>fn())
           }
       }
    }
    try{
        executor(resolve,reject)//封装执行器
    }catch(err){
        reject(err)
    }
    then(onFullfilled,onRejected){
       onFullfilled = typeof onFullfilled === "function"
       ?onFullfilled:function(value){
           return value
       };
       onRejected = typeof onRejected === "function"?
       onRejected:function(error)[
           throw error;
       }
       if(this.status === PENDING){
           this.resolvedCallbacks.push(onFullfilled)
           this.rejectedCallbacks.push(onRejected);
       }
       if(this.status === FULLFILLED ){
           onFullfilled(this.value)
       }
       if(this.status === REJECTED){
           onRejected(this.value);
       }
    }
}

let p = new MyPromise((resolve,reject)=>{//未实现链式调用值穿透
    resolve(2)//reject(3)
}).then(res=>{

},err=>{

})

手写Promise.all()

Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let count = 0;
        let len = promises.length;
        let result = [];
        if(len === 0) resolve([])
        promises.forEach((p,i)=>{
            Promise.resolve(p).then(res=>{
                count +=1;
                result[i] = res;
                if(count === len) resolve(result)
            }).catch(err=>{
                reject(err)
            })
        }) 
    })
}

手写Promise.race()

Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        promises.forEach(p=>{
            Promise.resolve(p).then(resolve).catch(reject)
        })
    })
}

手写allSettled()

Promise.allSettled = function(promises){
    return new Promise((resolve,reject)=>{
        let result = [];
        let count = 0;
        let len = promises.length;
        if(len === 0) resolve([])
        promises.forEach((p,i)=>{
            Promise.resolve(p).then(res=>{
              count +=1;
              result[i] = {
                  value:res,
                  status:"fullfilled"
              };
              if(count === len ) resolve(result)
            }).catch(err){
                result[i] = {
                    value:err,
                    status:"rejected"
                };
                if(count === len ) resolve(result)
            }
        })
    })
}

手写Promise.resolve()

Promise.resolve = function(value){
    if(value instanceOf Promise){
        return value;
    }
    return new Promise((resolve,_)=>resolve)
}

手写Promise.reject()

Promise.reject = function(value){
    return new Promise((_,reject)=>reject)
}

**Proxy**理解,使用场景有哪些?

Proxy用于创建一个对象的代理,从而实现基本操作的拦截和定义(如属性查找\赋值\枚举\函数调用)

Proxy本身为构造函数,new Proxy(target,handler)

target: 所有拦截的目标对象

handler: 通常以函数作为属性的对象, 执行代理行为

handler对象的属性:

  • get(target,propKey,receiver)
  • set(target,propKey,value,receiver)
  • has(target,propKey)拦截propKey in proxy
  • deleteProperty(target,propKey)
  • ownKeys(target)
  • defineProperty(target,propKey,propDesc)
  • getPrototypeOf(target)
  • setPrototypeOf(target,proto)
  • apply(target,Object,args)
  • construct(target,args)

Reflect 需要在内部调用对象的默认行为, 建议使用Reflect

特点:

  • Proxy对象具有的代理方法,Reflect全部具有,以静态方法的形式存在
  • 修改某些Object方法的返回结果, 让其变得合理
  • Object操作都变成函数行为

作用

  • Object中一些明显的语言内部行为, 比如Object.defineProperty(),放到Reflect对象上,未来一些方法也只会部署到Reflect对象上
  • Object上存在的一些命令式操作可以使用Reflect修改成函数操作,比如name in obj 或delete obj[name] 分别替换成Reflect.delete(obj,name) Reflect.has(obj,name)
  • 修改Object中一些函数的返回结果,使它更加合理,比如Object.definePropety无法定义对象的属性时,抛出一个错误, 使用Reflect返回false
  • Reflect对象上的方法与Proxy上的一一对象,可以为Proxy提供修改对象的默认行为

使用场景

  • 拦截和监视外部对对象得访问
  • 降低函数或类的复杂度
  • 在复杂操作前对操作进行校验或对所需资源进行管理

使用Proxy保障数据类型的准确性

let target = {count:0,amount:1234}
let proxy = new Proxy(target,{
    set(target,key,value,proxy){
        if(typeof value !== 'number'){
            throw Error('属性只能是number')
        }
        return Reflect.set(target,key,value)
    }
})

target.count ='foo'//Error:'属性只能是number'
target.count =23

使用Proxy实现观察者模式

观察者模式是指函数自动观察数据对象,一旦对象有变化,函数就会自动执行


function set(target,key,value,receiver){
    const result = Reflect.set(target,key,value,receiver);
    queueObservers.forEach(observer => observer())//set触发执行observe的print
    return result;
}
const queueObservers = new Set();
const observe = fn=>queueObservers.add(fn);
const observable = obj => new Proxy(obj,{set})

const person = observable({
    name:'远方',
    age:18
})
function print(){
    console.log(`${person.name},${person.age}`)
}
observe(print)
person.name = '星星'//触发print自动执行

**Generator函数**和其使用场景

Generator函数为异步编程提供了一种方案, 其特点是

  • function关键字与函数名之间有一个星号
  • 函数体内部使用yield表达式,定义不同的内部状态

Generator函数返回一个遍历器对象, 即具有Symbol.iterator属性, {value:23,done:true/false}结构 . 通过yield关键字可以暂停generator函数返回的遍历器状态

使用场景:

async/await实际上是Generator函数实现异步编程的语法糖

function* gen(){
    const res1 = yield fn(1);
    const res2 = yield fn(res1);
    const res3 = yield fn(res1);
    return res3
}

function generatorAuto(generatorFn){
    return function(){
        return new Promise((resolve,reject)=>{
            const gen = generatorFn();
            const next1 = gen.next();//返回值next1:{Promise:{},done:false}
            next1.value.then(res1=>{
                const next2 = gen.next(res1);//传入结果res1
                next2.value.then(res2=>{
                    const next3 = gen.next();//传入结果res2
                    next3.value.then(res3=>{
                        resolve(g.next(res3).value)
                    })
                })
            })
        })
    }
}
const asyncFn = generatorAuto(gen)
asyncFn().then(res=>console.log(res))//3秒后输出8

module模块化

commonJS与ES6模块化的区别:

  1. CommonJS模块输出的是一个值得拷贝,而ES6模块输出的是一个值的引用. 一个值的拷贝:也就是说一旦输出一个值,模块内部的变化不会引起整个值的变化. 而ES6模块在js引擎对脚本进行静态分析时,遇到import命令, 会生成一个只读引用,等到脚本真正执行时,才会根据值得引用去加载模块中的取值
  1. CommJS是同步加载,而ES6是编译时加载.

引入CommonJS加载是一个对象(module.exports属性), 该对象只有在脚本运行完才会生成; 而ES6模块不是对象,它的对外接口是export xx的静态定义,在代码静态解析阶段就会生成.