玩转ES6(四)Set、Map、Class类和decorator 装饰器

简介: 玩转ES6(四)Set、Map、Class类和decorator 装饰器

Set


set是放不重复的项,也就是去重

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

Set有几个常用的方法,add clear delete entries

// add
let set = new Set([1,2,3,4,3,2,1])
set.add(5)
console.log(set) // Set { 1, 2, 3, 4, 5 }
// 添加一个已有的值,则不会添加进去
set.add(1)
console.log(set) // Set { 1, 2, 3, 4, 5 }
// delete
set.delete(3)
console.log(set) // Set { 1, 2, 4, 5 }
// entries
console.log(set.entries()) // SetIterator { [ 1, 1 ],
                                            [ 2, 2 ],
                                            [ 4, 4 ], 
                                            [ 5, 5 ] }
// clear
set.clear()
console.log(set) // Set {}

Set常用于去重(并集)

function distinct(arr1,arr2){
    return [...new Set([...arr1,...arr2])]
}
let arr = distinct([1,2,3],[2,3,4,5])
console.log(arr) // [1,2,3,4,5]

求交集

function intersect(arr1,arr2) {
  // 利用Set里的方法has,来判断new Set(arr2)中是否含有item,
  // 如果含有,那么则是true,当为true时,filter函数则会保留该项
  // 如果没有,则是false,当为false时,filter函数则不会保留该项
  return arr1.filter(item => new Set(arr2).has(item))
}
console.log(intersect([1,2,3],[2,3,4,5])) // [2,3]

求差集

function difference(arr1,arr2){
    return arr1.filter(item => !new Set(arr2).has(item))
}
console.log(difference([1,2,3],[2,3,4,5])) // [1]

Map


也是集合,主要格式是 key => value,同样是不能放重复的key

// 如果放重复的key会怎样呢?会被覆盖
let map = new Map()
map.set('name','邵威儒')
map.set('name','swr')
console.log(map) // Map { 'name' => 'swr' }
// 取的话用get
map.get('name') // 'swr'
// 删的话用delete
map.delete('name')
console.log(map) // Map {}
// 很多方法和set差不多
let map = new Map()
map.set('name','邵威儒')
map.set('age',28)
// 一般使用for ... of ... 遍历
for(let [key,value] of map.entries()){
    console.log(key,value) // name 邵威儒
                           // age 28
}
// 也可以用forEach
map.forEach(item => {
    console.log(item) // 邵威儒
                      // 28
})

Set我用得最多的就是去重了,实际上Set Map我在开发中还是比较少会用到


Class类


核心还是继承,而Class我认为是es5面向对象的语法糖。

在看Class之前建议看一下js的面向对象 https://juejin.im/post/5b8a8724f265da435450c591

看完后,我们开始进入es6的class

// 语法
// 声明一个类
Class Person{ 
    // 在constructor中写实例属性、方法
    constructor(){ 
        this.name = "邵威儒" // 实例属性
        this.say = function(){ // 实例方法
            console.log("我是实例方法上的say")
        }
    }
    // 原型方法
    eat(){
        console.log("我是原型方法上的eat")
    }
    // 静态方法 也会被继承
    static myName(){ 
        return "我是静态方法的myName"
    }
    // 在es6中静态属性不能这样写 static name = "邵威儒"  这样会报错
    // 在es7中可以这样写static name = "邵威儒"
}
let p = new Person() // new一个对象
console.log(p.name) // 邵威儒
p.eat() // 我是原型方法上的eat
console.log(Person.myName()) // 我是静态方法的myName

那么子类怎么继承父类呢?

// 父类
class Person{
    constructor(){
        this.name = "swr"
    }
    static myName(){
        return "Person"
    }
    eat(){
        console.log('eat')
    }
}
// 子类
// 子类Child继承父类Person
// class Child extends Person实际上相当于
// Child.prototype = Object.create(Person.prototype)
// 打印出来可以看到
// console.log(Child.prototype === Person.prototype) // false
// console.log(Child.prototype.__proto__ === Person.prototype) // true
class Child extends Person{
    constructor(){
        super() // 此处的super相当于Person.call(this)
    }
}

