React Native:面向切面程式設計 && JavaScript ES7修飾符
在編譯期
、類載入期
、執行時
,動態地將程式碼切入到類的指定方法、指定位置上的程式設計思想就是面向切面的程式設計。AOP其實只是OOP的補充而已。OOP從橫向上區分出一個個的類來,而AOP則從縱向上向物件中加入特定的程式碼。有了AOP,OOP變得立體了。如果加上時間維度,AOP使OOP由原來的二維變為三維了,由平面變成立體了。從技術上來說,AOP基本上是通過代理機制
實現的。 AOP在程式設計歷史上可以說是里程碑式的,對OOP程式設計是一種十分有益的補充。
應用:將通用行的程式碼,在不汙染其他功能程式碼的前提下,更好的複用
ES7修飾器
參考文件1:讀懂ES7中javascript修飾器
參考文件2:JavaScript 修飾符是什麼及何時使用它們
參考文件3:ECMAScript 6 入門
什麼是修飾器
這個概念你以前可能聽說過,就是“功能組合
”,或者“高階函式
”。修飾器(Decorator
)是ES7的一個提案,它的出現能解決兩個問題:
- 不同類間共享方法
- 編譯期對類和方法的行為進行改變
怎麼使用 JavaScript 修飾符?
修飾符使用一個在 ES2017 中定義的特殊語法,在被修飾的程式碼前放置一個 @
開頭的符號。
為什麼使用修飾符?
JavaScript 中已經可以實現功能組合,但明顯比較困難 —— 甚至不可能 —— 把相同的技術應用到其它程式碼上(比如類和類屬性)。
ES2017 草案新增了支援類和屬性的修飾符,它可以用來解決這些問題,將來的 JavaScript 版本可能會允許在其它棘手的程式碼區域新增修飾符。
修飾符的不同型別
目前,唯一支援的修飾型別是用在類和類成員上的,包括屬性、方法、getters 和 setters。
修飾符只不過是返回另一個函式的函式,這被稱為被修飾項適當的細節。這些修飾符函式會在程式首次執行時被執行一次,而其返回值會替代被修飾的程式碼。
類成員修飾符
類修飾符
例子1:修飾類
@setProp
class User {}
function setProp(target) {
target.age = 30
}
console.log(User.age)
複製程式碼
這個例子要表達的是對User類使用setProp這個方法進行修飾,用來增加User類中age的屬性,setProp方法會接收3個引數,我們現在接觸第一個,target代表User類本身。
例子2:修飾類(自定義引數值)
@setProp(20)
class User {}
function setProp(value) {
return function (target) {
target.age = value
}
}
console.log(User.age)
複製程式碼
此例和上面功能基本一致,唯一差別在於值是參考修飾函式傳過來的
例子2:修飾方法
class User {
@readonly
getName() {
return 'Hello World'
}
}
// readonly修飾函式,對方法進行只讀操作
function readonly(target, name, descriptor) {
descriptor.writable = false
return descriptor
}
let u = new User()
// 嘗試修改函式,在控制檯會報錯
u.getName = () => {
return 'I will override'
}
複製程式碼
上例中,我們對
我們設定User
類中的getName
方法使用readonly
修飾器進行修飾,使得方法不能被修改。第一個引數我們已經知道了,引數name
為方法名,也就是readonly
,引數descriptor
是個啥東西呢,看到這行descriptor.writable = false
,我們大家猜的也差不多了,這三個引數對應的就是Object.defineProperty
的三個引數,我們來看一下:descriptor.writable = false
就是讓函式不可以被修改,如果我們寫成descriptor.value = 'function (){ console.log('Hello decorator') }'
那麼,輸出就是Hello World
了,而是Hello decorator
,是不是已經意識到修飾器的好處了。現在我們來看看實際工作中,我們用到修飾器的例子
實際應用1:日誌管理
我們經常在每一步列印一些日誌檔案,比如這步都幹了些什麼事,很明顯列印日誌的操作和業務程式碼根本就一點關係沒有,我們不應該把日誌和業務摻和在一起,這樣使用修飾器就是避免這個問題,以下為程式碼:
class Pack {
@log('讀取package.json檔案')
step1() {
// do something...
// 沒有修飾器之前,我們通常把console.log放到這裡寫
// 放到函式裡面寫會有兩個壞處
// 1.console和業務無關,會破壞函式單一性原則
// 2.如果要刪除所有的console,那我們只能深入到每一個方法中
}
@log('合併webpack配置檔案')
step2() {
// do something...
}
}
function log(value) {
return function (target, name, descriptor) {
// 在這裡,我們還可以拿到函式的引數,列印更加詳細的資訊
console.log(value)
}
}
let pack = new Pack()
pack.step1()
pack.step2()
複製程式碼
實際應用2:檢查登入
在實際的開發中常用得到,我們一些操作前,必須得判斷使用者是否登入,比較點贊、結算、傳送彈幕...按照之前的寫法,我們必須在每一個方法中判斷使用者的登入情況,然後再進行業務的操作,很顯然前置條件和業務又混到了一起,用修飾器,就可以完美的解決這一問題,程式碼如下:
class User {
// 獲取已登入使用者的使用者資訊
@checkLogin
getUserInfo() {
/**
* 之前,我們都會這麼寫:
* if(checkLogin()) {
* // 業務程式碼
* }
* 這段程式碼會在每一個需要登入的方法中執行
* 還是上面的問題,執行的前提和業務又混到了一起
*/
console.log('獲取已登入使用者的使用者資訊')
}
// 傳送訊息
@checkLogin
sendMsg() {
console.log('傳送訊息')
}
}
// 檢查使用者是否登入,如果沒有登入,就跳轉到登入頁面
function checkLogin(target, name, descriptor) {
let method = descriptor.value
// 模擬判斷條件
let isLogin = true
descriptor.value = function (...args) {
if (isLogin) {
method.apply(this, args)
} else {
console.log('沒有登入,即將跳轉到登入頁面...')
}
}
}
let u = new User()
u.getUserInfo()
u.sendMsg()
複製程式碼
實際應用3:定時器
普通寫法:
setTimeout(() => {
this.fn();
}, 0);
複製程式碼
修飾符寫法:
@timeout(1000)
fn() {
// doing
}
this.fn();
複製程式碼
對應的 timeout 修飾器程式碼:
// timeout.ts
export function timeout(milliseconds: number = 0) {
return function(target, key, descriptor) {
// value 值相當於上面示例中 `change` 方法。
var orgMethod = descriptor.value;
descriptor.value = function(...args) {
setTimeout(() => {
orgMethod.apply(this, args);
}, milliseconds);
};
return descriptor;
}
}
複製程式碼
target
:例項物件,即 IndexComponent 例項化物件。key
:方法名稱,即 fn。descriptor
:物件描述,同Object.getOwnPropertyDescriptor() 。
實際應用4:Core 修飾符
有一個神奇的庫,稱為 Core Decorators,它提供一些平常有用的修飾符。
這些修飾符使用簡潔的語法,提供了非常有用的通用功能(比如,呼叫方法的時候,否決警告,允許某個值只讀等)。
實際應用5:React
React 庫很好的利用了高階元件(Higher-Order Components)的概念。React 元件可以簡單的寫成函式,而它可以包含另一個元件。
實際應用6:MobX
MobX 庫廣泛使用了修飾符,讓你很容易把欄位標記為 Observable(可觀察物件) 或 Computed(計算屬性),以及把類變成 Observers(觀察者)。
結語:
只要我們涉及需要在執行前做一些處理的應用,不管是修改函式的引數值
,還是增加屬性
,還是執行的先決條件
,我們都可以使用修飾器
,這種程式設計的方式,就是面對切面程式設計