ES6新特性(接着上一篇,进阶)

155 阅读10分钟

前言: 在上一节,我们讲述了一下ECAMScript更新的一些基础内容,现在,接着上一节接着说一说在ES6中较为高阶的一些东西。

本节目标:

  • 1、对于Object对象的新增内容
  • 2、promise的见解
  • 3、defindeproperty类和proxy类比较
  • 3、新增数据类型Symbol
  • 4、class类
  • 5、新的数据结构Set和Map
  • 6、for..of..的实现原理
  • 7、迭代器
  • 8、生成器

内容讲解:

  1. 对于Object对象的新增内容

assign方法:将源对象合并到目标对象中

const obj = {
    name: 'df'
    age: 12
}
const objDog = {
    type: 'dog'
}
const action = { say : 'www'}
const animal = Object.assign(objDog, obj, action) // 后面也可以跟多个参数,都会赋值到第一个目标对象中
console.log(animal) // {name: 'df',age: 12,type: 'dog',say: 'www'}

具体的应用场景是:

1、如果一个对象(o1)的属性引用了另一个对象(o2)的值,在修改o1的时候,也会修改o2的值,这里用到assign方法,将o2的值赋值给o1的话,两个就相互不影响了。

2、从上面这句话就可以看出,assign是对于对象,是进行浅拷贝。


  1. defindeproperty类和proxy类比较

   proxy对象是对之前的defineProperty的一次强大升级,这个对象相当于对象的一个门卫,对于对象的读取操作以及delete都会做到监听和过滤。  

const obj = {
    name: 'df',
    age: 18
}
//参数: t1 -- 目标对象  t2 -- {},方法的对象
const proxyObject = new Proxy(obj, {
    get (targe, property) { // 对于读取对象属性进行监视
        console.log(targe, property);
        return property in obj ? obj[property] : 'error'
    },
    set (targe, property,val) { // 对于函数属性的写入进行监视 
        console.log(targe, property,val);
        if (property === 'age') {
            if(!Number.isInteger(val)) throw new TypeError(`${val} is not number`)
        }
    }
})  
console.log(proxyObject.name);  // { name: 'df', age: 18 } name   df
proxyObject.age = 123 // { name: 'df', age: 18 } age 123
console.log(proxyObject.age);//  18

对于Object的proxy 和 defineProperty 的对比:

  proxy中有对delete操作的监听,而defineProperty没有。

 proxy更好的支持对数组对象的监听,对原数组方法进行重写的方式实现。(vue.js所使用的方式)。

 proxy是以非侵入的方式监管了对象的读写,即对已经定义好的操作,不需要对对象本身进行过多的操作就可以监听到读写。

const list = []

const listProxy = new Proxy(list, {
    set (targe, property, val) {
        console.log(targe, property, val);
        targe[property] = val // 这里会自动监听到数组的索引进行赋值
        return true // 不写返回值会报错
    }
})

listProxy.push(100)   // [] 0 100    [ 100 ] length 1

  1. Reflect类的使用

Reflect是Object对象的一个静态类,里面有处理对象的操作的14个方法,这14个方法就是proxy对象的默认实现,即Proxy内部默认调用了Reflect对象的方法

Reflect存在的意义在于统一提供了一套操纵对象的API


const obj = {
    name: 'df',
    age: 18
}
console.log(Reflect.has(obj, 'age'));  // 类似  'age' in obj 
console.log(Reflect.defineProperty(obj, 'age'));  // 类似  delete obj.age
console.log(Reflect.ownKeys(obj, 'age'));  // 类似  Object.keys(obj)

// 之前的方法会被慢慢取代掉,建议使用Reflect的方法


  1. Promise

romise是ES6新增的链式调用所使用的类,多数用于异步请求,属于微任务类型,想了解更多,去看我的关于Promise的文章。


5、Class(类)

1、在之前的js中,我们会通过定义函数的方式进行构造函数的创建,在通过new 一个构造函数的类来实现一种独立的数据类型(Class)。


function Person (name) {
    this.name = name
    this.say = function (){
        console.log('这是一个构造函数方法', this)
    }
}
// 通过定义在原型链上的方式来进行类之间数据共享

const person = new Person('df')
person.say()
person.property.action = function () {console.log('原型链上的数据共享')}


现在,ES6中,定义了一个class关键字来定义类,通java、php这些面向对象语言一样中类的功能类似(强调,JavaScript是面向函数式编程)。