前面我说了Class就类型es5面向对象的语法糖,为什么这样说呢?

接下来我们看一下通过es5怎么模拟实现一个Class(可以用babel转一下,看看转为es5的代码是怎样的)

let Child = (function(){
    // 这种闭包的写法,好处可以把作用域封闭起来 
    // 在Child构造函数外写一系列变量
    // 如 let name = "邵威儒";let age = 28 等等…
    function Child(){
        console.log(this) // 打印内部this,看看指向哪里
    }
    return Child
})()
// 通过直接调用函数,看看什么情况
console.log(Child()) // 此时里面的this是指向全局的
// 通过new来生成对象
console.log(new Child()) // 此时里面的this是指向这个new出来的新对象

在es6中,不使用new来调用类,会报错 ClassconstructorChildcannot be invoked without'new'

class Child {
}
Child() // TypeError: Class constructor Child cannot be invoked without 'new'

也就是说,想在es5中,模拟类,那么没使用new来调用构造函数时,也要抛出一个错误,那么我们会想到类的校验方法

// * 1.声明一个类的校验方法
// *   参数一:指向的构造函数
// *   参数二:被调用时,this的指向
function _classCallCheck(constructor,instance) {
  // * 2.如果这个instance指向的不是constructor的话,意味着不是通过new来调用构造函数
  if(!(instance instanceof constructor)){
    // * 3.不满足时,则抛出异常
    throw TypeError("Class constructor Child cannot be invoked without 'new'")
  }
}
let Child = (function(){
  function Child(){
    // * 4.在调用该构造函数的时候,先执行以下类的校验方法
    _classCallCheck(Child,this)
  }
  return Child
})()
// 不通过new调用时,会报错
Child() // 报错 Class constructor Child cannot be invoked without 'new'

那么我们类上,有实例属性方法、原型属性方法、静态属性方法

function _classCallCheck(constructor,instance) {
  if(!(instance instanceof constructor)){
    throw TypeError("Class constructor Child cannot be invoked without 'new'")
  }
}
// * 4.描述器 descriptor
//     参数一:构造函数
//     参数二:描述原型属性方法数组
//     参数三:描述静态属性方法数组
function _createClass(constructor,protoProperties,staticProperties) {
  // * 5.如果protoProperties数组有数组成员
  if(protoProperties.length){
    // * 6.遍历
    for(let i = 0;i < protoProperties.length;i++){
      // * 7.通过Object.defineProperty把属性方法添加到constructor的原型对象上
      Object.defineProperty(constructor.prototype,protoProperties[i].key,{
        // * 8.利用扩展运算符,把{key:"say",value:function(){console.log("hello swr")}}展开
        ...protoProperties[i]
      })
    }
  }
}
// * 1.实例属性方法、原型属性方法、静态属性方法
//     在es6中,原型属性方法不是通过prototype实现的
//     而是通过一个叫描述器的东西实现的
let Child = (function(){
  function Child(){
    _classCallCheck(Child,this)
    // * 2.实例属性方法还是写在构造函数内
    this.name = '邵威儒'
  }
  // * 3.描述器 descriptor
  //     参数一:构造函数
  //     参数二:描述原型属性方法
  //     参数三:描述静态属性方法
  _createClass(Child,
    [
      {key:"say",value:function(){console.log("hello swr")}},
      {key:"myname",value:"iamswr"}
    ],
    [
      {key:"total",value:function(){return 100}}
    ])
  return Child
})()
// * 9.最后我们new一个对象出来,并且调用原型属性方法,看能否调用成功
let c = new Child()
c.say() // 'hello swr'    调用成功

接下来,我们把静态方法,staticProperties也处理一下,

此时会发现,protoProperties和staticProperties都会遍历然后使用Object.defineProperty

那么我们封装一个方法进行处理

