你真的懂ES6吗? 5000字从基础到源码再到自己实现!

239 阅读11分钟

一、ES5 var 和 ES6 let 、const 声明变量的优劣

1.1、var的不好地方

1.1.1 var 声明的变量 (污染全局变量)

var globalVar = '老铁,666'
console.log(window.globalVar);

image.png

1.1.2 var 会造成变量提升

console.log(improveVar);
var improveVar = '老铁,没毛病'

1.1.3 var 可以重复声明

var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
var repeatVar = '从前有座山,山里有座庙,庙里有个老和尚在说:'
console.log(repeatVar);

image.png

2.1. let 的好处

2.1.1 块级作用域

{
    let bolckVar = '快域'
}
console.log(bolckVar);

image.png

2.1.2 块级作用域的提升用法

比较var 和 let

for (var i =0 ;i < 5; i++) {
    setTimeout(()=>{
        console.log(i);
    })
}

var 申明的结果都一样

image.png

let 申明的都会有一个作用域,数值不同

for (let i =0 ;i < 5; i++) {
    setTimeout(()=>{
        console.log(i);
    })
}

image.png

2.1.3 注意let用法造成的暂存死区

let blockVarDied = '死海'
{
    console.log(blockVarDied);
    let blockVarDied = '死死海'
}

image.png

3、 const 常量

3.1.1. cosnt 申明的常量简单数据类型不可变

const finalVar = '万古长存'
finalVar = '枯木逢春'
console.log(finalVar);

image.png

3.1.2 const 申明的引用类型只要地址值不变,里面的对象可以赋予新的值

const finalObjVar = {title: '万古长存'}
finalObjVar.title = '枯木逢春'
console.log(finalObjVar);

image.png

二、 扩展运算符

2.1 合并数组

let concatArr1 = [1,2,3]
let concatArr2 = [4,5,6]
let concatArr = [...concatArr1,...concatArr2]
console.log(concatArr);

image.png

2.2 合并对象

let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {age: 18}
let mergeObj = {...mergeObj1,...mergeObj2}
console.log(mergeObj);

image.png

2.3 扩展运算符只能拷贝第一层的对象

let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);

image.png

2.4 要想用扩展运算符拷贝多层数据, 需要提前知道数据结构

扩展运算符, 相同的key 后面的会覆盖前面的

let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let newMergeObj2 = {...mergeObj2, friend: {...mergeObj2.friend}}
let mergeObj = {...mergeObj1,...newMergeObj2}
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);

解决知道数据结构前提下的多层拷贝

image.png

2.5 使用JSON 搭配扩展运算符 解决多层拷贝问题、

let mergeObj1 = {name: '孙圣东'}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj = JSON.parse(JSON.stringify(mergeObj))
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);

image.png

2.6 使用JSON 搭配扩展运算符 解决多层拷贝的弊端

  • 无法拷贝函数、undefined等数据
let mergeObj1 = {name: '孙圣东', paly: ()=> {console.log('唱、跳、rap');}, music: null, sex: undefined}
let mergeObj2 = {friend: {name: '刘翔'}}
let mergeObj = {...mergeObj1,...mergeObj2}
mergeObj = JSON.parse(JSON.stringify(mergeObj))
mergeObj2.friend.name = '翔刘'
console.log(mergeObj);

image.png

2.7 自己实现深拷贝

2.7.1 首先实现浅拷贝

  • 拷贝对象
function deepClone(obj) {
    // 1. 空obj 直接返回 ; 因为 null ==  undefined 为true , 所以只需要判断一个就可以了
    if(obj == null) return obj
    // 2. 特殊的 类型
    if( obj  instanceof Date) return new Date(obj)
    if( obj instanceof RegExp) return new RegExp(obj)
    // 3. 如果不是对象,想方法之类的没必要拷贝直接返回
    if( typeof obj  != 'object') return obj

    // 4.是对象, 要不就是{} 对象, 要不就是[] 对象
    // obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
    let cloneObj = new obj.constructor
    for (let key in obj) {
        console.log(key);
        // 只考虑自己的属性,不需要拷贝继承的
        if(obj.hasOwnProperty(key)) {
            cloneObj[key] = obj[key]
        }
    }
    return cloneObj
}

let obj = {name: '张易发', age: 18}

console.log(deepClone(obj));

image.png

  • 拷贝数组