class Dog {
        constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
            this.name = name
            this.age = age
        }
        say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
            console.log('这是一个calss创建的类', this)
        }
    }

2、通过静态方法来创建对象,静态方法只能函数自己来调用,它的实例对象不能调用,而实例方法只能通过它的实例对象来调用,它本身不能调用。实例方法都是定义在类原型上的,而通过关键字static修饰后,这个方法就变成了静态方法,只能通过类名去调用了。


    class Dog {
        constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
            this.name = name
            this.age = age
        }
        say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
            console.log('这是一个calss创建的类', this)
        }
        static action (name,age) {
            console.log('这是Dog类的静态方法')
            return new Dog(name, age)
        }
    }

    const ha = Dog.action('哈士奇', 3)
    console.log(ha);  // 这是Dog类的静态方法    Dog { name: '哈士奇', age: 3 }

3、 静态方法中的this是是指向当前类型,而不是当前对象。

4、使用extends关键字来实现继承一个父类,通过super关键字来调用继承的父类的属性和方法

     class Dog {
        constructor (name,age) { // 这是构造器,对象一经创建就立即执行的类,里面的参数就是new对象时候传入的参数
            this.name = name
            this.age = age
        }
        say () { // 之前都是将方法放在原型上或者属性上,不能直接定义,现在可以了
            console.log('这是一个calss创建的类', this)
        }
        static action (name,age) {
            console.log('这是Dog类的静态方法')
            return new Dog(name, age)
        }
    }

    class MinDog extends Dog {
        constructor (name,age,type) {
            super(name,age) // super关键字是用来调用父类 的,这里相当于在调用父类的构造方法
            this._type = type
        }
        hello () {
            super.say()
            console.log('上一句是调用父类super打印出来的');
        }
    }

    const ha = new MinDog('哈士奇', 3, 'man')
    ha.hello() // 这是一个calss创建的类 MinDog { name: '哈士奇', age: 3, _type: 'man' }   // 上一句是调用父类super打印出来的


6、set map

set 数据结构

你可以将set结构看作是一个array的结构,但是区别是set的值都是唯一的值,不能重复,因此set经常应用的场景是数组去重。

set的方法:

1、add 因为该方法的返回值是一个set类型,所以可以链式调用。

2、size 相当于array 的length属性

3、delete 删除值

4、clear 清除set

5、has 类似于array的include

6、forEach 循环

const arr = new Set() // 参数可以放入数组array

// add 方法 返回 set,因此可以链式调用,且有重复的话会省略不加入
arr.add(1).add(2).add(2) // [1,2]

// set 不等同于arr 是一个伪数组结构,通过Array的from方法可以将其变成数组结构
console.log(Array.from(arr));
console.log(...arr); // 通过解构也可以

// size -> length
console.log(arr.size); // 2

// has 方法  检查set中是否包含 对应值

console.log(arr.has(i)); // true

// delete  删除
console.log(arr.delete(1)); // [2]

// clear  清空set
console.log(arr.clear); // []

map 数据结构

map是<key, value>结构的真正意义上的实现,这里的key是可以存储任意类型的数据,而在object对象中,对于key值,即使我们不是存放的String类型的数据,它也会自动给我们使用toString的方法。


const obj = {}
obj[true] = '123'
obj[1] = 1
obj[{a: 1}] = 'a = 1'
 // 打结果都是使用过toString方法的键
console.log(Object.keys(obj)); //  [ '1', 'true', '[object Object]' ]

map 的使用方法:

set 方法 存数据

get方法 获取方法

delete 方法 删除键值对

has 方法 判断是否存在

clear 方法 清楚map

forEeach方法 遍历map (value, key)


Symbol

Symbol数据类型,符号数据类型,它的每一次创建都是独一无二的,其参数是为其添加说明内容,每一个symbol都是独一无二 ,不相等的,常用的场景是为对象进行属性名的命名,那么现在对象就有String和Symbol两个格式的属性命名。

const s =  Symbol()
console.log(s); // Symbol()  -- 这是一个值,Symbol值
console.log(typeof s); // Symbol
console.log(Symbol() === Symbol()); // false    

const obj = {}
obj[Symbol()] = 213
obj[Symbol('foo')] = 123
obj[Symbol(123)] = 321

console.log(obj); // { [Symbol()]: 213, [Symbol('foo')]: 123, [Symbol(123)]: 321 }

