摘要:綁定事件,傳入一個回調函數,但是這里還給加了一個功能,就是第三個參數規定回調函數執行的順序。比如當你給同一個事件傳入了多個回調函數,怎么來規定它們之間的順序呢通過傳入第三個參數即可,數字越小的,越靠前執行。
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 留言,我會聯系你。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/86941.html
摘要:新增的語法非常帥,但是圍繞這個新的語法糖,在中如何實現靜態屬性私有屬性私有方法的問題,成為了大家探討的話題。私有方法理論上講,私有屬性和私有方法的區別是,私有方法是函數。 ES6新增的class語法非常帥,但是圍繞這個新的語法糖,在class中如何實現靜態屬性、私有屬性、私有方法的問題,成為了大家探討的話題。本文打算繞過現有的weakmap、symbol的方案,從最簡單的實踐中抽取出滿...
摘要:盲目使用替換后可能會導致預期意外的結果。在中,許多種方法來處理函數的參數默認值,參數數量,參數命名。此外,處理后的值,無論是解決還是拒絕的結果值,都是不可改變的。 這是一個 ES2015(ES6) 的Cheatsheet,其中包括提示、小技巧、最佳實踐和一些代碼片段,幫助你完成日復一日的開發工作。 Table of Contents var 與 let / const 聲明 代碼執行...
摘要:它們都用于聲明變量。盲目使用替換后可能會導致預期意外的結果。有鑒于此,還是建議使用字符串,布爾和數字類型的數據類型。像使用這種下劃線命名約定在一個開源項目中,命名規則很難維持得一直很好,這樣經常會造成一些困擾。 今天群里有小伙伴跟我聊天,問了我幾個關于ES6的問題,我才意識到,大部分初學者在學習的過程中,都是學了HTML/CSS/JS之后就開始上手學習框架了,而對于ES6的重視程度卻不...
閱讀 652·2021-11-24 09:39
閱讀 3012·2021-11-23 10:06
閱讀 981·2021-10-08 10:05
閱讀 754·2019-08-30 10:49
閱讀 1719·2019-08-29 14:08
閱讀 1325·2019-08-29 12:48
閱讀 3320·2019-08-26 14:04
閱讀 3613·2019-08-26 13:50