let obj = [{name: '张易发', age: 18}, {name: '鲁班7号', age: 18}] 

console.log(deepClone(obj));

拷贝数组的key就是数组的索引

image.png

  • 这样还是无法拷贝深层对象的
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
let cloneObj = deepClone(obj)
obj.friend.name = '666'
console.log(cloneObj);

image.png

2.7.2 一键开启深拷贝

  • 只需要使用递归的方式调用我们自己封装的深拷贝方法就可以了
function deepClone(obj) {
    // 1. 空obj 直接返回 ; 因为 null ==  undefined 为true , 所以只需要判断一个就可以了
    if(obj == null) return obj
    // 2. 特殊的 类型
    if( obj  instanceof Date) return new Date(obj)
    if( obj instanceof RegExp) return new RegExp(obj)
    // 3. 如果不是对象,想方法之类的没必要拷贝直接返回
    if( typeof obj  != 'object') return obj

    // 4.是对象, 要不就是{} 对象, 要不就是[] 对象
    // obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
    let cloneObj = new obj.constructor
    for (let key in obj) {
        console.log(key);
        // 只考虑自己的属性,不需要拷贝继承的
        if(obj.hasOwnProperty(key)) {
            // ! 递归调用
            cloneObj[key] = deepClone(obj[key]) 
        }
    }
    return cloneObj
}

// let obj = [{name: '张易发', age: 18, friend: {name: '张立'}}, {name: '鲁班7号', age: 18}] 
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
let cloneObj = deepClone(obj)
obj.friend.name = '666'
console.log(cloneObj);

深拷贝成功

image.png

2.7.3 自己实现深拷贝的弊端

  • 循环引用导致的堆栈溢出 创建了这个对象,然后然对象中的某个属性的值等于这个对象,然后再用自己封装的深拷贝方法去深拷贝,就死循环了
let obj ={name: '张易发', age: 18, friend: {name: '张立'}}
obj.circle = obj
let cloneObj = deepClone(obj)
// obj.friend.name = '666'
console.log(cloneObj);

哈哈,我电脑配置还可以, 没有报堆栈溢出的错误,只是一直在执行拷贝方法 image.png

  • 后记: 如何解决这个问题呢?我们先学完set和map回过头再来看看

三、set和map

3.1 set去重集合

  • 可以理解为[] 的升级版, 只不过加了去重方法, 当然既然是升级版,当然多了些好用的api
let collectionOne = new Set([1,2,3,4,1,3]) 
console.log(collectionOne);

image.png

3.2 set的添加和删除

  • add和delete方法
// 1 set 去重
let collectionOne = new Set([1,2,3,4,1,3]) 
// 添加
collectionOne.add('5')
// 删除
collectionOne.delete(1)
console.log(collectionOne);

image.png

3.3 set的遍历

  • forEach方法
collectionOne.forEach(v => {console.log(v);})

image.png

3.4 其他的一些api

  • values获取值, 集合大小size,keys获取所有的键,has集合中有没有某个元素, 不再举例

image.png

3.5 多个数组的结果集

3.5.1 两个数组中的去重数组

let arr1 = [1,2,3,4,3,2,1] 
let arr2 = [4,5,6,5,6,7]

function union(arr1, arr2) {
    let unionArr = new Set([...new Set(arr1), ...new Set(arr2)]) 
    return  [...unionArr]
}

console.log(union(arr1, arr2));

image.png

3.5.2 两个数组取交集数组

function intersection(arr1, arr2) {
    // let unionArr = new Set([...new Set(arr1), ...new Set(arr2)]) 
    let set2 = new Set(arr2);
    let  intersectionArr = [...new Set(arr1)].filter(v => set2.has(v))
    return  intersectionArr
}

console.log(intersection(arr1, arr2));

image.png

3.5.3 两个数组取差集数组

function diff(arr1, arr2) {
    // let unionArr = new Set([...new Set(arr1), ...new Set(arr2)]) 
    let set2 = new Set(arr2);
    let  intersectionArr = [...new Set(arr1)].filter(v => !set2.has(v))
    return  intersectionArr
}

console.log(diff(arr1, arr2));

image.png

3.6、map

  • map是映射关系,一个键对应一个值,键相同,值会覆盖

3.6.1 map的set用法

let map1 = new Map()
map1.set('name','张三')
map1.set('name','李四')
console.log(map1);

