裝飾者模式在日常開發(fā)中的縮影和vue中的使用詳解
一、日常開發(fā)
裝飾者模式以其不改變原對象,并且與原對象有著相同接口的特點,廣泛應(yīng)用于日常開發(fā)和主流框架的功能中。
1、數(shù)據(jù)埋點
假如我們開發(fā)了一個移動端網(wǎng)頁,有圖書搜索、小游戲、音頻播放和視頻播放等主要功能,初期,我們并不知道這幾個功能用戶的使用規(guī)律。
有一天,產(chǎn)品經(jīng)理說,我想要各個功能用戶的使用規(guī)律,并且通過echarts繪制折線圖和柱狀圖,能加嗎?
這就加......
起初:
<button id="smallGameBtn">小游戲</button> <script> var enterSmallGame = function () { console.log('進入小游戲') } document.getElementById('smallGameBtn').onclick = enterSmallGame; </script>
通過裝飾者模式增加數(shù)據(jù)埋點之后:
<button id="smallGameBtn">小游戲</button> <script> Function.prototype.after= function (afterFn) { var selfFn = this; return function () { var ret = selfFn.apply(this, arguments) afterFn.apply(this.arguments) return ret } } var enterSmallGame = function () { console.log('進入小游戲') } var dataLog = function () { console.log('數(shù)據(jù)埋點') } enterSmallGame = enterSmallGame.after(dataLog) document.getElementById('smallGameBtn').onclick = enterSmallGame; </script>
定義Function.prototype.after
函數(shù),其中通過閉包的方式緩存selfFn
,然后返回一個函數(shù),該函數(shù)首先執(zhí)行selfFn
,再執(zhí)行afterFn
,這里也很清晰的可以看出兩個函數(shù)的執(zhí)行順序。
在當(dāng)前例子中,首先執(zhí)行進入小游戲的功能,然后,再執(zhí)行數(shù)據(jù)埋點的功能。
可以看出,加了數(shù)據(jù)埋點,執(zhí)行函數(shù)是enterSmallGame
,不加也是。同時,也未對函數(shù)enterSmallGame
內(nèi)部進行修改。
2、表單校驗
假如,我們開發(fā)了登錄頁面,有賬號和密碼輸入框。
我們知道,校驗是必須加的功能。
不加校驗:
賬號:<input id="account" type="text"> 密碼:<input id="password" type="password"> <button id="loginBtn">登錄</button> <script> var loginBtn = document.getElementById('loginBtn') var loginFn = function () { console.log('登錄成功') } loginBtn.onclick = loginFn; </script>
通過裝飾者模式加校驗之后:
賬號:<input id="account" type="text"> 密碼:<input id="password" type="password"> <button id="loginBtn">登錄</button> <script> var loginBtn = document.getElementById('loginBtn') Function.prototype.before = function (beforeFn) { var selfFn = this; return function () { if (!beforeFn.apply(this, arguments)) { return; } return selfFn.apply(this, arguments) } } var loginFn = function () { console.log('登錄成功') } var validateFn = function () { var account = document.getElementById('account').value; var password = document.getElementById('password').value; return account && password; } loginFn = loginFn.before(validateFn) loginBtn.onclick = loginFn; </script>
定義Function.prototype.before
函數(shù),其中通過閉包的方式緩存selfFn
,然后返回一個函數(shù),該函數(shù)首先執(zhí)行beforeFn
,當(dāng)其返回true
時,再執(zhí)行afterFn
。
在當(dāng)前例子中,首先執(zhí)行表單校驗的功能,然后,提示登錄成功,進入系統(tǒng)。
可以看出,加了表單校驗,執(zhí)行函數(shù)是loginFn
,不加也是。同時,也未對函數(shù)loginFn
內(nèi)部進行修改。
二、框架功能(vue)
1、數(shù)組監(jiān)聽
vue
的特點之一就是數(shù)據(jù)響應(yīng)式,數(shù)據(jù)的變化會改變視圖的變化。但是,數(shù)組中通過下標(biāo)索引的方式直接修改不會引起視圖變化,通過push
、pop
、shift
、unshift
和splice
等方式修改數(shù)據(jù)就可以。
這里我們只看響應(yīng)式處理數(shù)據(jù)的核心邏輯Observer
:
export class Observer { value: any; dep: Dep; vmCount: number; // number of vms that have this object as root $data constructor (value: any) { if (Array.isArray(value)) { protoAugment(value, arrayMethods) } } }
如果需要響應(yīng)式處理的數(shù)據(jù)滿足Array.isArray(value)
,則可通過protoAugment
對數(shù)據(jù)進行處理。
function protoAugment (target, src: Object) { target.__proto__ = src }
這里修改目標(biāo)的__proto__
指向為src
,protoAugment(value, arrayMethods)
執(zhí)行的含義就是修改數(shù)組的原型指向為arrayMethods
:
import { def } from '../util/index' const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change ob.dep.notify() return result }) })
通過const arrayProto = Array.prototype
的方式緩存Array
的原型,通過const arrayMethods = Object.create(arrayProto)
原型繼承的方式讓arrayMethods
上繼承了Array
原型上的所有方法。這里有定義了包含push
和splice
等方法的數(shù)組methodsToPatch
,循環(huán)遍歷methodsToPatch
數(shù)組并執(zhí)行def
方法:
export function def (obj: Object, key: string, val: any, enumerable?: boolean) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) }
這里的目的是當(dāng)訪問到methodsToPatch
中的方法method
的時候,先const result = original.apply(this, args)
執(zhí)行的原始方法,獲取到方法的執(zhí)行結(jié)果result
。然后通過switch (method)
的方式針對不同的方法進行參數(shù)的處理,手動響應(yīng)式的處理,并且進行視圖重新渲染的通知ob.dep.notify()
。
整個過程可以看出,是對數(shù)組的原型進行了裝飾者模式的處理。目的是,針對push
、pop
、shift
、unshift
和splice
等方法進行裝飾,當(dāng)通過這些方法進行數(shù)組數(shù)據(jù)的修改時,在執(zhí)行本體函數(shù)arrayProto[method]
的同時,還執(zhí)行了手動響應(yīng)式處理和視圖更新通知的操作。
2、重寫掛載
vue
還有特點是支持跨平臺,不僅可以使用在web
平臺運行,也可以使用在weex
平臺運行。
首先定義了公共的掛載方法:
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
通過裝飾者模式處理平臺相關(guān)的節(jié)點掛載和模板編譯:
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { outputSourceRange: process.env.NODE_ENV !== 'production', shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } // 這里執(zhí)行緩存的與平臺無關(guān)的mount方法 return mount.call(this, el, hydrating) }
可以看出,在web
平臺中先緩存公共的掛載方法。當(dāng)其處理完平臺相關(guān)的掛載節(jié)點和模板編譯等操作后,再去執(zhí)行與平臺無關(guān)的掛載方法。
總結(jié)
裝飾者模式,是一種可以首先定義本體函數(shù)進行執(zhí)行。然后,在需要進行功能添加的時候,重新定義一個用來裝飾的函數(shù)。
裝飾的函數(shù)可以在本體函數(shù)之前執(zhí)行,也可以在本體函數(shù)之后執(zhí)行,在某一天不需要裝飾函數(shù)的時候,也可以只執(zhí)行本體函數(shù)。因為本體函數(shù)和通過裝飾者模式改造的函數(shù)有著相同的接口,而且也可以不必去熟悉本體函數(shù)內(nèi)部的實現(xiàn)。
以上就是裝飾者模式在日常開發(fā)中的縮影和vue中的使用詳解的詳細(xì)內(nèi)容,更多關(guān)于vue 使用裝飾者模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中關(guān)于element的el-image 圖片預(yù)覽功能增加一個下載按鈕(操作方法)
這篇文章主要介紹了vue中關(guān)于element的el-image 圖片預(yù)覽功能增加一個下載按鈕,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-04-04前端vue+express實現(xiàn)文件的上傳下載示例
本文主要介紹了前端vue+express實現(xiàn)文件的上傳下載示例,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12Vue項目部署的實現(xiàn)(阿里云+Nginx代理+PM2)
這篇文章主要介紹了Vue項目部署的實現(xiàn)(阿里云+Nginx代理+PM2),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-03-03vue 框架下自定義滾動條(easyscroll)實現(xiàn)方法
這篇文章主要介紹了vue 框架下自定義滾動條(easyscroll)實現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08vue3+element?plus實現(xiàn)側(cè)邊欄過程
這篇文章主要介紹了vue3+element?plus實現(xiàn)側(cè)邊欄過程,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03使用vue-router beforEach實現(xiàn)判斷用戶登錄跳轉(zhuǎn)路由篩選功能
這篇文章主要介紹了vue使用vue-router beforEach實現(xiàn)判斷用戶登錄跳轉(zhuǎn)路由篩選,本篇文章默認(rèn)您已經(jīng)會使用 webpack 或者 vue-cli 來進行環(huán)境的搭建,并且具有一定的vue基礎(chǔ)。需要的朋友可以參考下2018-06-06