资讯专栏INFORMATION COLUMN

ES6的一个基础类,支持私有属性和方法,支持event和mix

Jeffrrey / 3280人阅读

摘要:绑定事件,传入一个回调函数,但是这里还给加了一个功能,就是第三个参数规定回调函数执行的顺序。比如当你给同一个事件传入了多个回调函数,怎么来规定它们之间的顺序呢通过传入第三个参数即可,数字越小的,越靠前执行。

ES6提供了完整的class语法,因此,可以非常方便的使用extends关键字对类进行扩展(继承)。为了实现类的一些基础功能,我撰写了下面这个类,用以被其他类继承,拥有这个基础类的基础功能。

源码

</>复制代码

  1. var events = {}
  2. var data = {}
  3. var copyProperty = function(Target, Source) {
  4. for(let key of Reflect.ownKeys(Source)) {
  5. if(key !== "constructor" && key !== "prototype" && key !== "name") {
  6. let descriptor = Object.getOwnPropertyDescriptor(Source, key)
  7. Object.defineProperty(Target, key, descriptor)
  8. }
  9. }
  10. }
  11. export default class ClassBase {
  12. constructor(...args) {
  13. events[this] = {}
  14. data[this] = {}
  15. this.call(this.initialize, ...args)
  16. return this
  17. }
  18. /**
  19. * @desc initialize class method, be called every time class is initialized
  20. * Notice: never use constructor when extends by a sub class
  21. */
  22. initialize() {}
  23. /**
  24. * @desc get data from data manager
  25. * @param string key: the key of data, you can use "." to get tree info. e.g. .get("root.sub.ClassBaseMix") => .get("root").sub.ClassBaseMix
  26. */
  27. get(key) {
  28. let target = data[this]
  29. if(key.indexOf(".") === -1) return target[key]
  30. let nodes = key.split(".").filter(item => item && item !== "")
  31. if(nodes.length === 0) return
  32. for(let node of nodes) {
  33. if(typeof target !== "object" || !target[node]) return
  34. target = target[node]
  35. }
  36. return target
  37. }
  38. /**
  39. * @desc save data to data manager
  40. * @param string key: the key of data, use "." to set tree structure. e.g. .set("root.sub.ClassBaseMix", "value") => .get("root").sub.ClassBaseMix = "value"
  41. * @param mix value: the value to save
  42. * @param boolean notify: whether to trigger data change event
  43. */
  44. set(key, value, notify = true) {
  45. if(!data[this]) data[this] = {}
  46. let target = data[this]
  47. if(key.indexOf(".") === -1) {
  48. target[key] = value
  49. if(notify) {
  50. this.trigger("change:" + key, value)
  51. }
  52. return this
  53. }
  54. let nodes = key.split(".").filter(item => item && item !== "")
  55. if(nodes.length === 0) return
  56. let lastKey = nodes.pop()
  57. for(let node of nodes) {
  58. if(typeof target !== "object") return
  59. if(!target[node]) {
  60. target[node] = {}
  61. }
  62. target = target[node]
  63. }
  64. target[lastKey] = value
  65. if(notify) {
  66. nodes.push(lastKey)
  67. let event = nodes.shift()
  68. this.trigger("change:" + event, value)
  69. while (nodes.length > 0) {
  70. event += "." + nodes.shift()
  71. this.trigger("change:" + event, value)
  72. }
  73. }
  74. return this
  75. }
  76. /**
  77. * @desc call some function out of this class bind with this
  78. * @param function factory: the function to call
  79. * @param args: arguments to pass to function be called
  80. */
  81. call(factory, ...args) {
  82. factory.apply(this, args)
  83. return this
  84. }
  85. /**
  86. * @desc bind events on Instantiate objects
  87. * @param string evts: events want to bind, use " " to split different events, e.g. .on("change:data change:name", ...)
  88. * @param function handler: function to call back when event triggered
  89. * @param number order: the order to call function. functions are listed one by one with using order.
  90. */
  91. on(evts, handler, order = 10) {
  92. if(!events[this]) events[this] = {}
  93. evts = evts.split(" ")
  94. let target = events[this]
  95. evts.forEach(evt => {
  96. if(!target[evt]) {
  97. target[evt] = {}
  98. }
  99. let node = target[evt]
  100. if(!node[order]) node[order] = []
  101. let hdles = node[order]
  102. if(hdles.indexOf(handler) === -1) hdles.push(handler) // make sure only once in one order
  103. })
  104. return this
  105. }
  106. /**
  107. * @desc remove event handlers
  108. * @param string event: event name, only one event supported
  109. * @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.
  110. */
  111. off(event, handler) {
  112. if(!handler) {
  113. events[this][event] = {}
  114. return
  115. }
  116. let node = events[this][event]
  117. if(!node) return
  118. let orders = Object.keys(node)
  119. if(!orders || orders.length === 0) return
  120. if(orders.length > 1) orders = orders.sort((a, b) => a - b)
  121. orders.forEach(order => {
  122. let hdles = node[order]
  123. let index = hdles.indexOf(handler)
  124. if(index > -1) hdles.splice(index, 1) // delete it/them
  125. if(hdles.length === 0) delete node[order]
  126. })
  127. return this
  128. }
  129. /**
  130. * @desc trigger events handlers
  131. * @param string event: which event to trigger
  132. * @param args: arguments to pass to handler function
  133. */
  134. trigger(event, ...args) {
  135. let node = events[this][event]
  136. if(!node) return
  137. let orders = Object.keys(node)
  138. if(!orders || orders.length === 0) return
  139. if(orders.length > 1) orders = orders.sort((a, b) => a - b)
  140. let handlers = []
  141. orders.forEach(order => {
  142. let hdles = node[order]
  143. handlers = [...handlers, ...hdles]
  144. })
  145. handlers.forEach(handler => {
  146. if(typeof handler === "function") {
  147. // this.call(handler, ...args) // 会绑定this
  148. handler(...args) // 不会绑定this,其实可以在on的时候用bind去绑定
  149. }
  150. })
  151. return this
  152. }
  153. /**
  154. * @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
  155. * @param Classes: the classes passed to mix, previous class will NOT be overwrite by the behind ones.
  156. */
  157. static mixin(...Classes) {
  158. class ClassBaseMix {}
  159. Classes.reverse()
  160. Classes.push(this)
  161. for(let Mixin of Classes) {
  162. copyProperty(ClassBaseMix, Mixin)
  163. copyProperty(ClassBaseMix.prototype, Mixin.prototype)
  164. }
  165. return ClassBaseMix
  166. }
  167. /**
  168. * @desc mix other classes into this class, property may be overwrite by passed class, behind class will cover previous class
  169. */
  170. static mixto(...Classes) {
  171. class ClassBaseMix {}
  172. Classes.unshift(this)
  173. for(let Mixin of Classes) {
  174. copyProperty(ClassBaseMix, Mixin)
  175. copyProperty(ClassBaseMix.prototype, Mixin.prototype)
  176. }
  177. return ClassBaseMix
  178. }
  179. toString() {
  180. return this.constructor.name
  181. }
  182. }