image.png

3.6.2 引用类型的key

  • 引用类型的key重置为null,原来的对象还是被map引用
let map1 = new Map()
map1.set('name','张三')
map1.set('name','李四')
let obj = {'name':'jack'}
map1.set(obj,666)
obj = null
console.log(map1);

image.png

3.7、weakMap

3.7.1 WeakMap的key不能是简单类型

let map2 = new WeakMap()
map2.set('name','张三')
console.log(map2);

image.png

引用数据类型可以

let map2 = new WeakMap()
let obj2 = {'name':'jack'}
map2.set(obj2,'123')
console.log(map2);

image.png

3.8、利用map不引用原来对象的特点,完善我们之前的递归方法

function deepClone(obj,hash = new WeakMap()) {
    // 1. 空obj 直接返回 ; 因为 null ==  undefined 为true , 所以只需要判断一个就可以了
    if(obj == null) return obj
    // 2. 特殊的 类型
    if( obj  instanceof Date) return new Date(obj)
    if( obj instanceof RegExp) return new RegExp(obj)
    // 3. 如果不是对象,想方法之类的没必要拷贝直接返回
    if( typeof obj  != 'object') return hash.get(obj)

    // 4.是对象, 要不就是{} 对象, 要不就是[] 对象
    // obj.constructor 是对象的构造方法, new 这个构造方法可以得到一个同类型对象
    if(hash.has(obj)) return obj
    let cloneObj = new obj.constructor
    hash.set(obj,cloneObj)
    for (let key in obj) {
        console.log(key);
        // 只考虑自己的属性,不需要拷贝继承的
        if(obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash) 
        }
    }
    return cloneObj
}

现在就不会死循环了, 返回的是一个循环引用对象

image.png

四、Object.defineProperty

  • 为什么需要使用Object.defineProperty, 我们创建了一个对象{name: '张三'}, 我们既无法对这个对象进行监视,比如谁读了这个对象,谁改了这个对象,能不能改,能不能删某个属性。这时我们就需要用到Object.defineProperty,这个也是Vue2中用到的定义对象的方法

4.1 初体验Object.defineProperty

let obj = {}
Object.defineProperty(obj, 'name', {
    value: '张三'
})
console.log(obj.name);

可以见到,一个对象的属性就被我们设置好了

image.png

4.2 Object.defineProperty的可见性

  • 我们直接打印上述的obj, 并不能看见里面的值
let obj = {}
Object.defineProperty(obj, 'name', {
    value: '张三'
})
console.log(obj);

image.png

  • 如果我们加一个可枚举性, 就可以看见了,默认的enumerable是false
let obj = {}
Object.defineProperty(obj, 'name', {
    enumerable: true,
    value: '张三'
})
console.log(obj);

image.png

4.3 Object.defineProperty的可配置性

  • 比如我们使用 delete 删除obj中的name属性,默认是删除不了的
let obj = {}
Object.defineProperty(obj, 'name', {
    enumerable: true,
    value: '张三'
})
delete obj.name
console.log(obj);

image.png

  • 配置 configurable: true 之后就可以删除了
let obj = {}
Object.defineProperty(obj, 'name', {
    enumerable: true,
    configurable: true,
    value: '张三'
})
delete obj.name
console.log(obj);

image.png

4.4 Object.defineProperty的可写入性

  • 我们直接赋值, name属性并没有被改变
let obj = {}
Object.defineProperty(obj, 'name', {
    enumerable: true,
    configurable: true,
    value: '张三'
})
obj.name = '李四'
console.log(obj);

image.png

  • 开启writable后就可以写入值
let obj = {}
Object.defineProperty(obj, 'name', {
    enumerable: true,
    configurable: true,
    writable: true,
    value: '张三'
})
obj.name = '李四'
console.log(obj);

image.png

4.5 Object.defineProperty的get,set 拦截器

  • 注意writable, value 和 set方法不能同时使用
let obj = {}
let nameV = ''
Object.defineProperty(obj, 'name', {
    enumerable: true, // 可枚举
    configurable: true, // 可配置
    get() {
        return nameV + '---读拦截'
    },
    set(val) {
        nameV = '写拦截---' + val 
    }
   // writable: true,   // 可重写
    // value: '张三'  // 值
})
obj.name = '李四'
console.log(obj.name);

image.png

4.6 setter和getter在对象中的简写

