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是简写,逼格高一些。