你可以在这里阅读每一个方法的说明,这里简单的说明一下它们的各自用途。

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是把自己的方法或属性往参数里面的类塞,方向上正好相反。由于塞的方向不同,最终如果方法有重名的话,被塞的一方的方法就会被保留下来,作为最终产生的混类的主体。写一个继承就非常简单:

</>复制代码

  1. class SubClass extends ClassBase.mixin(A, B, C) {}

mixin和mixto都是静态属性,所以可以直接用类名来调用。

toString获取类名

有的时候你想看下当前的实例化对象到底是从哪个类实例化出来的,那么直接用toString方法来获取类的名称。原本我想返回"[class BaseClass]"这种类型的字符串,但是绝对没什么意义,还不如直接返回类名,还可以用来做比较。

本文发布在我的博客
求个兼职,如果您有web开发方面的需要,可以联系我,生活不容易,且行且珍惜。
请在我的个人博客 www.tangshuang.net 留言,我会联系你。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86941.html

相关文章

  • ES6中class私有属性私有方法

    摘要:新增的语法非常帅,但是围绕这个新的语法糖,在中如何实现静态属性私有属性私有方法的问题,成为了大家探讨的话题。私有方法理论上讲,私有属性和私有方法的区别是,私有方法是函数。 ES6新增的class语法非常帅,但是围绕这个新的语法糖,在class中如何实现静态属性、私有属性、私有方法的问题,成为了大家探讨的话题。本文打算绕过现有的weakmap、symbol的方案,从最简单的实践中抽取出满...

    bergwhite 评论0 收藏0
  • ES6中文手册、ES6 Cheatsheet

    摘要:盲目使用替换后可能会导致预期意外的结果。在中,许多种方法来处理函数的参数默认值,参数数量,参数命名。此外,处理后的值,无论是解决还是拒绝的结果值,都是不可改变的。 这是一个 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳实践和一些代码片段,帮助你完成日复一日的开发工作。 Table of Contents var 与 let / const 声明 代码执行...

    Cristalven 评论0 收藏0
  • 从0到1使用VUE-CLI3开发实战(三): ES6/ES7知识储备

    摘要:它们都用于声明变量。盲目使用替换后可能会导致预期意外的结果。有鉴于此,还是建议使用字符串,布尔和数字类型的数据类型。像使用这种下划线命名约定在一个开源项目中,命名规则很难维持得一直很好,这样经常会造成一些困扰。 今天群里有小伙伴跟我聊天,问了我几个关于ES6的问题,我才意识到,大部分初学者在学习的过程中,都是学了HTML/CSS/JS之后就开始上手学习框架了,而对于ES6的重视程度却不...

    crossoverJie 评论0 收藏0

发表评论

0条评论

Jeffrrey

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<