let obj2 = {
    value: '',
    get name () {
        return  this.value + '---读拦截'
    },
    set name (val) {
       this.value =  '写拦截--' + val
    },
}

obj2.name  = 'javascript'

console.log(obj2.name);

image.png

4.7 使用 Object.defineProperty实现vue的的数据劫持

4.7.1 第一层数据劫持

let people = {
    name: '圣光',
    age: 6,
    friend: {
        sex: 'man'
    }
}
// 观察方法
function observe(obj) {
    if( typeof obj !== 'object') return
    for(let key in obj) {
        defineReactive(obj, key , obj[key])
    }
}
// 更新时的操作
function update(val) {
    console.log(val, '更新视图');
}
// 双向数据响应式
function defineReactive(obj, key , value) {
    Object.defineProperty(obj, key , {
        get() {
            return value
        },
        set(val) {
            update(val)
            value = val 
        }
    })
}

observe(people)

people.name = '光圣'

4.7.2 多层数据劫持

如果我们改第二层friend的性别,并没有执行update方法

people.friend.sex = 'woman'

image.png

  • 要想多层数据劫持
  • 对传过来的value 递归劫持

image.png

image.png

  • 如果我们设置的值是一个新的对象,然后改变这个对象的属性,这个对象是不会被观测的
people.friend = {
   age: 18
}

people.friend.age = 19

完成的观测方法:

let people = {
    name: '圣光',
    age: 6,
    friend: {
        sex: 'man'
    }
}
// 观察方法
function observe(obj) {
    if( typeof obj !== 'object') return
    for(let key in obj) {
        defineReactive(obj, key , obj[key])
    }
}
// 更新时的操作
function update(val) {
    console.log(val, '更新视图');
}
// 双向数据响应式
function defineReactive(obj, key , value) {
    observe(value)
    Object.defineProperty(obj, key , {
        get() {
            return value
        },
        set(val) {
            if(value !== val) {
                observe(val)
                update(val)
                value = val 
            }
        }
    })
}

observe(people)

people.friend.sex = 'woman'

people.friend = {
   age: 18
}

people.friend.age = 19

image.png

4.7.2 对数组的劫持

let methods = ['push','slice','pop','sort','reverse','unshift']
methods.forEach(v => {
    let oldMethod = Array.prototype[v];
    Array.prototype[v] = function () {
        update();
        oldMethod.call(this,...arguments);
    }
})

people.likes.push('item...')

五、箭头函数

  • () => {} 没有this 2. 没有arguments

5.1. 箭头函数初体验

let sum = (x,y) => x + y
console.log(sum(1,2));

image.png

5.2. 单个参数, 返回值简写

let increment = x => ++x
 console.log(increment(1));

image.png

5.3. 高阶函数

let sumHoc = (x) => {
    return (y) => {
        return x + y
    }
}
console.log(sumHoc(1)(2));

image.png

5.3.1 高阶函数简写

let sumHoc2 = x => y => x + y
// console.log(sumHoc(1)(2));

5.4. 返回一个对象

let backFunc = age => ({age})
console.log(backFunc(18));

image.png

5.5 测试this指向

let a = 1
let objA = {
    a: 2,
    fn: function () {
        console.log(this.a);
    }
}

objA.fn()

image.png

六、代理Proxy

  • 我们知道vue2中实现原理是Object中defineProperty, vue3中是基于代理Proxy和反射ReflectProxy中提供了, set和get方法去设置值, 在proxy的set中使用Reflect反射的set去设置值, 相比于vue2的实现方式, Object.defineProperty 不能监控数组的变化,需要重写数组的方法。
  • 下面是Proxy, get和set的简单用法
// 更新时的操作
function update(key,val) {
    console.log(key,val, '更新视图');
}

let arr  = [1,2,3,4]
let proxy = new Proxy(arr, {
    set(target,key,value) {
        update(key,value)
        return Reflect.set(target,key,value)
        // target[key] =value
    },
    get(target,key){
        return Reflect.get(target,key)
    }
}) 

proxy.push(1)

console.log( proxy[0] = 100);

console.log(proxy);›

image.png

  • 可见在尾部push添加了一个数组, 在头部通过索引赋值的方式, 添加了一个数, 中间调用update可以拿到更新的数据。

七、Array中的Reduce详解

7.1 reduce 收敛方法