function _classCallCheck(constructor,instance) {
  if(!(instance instanceof constructor)){
    throw TypeError("Class constructor Child cannot be invoked without 'new'")
  }
}
// * 1.封装一个方法,处理遍历和Object.defineProperty
function _defineProperty(target,properties) {
  for (let i = 0; i < properties.length; i++) {
    Object.defineProperty(target, properties[i].key, {
      ...properties[i]
    })
  }
}
function _createClass(constructor,protoProperties,staticProperties) {
  if(protoProperties.length){
    _defineProperty(constructor.prototype, protoProperties)
  }
  // * 2.如果staticProperties数组有数组成员
  if(staticProperties.length){
    // * 3.静态方法需要添加在constructor
    _defineProperty(constructor, staticProperties)
  }
}
let Child = (function(){
  function Child(){
    _classCallCheck(Child,this)
    this.name = '邵威儒'
  }
  _createClass(Child,
    [
      {key:"say",value:function(){console.log("hello swr")}},
      {key:"myname",value:"iamswr"}
    ],
    [
      {key:"total",value:function(){return 100}}
    ])
  return Child
})()
let c = new Child()
c.say()
// * 4.最后我们通过Child来调用静态方法
console.log(Child.total())  // 100

这样完成了一个雏形,但是还有最重要的继承还没实现,接下来我们实现继承。

function _classCallCheck(constructor,instance) {
  if(!(instance instanceof constructor)){
    throw TypeError("Class constructor Parent cannot be invoked without 'new'")
  }
}
function defineProperty(target,properties) {
  for (let i = 0; i < properties.length; i++) {
    Object.defineProperty(constructor.prototype, properties[i].key, {
      ...properties[i]
    })
  }
}
function _createClass(constructor,protoProperties,staticProperties) {
  if(protoProperties.length){
    defineProperty(constructor.prototype, protoProperties)
  }
  if(staticProperties.length){
    defineProperty(constructor, staticProperties)
  }
}
// * 6.继承方法
function _inherits(subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }
  // * 7.把子类的原型对象指向新的原型对象 组合寄生式继承 继承原型属性方法
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass, // 把constructor指向子类
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  // * 8.继承父类的静态方法
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
function _possibleConstructorReturn(self, call) {
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}
// * 1.父类
let Parent = (function(){
  function Parent(){
    _classCallCheck(Parent,this)
    this.name = '父类实例属性'
  }
  _createClass(Parent,
    [
      {key:"say",value:function(){console.log("父类原型方法say")}},
      {key:"myname",value:"父类原型属性myname"}
    ],
    [
      {key:"total",value:function(){return 100}}
    ])
  return Parent
})()
// * 2.子类
let Child = (function (Parent) { // * 4.这里接收传进的参数 父类
  // * 5.写一个继承方法,继承原型属性方法和静态方法
  _inherits(Child, Parent);
  function Child() {
    _classCallCheck(Child, this)
    // * 9.继承实例属性方法
    return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
  }
  return Child
})(Parent) // * 3.在这里通过传参,把父类传进去
let c = new Child()
console.log(c.name) // '父类实例属性'

这样就可以用es5模拟es6的class了,会发现其实es6的class是es5面向对象的一个语法糖,经过这样解剖一下源码实现,会对class有更深刻的理解。

还有个问题,我们在react中,会这样写class

class Parent{
    name = "邵威儒"
}
// 在正常情况下会报错,但是因为平时项目是使用了babel插件
// 会帮我们自动编译语法,这种写法目前还处于草案阶段
// 上面的写法实际等价于下面的写法
class Parent{
    constructor(){
        this.name = "邵威儒"
    }
}

decorator 装饰器


装饰器是用来装饰类的

class Person {
}
function myFunction(target) {
  target['myName'] = "邵威儒"
}
myFunction(Person)
console.log(Person['myName']) // 邵威儒

这种写法,相当于给Person这个类添加了myName的属性

那么换成decorator该怎么写呢?

// 在类前面写@myFunction
@myFunction
class Person {
}
function myFunction(target) {
  target['myName'] = "邵威儒"
}
// myFunction(Person)  这一步可以不写
console.log(Person['myName']) // 邵威儒

那么我们该怎么给myName传参呢?

@myFunction('邵威儒')
class Person {
}
function myFunction(value) {
  return function(target){ // target代表的是类
      target['myName'] = value
  }
}
console.log(Person['myName']) // 邵威儒

修饰符也可以修饰类的方法

