React Native面向切面程式設計

在路上重名了啊發表於2018-11-04

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的三個引數,我們來看一下:

React Native面向切面程式設計
我們設定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(觀察者)。

結語: 只要我們涉及需要在執行前做一些處理的應用,不管是修改函式的引數值,還是增加屬性,還是執行的先決條件,我們都可以使用修飾器,這種程式設計的方式,就是面對切面程式設計

相關文章