詳解在Javascript中進(jìn)行面向切面編程
面向切面編程(Aspect-oriented programming,AOP)是一種編程范式。做后端 Java web 的同學(xué),特別是用過 Spring 的同學(xué)肯定對它非常熟悉。AOP 是 Spring 框架里面其中一個重要概念??墒窃?Javascript 中,AOP 是一個經(jīng)常被忽視的技術(shù)點。
場景
假設(shè)你現(xiàn)在有一個牛逼的日歷彈窗,有一天,老板讓你統(tǒng)計一下每天這個彈窗里面某個按鈕的點擊數(shù),于是你在彈窗里做了埋點;
過了一個星期,老板說用戶反饋這個彈窗好慢,各種卡頓。你想看一下某個函數(shù)的平均執(zhí)行時間,于是你又在彈窗里加上了性能統(tǒng)計代碼。
時間久了,你會發(fā)現(xiàn)你的業(yè)務(wù)邏輯里包含了大量的和業(yè)務(wù)無關(guān)的東西,即使是一些你已經(jīng)封裝過的函數(shù)。
那么 AOP 就是為了解決這類問題而存在的。
關(guān)注點分離
分離業(yè)務(wù)代碼和數(shù)據(jù)統(tǒng)計代碼(非業(yè)務(wù)代碼),無論在什么語言中,都是AOP的經(jīng)典應(yīng)用之一。從核心關(guān)注點中分離出橫切關(guān)注點,是 AOP 的核心概念。
在前端的常見需求中,有以下一些業(yè)務(wù)可以使用 AOP 將其從核心關(guān)注點中分離出來
- Node.js 日志log
- 埋點、數(shù)據(jù)上報
- 性能分析、統(tǒng)計函數(shù)執(zhí)行時間
- 給ajax請求動態(tài)添加參數(shù)、動態(tài)改變函數(shù)參數(shù)
- 分離表單請求和驗證
- 防抖與節(jié)流
裝飾器(Decorator)
提到 AOP 就要說到裝飾器模式,AOP 經(jīng)常會和裝飾器模式混為一談。
在ES6之前,要使用裝飾器模式,通常通過Function.prototype.before做前置裝飾,和Function.prototype.after做后置裝飾(見《Javascript設(shè)計模式和開發(fā)實踐》)。
Javascript 引入的 Decorator ,和 Java 的注解在語法上很類似,不過在語義上沒有一丁點關(guān)系。Decorator 提案提供了對 Javascript 的類和類里的方法進(jìn)行裝飾的能力。(盡管只是在編譯時運(yùn)行的函數(shù)語法糖)
埋點數(shù)據(jù)上報
因為在使用 React 的實際開發(fā)中有大量基于 Class 的 Component,所以我這里用 React 來舉例。
比如現(xiàn)在頁面中有一個button,點擊這個button會彈出一個彈窗,與此同時要進(jìn)行數(shù)據(jù)上報,來統(tǒng)計有多少用戶點擊了這個登錄button。
import React, { Component } from 'react'; import send from './send'; class Dialog extends Component { constructor(props) { super(props); } @send showDialog(content) { // do things } render() { return ( <button onClick={() => this.showDialog('show dialog')}>showDialog</button> ) } } export default Dialog;
上面代碼引用了@send裝飾器,他會修改這個 Class 上的原型方法,下面是@send裝飾器的實現(xiàn)
export default function send(target, name, descriptor) { let oldValue = descriptor.value; descriptor.value = function () { console.log(`before calling ${name} with`, arguments); return oldValue.apply(this, arguments); }; return descriptor; }
在按鈕點擊后執(zhí)行showDialog前,可以執(zhí)行我們想要的切面操作,我們可以將埋點,數(shù)據(jù)上報相關(guān)代碼封裝在這個裝飾器里面來實現(xiàn) AOP。
前置裝飾和后置裝飾
上面的send這個裝飾器其實是一個前置裝飾器,我們可以將它再封裝一下使它可以前置執(zhí)行任意函數(shù)。
function before(beforeFn = function () { }) { return function (target, name, descriptor) { let oldValue = descriptor.value; descriptor.value = function () { beforeFn.apply(this, arguments); return oldValue.apply(this, arguments); }; return descriptor; } }
這樣我們就可以使用@before裝飾器在一個原型方法前切入任意的非業(yè)務(wù)代碼。
function beforeLog() { console.log(`before calling ${name} with`, arguments); } class Dialog { ... @before(beforeLog) showDialog(content) { // do things } ... }
和@before裝飾器類似,可以實現(xiàn)一個@after后置裝飾器,只是函數(shù)的執(zhí)行順序不一樣。
function after(afterFn = function () { }) { return function (target, name, descriptor) { let oldValue = descriptor.value; descriptor.value = function () { let ret = oldValue.apply(this, arguments); afterFn.apply(this, arguments); return ret; }; return descriptor; } }
性能分析
有時候我們想統(tǒng)計一段代碼在用戶側(cè)的執(zhí)行時間,但是又不想將打點代碼嵌入到業(yè)務(wù)代碼中,同樣可以利用裝飾器來做 AOP。
function measure(target, name, descriptor) { let oldValue = descriptor.value; descriptor.value = function () { let ret = oldValue.apply(this, arguments); performance.mark("startWork"); afterFn.apply(this, arguments); performance.mark("endWork"); performance.measure("work", "startWork", "endWork"); performance .getEntries() .map(entry => JSON.stringify(entry, null, 2)) .forEach(json => console.log(json)); return ret; }; return descriptor; }
在要統(tǒng)計執(zhí)行時間的類方法前面加上@measure就行了,這樣做性能統(tǒng)計的代碼就不會侵入到業(yè)務(wù)代碼中。
class Dialog { ... @measure showDialog(content) { // do things } ... }
小結(jié)
面向切面編程的重點就是將核心關(guān)注面分離出橫切關(guān)注面,前端可以用 AOP 優(yōu)雅的來組織數(shù)據(jù)上報、性能分析、統(tǒng)計函數(shù)的執(zhí)行時間、動態(tài)改變函數(shù)參數(shù)、插件式的表單驗證等代碼。
以上所述是小編給大家介紹的Javascript面向切面編程詳解整合,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
相關(guān)文章
JavaScript實現(xiàn)鼠標(biāo)懸浮頁面切換效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實現(xiàn)鼠標(biāo)懸浮頁面切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03javascript實現(xiàn)的像java、c#之類的sleep暫停的函數(shù)代碼
我們都知道java、c#、vb等語言都有sleep暫停的函數(shù),而JavaScript腳本沒有類似的功能。2010-03-03JavaScript使用AOP編程思想實現(xiàn)監(jiān)聽HTTP請求
這篇文章主要為大家詳細(xì)介紹了如何在JavaScript使用AOP編程思想實現(xiàn)監(jiān)聽HTTP請求,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-02-02javascript鼠標(biāo)跟隨運(yùn)動3種效果(眼球效果,蘋果菜單,方向跟隨)
在很多網(wǎng)站上能看到圖片跟隨鼠標(biāo)移動的JS特效,其實做法很簡單,本文就介紹了很多javascript鼠標(biāo)跟隨運(yùn)動,在這里與大家分享下。2016-10-10Uniapp自定義導(dǎo)航欄并自適應(yīng)機(jī)型的實現(xiàn)方法
Uniapp 是一款跨平臺開發(fā)框架,可以同時開發(fā)出可以在多個平臺上運(yùn)行的應(yīng)用,在開發(fā)過程中,我們常常需要自定義導(dǎo)航欄來滿足 UI 設(shè)計的需求,本文將介紹如何在 Uniapp 中自定義導(dǎo)航欄并自適應(yīng)不同機(jī)型的屏幕大小,需要的朋友可以參考下2023-09-09