class Person {
    @myFunction
    say(){}
}
// 如果修饰的是方法
// 参数一:是Person.prototype
// 参数二:是say
// 参数三:是描述器
function myFunction(target,key,descriptor){
    // 给这个类添加一个原型属性
    Object.assign(target,{name:"邵威儒"})
}
let p = new Person()
console.log(p.name) // 邵威儒

修饰符也可以修饰类的属性,比如我们有个不可修改的属性

class Person {
    @onlyRead
    name = '邵威儒'
}
function onlyRead(target,key,descriptor){
    descriptor.writable = false
}
let p = new Person()
p.name = 'swr' // 报错,不能赋值

decorator的用处很多,包括重写函数

function myFunction(target,key,descriptor){
    // 拿出原本的函数
    let fn = descriptor.value
    // 并且在原有的fn上加上自己的业务逻辑,比如console.log('哈哈哈')
    descriptor.value = function(){
        // 这里写我们需要加入的内容
        console.log('哈哈哈')
        // 这里执行原来的fn
        fn()
    }
}

装饰器经常在react中使用~其实decorator是简写,逼格高一些。


目录
相关文章
|
2月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
53 6
|
21天前
|
算法
你对Collection中Set、List、Map理解?
你对Collection中Set、List、Map理解?
55 18
你对Collection中Set、List、Map理解?
|
14天前
|
存储 缓存 安全
只会“有序无序”?面试官嫌弃的List、Set、Map回答!
小米,一位热衷于技术分享的程序员,通过与朋友小林的对话,详细解析了Java面试中常见的List、Set、Map三者之间的区别,不仅涵盖了它们的基本特性,还深入探讨了各自的实现原理及应用场景,帮助面试者更好地准备相关问题。
51 20
|
1月前
|
存储 C++ 容器
【C++】map、set基本用法
本文介绍了C++ STL中的`map`和`set`两种关联容器。`map`用于存储键值对,每个键唯一;而`set`存储唯一元素,不包含值。两者均基于红黑树实现,支持高效的查找、插入和删除操作。文中详细列举了它们的构造方法、迭代器、容量检查、元素修改等常用接口,并简要对比了`map`与`set`的主要差异。此外,还介绍了允许重复元素的`multiset`和`multimap`。
30 3
【C++】map、set基本用法
|
1月前
|
存储 算法 C++
【C++】unordered_map(set)
C++中的`unordered`容器(如`std::unordered_set`、`std::unordered_map`)基于哈希表实现,提供高效的查找、插入和删除操作。哈希表通过哈希函数将元素映射到特定的“桶”中,每个桶可存储一个或多个元素,以处理哈希冲突。主要组成部分包括哈希表、哈希函数、冲突处理机制、负载因子和再散列,以及迭代器。哈希函数用于计算元素的哈希值,冲突通过开链法解决,负载因子控制哈希表的扩展。迭代器支持遍历容器中的元素。`unordered_map`和`unordered_set`的插入、查找和删除操作在理想情况下时间复杂度为O(1),但在冲突较多时可能退化为O(n)。
22 5
|
2月前
|
存储 Java API
详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
【10月更文挑战第19天】深入剖析Java Map:不仅是高效存储键值对的数据结构,更是展现设计艺术的典范。本文从基本概念、设计艺术和使用技巧三个方面,详细解析HashMap、TreeMap、LinkedHashMap等实现类,帮助您更好地理解和应用Java Map。
71 3
|
2月前
|
存储 算法 Java
Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
45 4
|
2月前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。
【10月更文挑战第14天】从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其独特的“不重复性”要求,彻底改变了处理唯一性约束数据的方式。本文深入探讨Set的核心理念,并通过示例代码展示了HashSet和TreeSet的特点和应用场景。
23 2
|
2月前
|
存储 JavaScript 前端开发
Set、Map、WeakSet 和 WeakMap 的区别
在 JavaScript 中,Set 和 Map 用于存储唯一值和键值对,支持多种操作方法,如添加、删除和检查元素。WeakSet 和 WeakMap 则存储弱引用的对象,有助于防止内存泄漏,适合特定场景使用。
|
2月前
|
存储 缓存 Java
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
【用Java学习数据结构系列】HashMap与TreeMap的区别,以及Map与Set的关系
44 1