ES6供应了完整的class语法,因而,能够异常轻易的运用extends关键字对类举行扩大(继承)。为了完成类的一些基本功用,我撰写了下面这个类,用以被其他类继承,具有这个基本类的基本功用。
源码
var events = {}
var data = {}
var copyProperty = function(Target, Source) {
for(let key of Reflect.ownKeys(Source)) {
if(key !== 'constructor' && key !== 'prototype' && key !== 'name') {
let descriptor = Object.getOwnPropertyDescriptor(Source, key)
Object.defineProperty(Target, key, descriptor)
}
}
}
export default class ClassBase {
constructor(...args) {
events[this] = {}
data[this] = {}
this.call(this.initialize, ...args)
return this
}
/**
* @desc initialize class method, be called every time class is initialized
* Notice: never use constructor when extends by a sub class
*/
initialize() {}
/**
* @desc get data from data manager
* @param string key: the key of data, you can use '.' to get tree info. e.g. .get('root.sub.ClassBaseMix') => .get('root').sub.ClassBaseMix
*/
get(key) {
let target = data[this]
if(key.indexOf('.') === -1) return target[key]
let nodes = key.split('.').filter(item => item && item !== '')
if(nodes.length === 0) return
for(let node of nodes) {
if(typeof target !== 'object' || !target[node]) return
target = target[node]
}
return target
}
/**
* @desc save data to data manager
* @param string key: the key of data, use '.' to set tree structure. e.g. .set('root.sub.ClassBaseMix', 'value') => .get('root').sub.ClassBaseMix = 'value'
* @param mix value: the value to save
* @param boolean notify: whether to trigger data change event
*/
set(key, value, notify = true) {
if(!data[this]) data[this] = {}
let target = data[this]
if(key.indexOf('.') === -1) {
target[key] = value
if(notify) {
this.trigger('change:' + key, value)
}
return this
}
let nodes = key.split('.').filter(item => item && item !== '')
if(nodes.length === 0) return
let lastKey = nodes.pop()
for(let node of nodes) {
if(typeof target !== 'object') return
if(!target[node]) {
target[node] = {}
}
target = target[node]
}
target[lastKey] = value
if(notify) {
nodes.push(lastKey)
let event = nodes.shift()
this.trigger('change:' + event, value)
while (nodes.length > 0) {
event += '.' + nodes.shift()
this.trigger('change:' + event, value)
}
}
return this
}
/**
* @desc call some function out of this class bind with this
* @param function factory: the function to call
* @param args: arguments to pass to function be called
*/
call(factory, ...args) {
factory.apply(this, args)
return this
}
/**
* @desc bind events on Instantiate objects
* @param string evts: events want to bind, use ' ' to split different events, e.g. .on('change:data change:name', ...)
* @param function handler: function to call back when event triggered
* @param number order: the order to call function. functions are listed one by one with using order.
*/
on(evts, handler, order = 10) {
if(!events[this]) events[this] = {}
evts = evts.split(' ')
let target = events[this]
evts.forEach(evt => {
if(!target[evt]) {
target[evt] = {}
}
let node = target[evt]
if(!node[order]) node[order] = []
let hdles = node[order]
if(hdles.indexOf(handler) === -1) hdles.push(handler) // make sure only once in one order
})
return this
}
/**
* @desc remove event handlers
* @param string event: event name, only one event supported
* @param function handler: the function wanted to remove, notice: if you passed it twice, all of them will be removed. If you do not pass handler, all handlers of this event will be removed.
*/
off(event, handler) {
if(!handler) {
events[this][event] = {}
return
}
let node = events[this][event]
if(!node) return
let orders = Object.keys(node)
if(!orders || orders.length === 0) return
if(orders.length > 1) orders = orders.sort((a, b) => a - b)
orders.forEach(order => {
let hdles = node[order]
let index = hdles.indexOf(handler)
if(index > -1) hdles.splice(index, 1) // delete it/them
if(hdles.length === 0) delete node[order]
})
return this
}
/**
* @desc trigger events handlers
* @param string event: which event to trigger
* @param args: arguments to pass to handler function
*/
trigger(event, ...args) {
let node = events[this][event]
if(!node) return
let orders = Object.keys(node)
if(!orders || orders.length === 0) return
if(orders.length > 1) orders = orders.sort((a, b) => a - b)
let handlers = []
orders.forEach(order => {
let hdles = node[order]
handlers = [...handlers, ...hdles]
})
handlers.forEach(handler => {
if(typeof handler === 'function') {
// this.call(handler, ...args) // 会绑定this
handler(...args) // 不会绑定this,实在能够在on的时刻用bind去绑定
}
})
return this
}
/**
* @desc mix this class with other classes, this class property will never be overwrite, the final output class contains certain property and all of this class's property
* @param Classes: the classes passed to mix, previous class will NOT be overwrite by the behind ones.
*/
static mixin(...Classes) {
class ClassBaseMix {}
Classes.reverse()
Classes.push(this)
for(let Mixin of Classes) {
copyProperty(ClassBaseMix, Mixin)
copyProperty(ClassBaseMix.prototype, Mixin.prototype)
}
return ClassBaseMix
}
/**
* @desc mix other classes into this class, property may be overwrite by passed class, behind class will cover previous class
*/
static mixto(...Classes) {
class ClassBaseMix {}
Classes.unshift(this)
for(let Mixin of Classes) {
copyProperty(ClassBaseMix, Mixin)
copyProperty(ClassBaseMix.prototype, Mixin.prototype)
}
return ClassBaseMix
}
toString() {
return this.constructor.name
}
}
你能够在这里浏览每一个要领的申明,这里简朴的申明一下它们的各自用处。
initialize要领
用来替代constructor作为实例化要领,虽然在class中运用constructor并没有什么问题,然则人人好像商定熟成的运用initialize要领替代它。所以constructor要领作为一个最肇端的要领,不该该在子类中出现被掩盖,因为这里会用它来挪用initialize要领,一旦被掩盖,子类中就不能自动挪用initialize要领了。
setter和getter
用以猎取和设置私有属性,固然也能够保存其他任何数据范例。这些数据都会被保存在attributions这个变量内里,然则它仅在这个文档中可见,所以不会被外部接见,只要经由过程get和set要领才接见。
而且功用有所提拔,传入的变量支撑用点隔开来示意父子关联。比方set(‘book.name’, ‘News’),如许能够直接设置book对象的name属性,用get(‘book’).name = ‘News’也能够到达这个结果(机能更高),但形式上没有前者悦目,运用get(‘book.name’)这类写法也更文雅。
on、off和trigger
和大多数事宜绑定和触发一样,这三个要领也是完成这个功用的。on绑定事宜,传入一个回调函数,然则这里还给on加了一个功用,就是第三个参数划定回调函数实行的递次。比方当你给同一个事宜传入了多个回调函数,怎样来划定它们之间的递次呢?经由过程传入第三个参数即可,数字越小的,越靠前实行。
off在解绑事宜的时刻,也有一个比较好的功用,能够只解绑某一个回调函数,但条件是,你在on的时刻,传入的是变量名函数,解绑的时刻也是这个指向函数的变量。
setter中的事宜
当你运用set要领设置一个新值的时刻,这个类会自动挪用trigger要领去触发一个change事宜,比方set(‘name’, ‘new name’)的时刻trigger(‘change:name’, ‘new name’)会被自动触发。这和backbone的划定规矩异常像。
同时,像getter,setter内里的层级关联也被支撑了,比方set(‘book.name’, ‘Book’),这个时刻实在触发了两个事宜,一个是change:book,一个是change:book.name,它们都会被触发,而且是两个自力的事宜,绑在change:book上的回调函数和绑在change:book.name上的回调函数是完整离开的,没有任何关联,change:book事宜的回调函数会被先实行。假如你不想运用这个功用,能够把set要领的第三个参数设置为false,如许就不会触发trigger。
call私有要领
ES还不支撑private关键字,所以不能直接定义私有要领。这个类具有一个.call要领,能够用来挪用私有要领。私有要领写在class表面,跟attributions、events这两个变量差不多。然则在私有要领内里能够运用this,以及this照顾的任何东西,在class内里call它的时刻,this都是有用的。
mix一次性继承多个类
在backbone或其他一些框架中,每一个类有一个extends要领来建立一个子类,在extends参数中写要领来掩盖父类的要领。然则这类操纵只能继承于一个类,而假如想一次性继承几个类的某些要领,还须要自身写个扩大要领来完成。再说了,ES6自身就供应了extends关键字来继承类,所以纯真的extends要领不该该再继承运用了,只须要写一个mix要领来混入这些想要继承的类就能够了。
我写的这个类供应了两个要领,mixin和mixto,混入的体式格局差别。mixin是把参数内里的类的要领或属性一个一个往自身内里塞,而mixto是把自身的要领或属性往参数内里的类塞,方向上恰好相反。因为塞的方向差别,终究假如要领有重名的话,被塞的一方的要领就会被保存下来,作为终究发生的混类的主体。写一个继承就异常简朴:
class SubClass extends ClassBase.mixin(A, B, C) {}
mixin和mixto都是静态属性,所以能够直接用类名来挪用。
toString猎取类名
有的时刻你想看下当前的实例化对象究竟是从哪一个类实例化出来的,那末直接用toString要领来猎取类的称号。底本我想返回'[class BaseClass]’这类范例的字符串,然则相对没什么意义,还不如直接返回类名,还能够用来做比较。
本文宣布在我的博客
求个兼职,假如您有web开辟方面的须要,能够联络我,生涯不容易,且行且珍爱。
请在我的个人博客 www.tangshuang.net 留言,我会联络你。