Symbold特点:

    const s =  Symbol()
    // 特点一:每一个Symbol值都是独一无二的
    console.log(Symbol() === Symbol() );  // false

    // 特点二: 通过全局注册或者它的内置静态方法for可以拿到同一份Symbol()值
        const s1 = Symbol()
        console.log(s1 === s1); // true
        // for方法是用来维护同一个字符串的参数的Symbol值,传入的参数如果不是String类型,也会在内部转化为String
        const s2 = Symbol.for(123)
        const s3 = Symbol.for('123')
        console.log(s2 === s3); // true

        // 特点三: Symbol中自带了很多自定义常量,用于实现对象的自定义属性标签
        const obj= {}
        console.log(obj.toString()); // [object Object]这个字符串就是属性标签
        // 通过Symbol的自带常量可以为这些属性从新定义 (这块内容不太懂,后续再理解)
        obj[Symbol.toStringTag] = 'XXX'
        console.log(obj.toString()); // [object XXX]


        // 特点四:通过Symbol定义的内置属性是无法通过 forEach、Object.keys、json.stringify 方式拿到值的 -- 这些都是获取到的字符串属性名
        // 只有通过Object.getOwnPropertySymbol可以取到全部symbol值


for ... of ...结构

1、for 循环,使用于遍历数组,而且可以使用break中途退出

2、for ... in ... 和方法foerEach 循环键值对数组或对象,但是不能brake

3、for ... of ... ,ES6新增的循环方式,可以遍历所有的可循环数据,并且有brake中途退出,作为以后主要使用的方式,它可以遍历数组、对象、map、set、伪数组等数据结果。


     // 数组
    const arr = [1,2,3,4]
    for( i of arr) {
        console.log(i);
    }
    // set

    const set1 = new Set(arr)
    for( i of set1) {
        console.log(i);
    }

    // map
    const m = new Map()
    m.set('name','df')
    m.set('age',18)
    for(const [i,key] of m) {
        console.log(i,key);
    }
    // 对象
    const obj = {
        name: 'jxl',
        age: 23
    }
    // 这里的循环不能执行,是因为Object没有实现Symbol.iterator的可迭代接口,详情看下面迭代器的介绍


迭代器和生成器

迭代器是什么,它是一种模式。

对于上面for...of...的方法可迭代的实现,主要是通过它原型对象上实现的Symbol.iterator接口来完成的。


     // 这里我们创建一个数组
            const arr = [1,2,3]
            const t = arr[Symbol.iterator]() //调用iterator方法可以得到该方法的执行体,里面有一个next()方法,这个方法就是循环中指向下一个地址的指针

            console.log(t.next()) // {value: '1', done: false} // 这里得到的对象就是每次循环的对象值(value)和是否完成循环(done)

这里为Object添加一个Symbol.iterator的方法,让其实现循环,要是没有Symbol.iterator方法,只要我们手动去实现该方法,就可以实现迭代器,继而进行遍历。

    const obj ={
        arr:  ['df', 'foo', 'fn'],
        // 这里其实也是利用了闭包的机制,进行循环处理的。iterator的方法可以根据结构自己进行循环
        [Symbol.iterator]: function () {
             const self = this // 因为下面是函数,this指向会变,所以临时保存
             let index = 0 // 记录完成循环个数
            return { // 返回一个迭代器对象
                 next: function () { // 返回一个可迭代函数
                     const result =  { // 迭代结果对象
                         value: self.arr[index],
                         done: self.arr.length <= index
                     }
                     index++
                     return result
                 }   
            }
        }
    }

    for (const i of obj) {
        console.log(i,'循环体哈哈哈');
    }

生成器 (Generation)

这里先说一个生成器函数,function * fn () {} 的格式,这种函数可以解决异步编程带来的代码嵌套的问题,其实,它内部就实现了Symbol.iterator方法的实现,对于自定义对象的Symbol.iterator方法,可以通过生成器去实现。

    const obj = {
        name: ['df', 'foo', 'fn'],
        age: [12, 16, 18],
        [Symbol.iterator]: function * () { // 这里通过 generator 函数进行 Symbol.iterator 的定义
            const tol = [...this.age, ...this.name]
            for ( const i of tol) {
                yield i
            }
        }
    }

    for (const i of obj ) {
        console.log(i,'generator');
    }

结束

到这里,ES6的常用特性包括一些代码的实现就算是总结完了,在平常的工作或者学习中,要多使用才能尽快的掌握。