let arr = [1, 2, 3, 4, 5]
let sum = arr.reduce((x, y) => x + y)
console.log(sum);

image.png

7.2 reduce 数组嵌套对象求和, 可以传第二个参数, 初始化的参数

let arr2 = [{ price: 300, count: 2 }, { price: 400, count: 3 }, { price: 500, count: 4 }]
let sum2 = arr2.reduce((x, y) => x + y.price * y.count, 0)

image.png

7.3 reduce 合并两个对应数组为对象

let arrKey = ['name', 'age']
let arrValue = ['zs', 18]

image.png

7.4 reduce 的高级用法

  • 需要使用reduce合并三个函数, 实现下面的效果
function sum3(x, y) {
    return x + y
}
function toUpperCase(str) {
    return str.toUpperCase()
}
function add(str) {
    return `****${str}****`
}

let _r = add(toUpperCase(sum3('zfpw', 'jw')))
console.log(_r);

image.png

接下来使用reduce实现, 不需要我们一个个函数的去调用, 自己定义一个compose方法

function compose(...fns) {
    return function (str1,str2) {
        let lastMethod = fns.pop()
      return  fns.reduceRight((x,y) => {
            return y(x)
        },lastMethod(str1,str2))
    }
}
let reduce_str = compose(add, toUpperCase, sum3)('zfpw', 'jw')
console.log(reduce_str);

image.png

使用es6简写上述方法

function compose2(...fns) {
    return fns.reduce((x,y) => {
        return (...args) => x(y(...args))
    })
}

image.png 继续在简写

let compose = (...fns) => fns.reduce((a, b) => (...args) => a(b(...args)))
  • 浓缩就是精华, react源码中就是这行代码实现的, 大道至简 image.png

7.5 自己实现reduce方法

Array.prototype.reduce = function (callback, prev) {
    for (let i = 0; i < this.length; i++) {
        if (prev == undefined) {
            prev = callback(this[i], this[i + 1], i + 1, this)
            i++;
        } else {
            prev = callback(prev,this[i],i,this)
        }
    }
    return prev
}

let __r =[1,2,3].reduce((a,b) => a +b, 10 )

console.log(__r);

image.png

八 Class方法

  • 就是function的升级版, 不过有些construct构造函数,和新增的构子方法

8.1 先看class的前身function

8.1.1 function的construct、prototype、 proto

function Animal(name) {
    this.name = name
    this.skill = ['eat','foot']
}

Animal.prototype.address = [{location: '地球'}]

let animal1  = new Animal('猴子')
let animal2  = new Animal('小狗')

console.log(animal1.skill === animal2.skill);
console.log(animal1.address === animal2.address);

// 原型链
console.log(animal1.__proto__ === Animal.prototype);

console.log(animal1.constructor === Animal);

console.log(Animal.__proto__ === Function.prototype);
console.log(animal1.__proto__.__proto__ === Object.prototype);
console.log( Object.prototype.__proto__);
  • 哪些相等, 哪些不相等, 你中招了没? image.png

8.1.2 function中的继承

// 1. 继承实例上的属性
function Animal(name) {
    this.name = name
    this.skill = ['eat','foot']
    this.play = ['football']
}

function Tiger(name) {
    this.name = name
    this.skill = ['eat','foot']
    // Animal.call(this)
}

// 2. __proto__

function Tiger(name) {
    this.name = name
    this.skill = ['eat','foot']
}

let tiger = new Tiger('小老虎')
Tiger.prototype.__proto__ = Animal.prototype;


8.1.3 Object.create

Tiger.prototype = Object.create(Animal.prototype);

console.log(tiger.play);

8.2 再来看class

8.2.1 new Class 、 实例方法、静态方法

class Animal  {
  
  // 构造器
  constructor(name) {
      // 实例方法
      this.name = name
      this.eat = '吃肉'
  }
  // 原型方法
  sayHellow() {
    console.log('你好');
  }
  // es7支持静态属性
  static flag = '666'
  static flagFunc () {
    return '静态方法'
  }
}

image.png

8.2.2 继承

  • 这里的继承超级简单, extend就可以了, 不需要像function一样,通过call去改变this指向
class Tiger extends Animal {
  constructor(name){
    super(name)
  }
}
let tiger = new Animal()
// 使用实例方法
console.log(tiger.eat); 
// 使用静态方法
console.log(Tiger.flagFunc());