在 JavaScript 中應用接口
這篇是 js-interface 的 README,雖然並非很龐雜的一個東西,假如有人看的話我就寫寫源碼思緒了 ORZ
引見
在做一個前後星散的項目時,有些頭疼 Api 之類的東西要怎樣治理,在瀏覽 《JavaScript 設想形式》 一書時,第二章提到了在 JavaScript 中模仿接口 (interface) 的觀點,以輕易應用浩瀚設想形式,因而嘗試着做一個接口的模仿。因為我本職是一位後端 Java 開闢,因而願望在這個模仿層能夠到場 接口默許完成、接口繼續、要領重載 等才能,雖然這些東西加上以後不可避免得會在機能上有所捐軀,但對我來講能夠提拔一些開闢體驗(我曉得 TypeScript,只是想搞個輪子嘗嘗 :P)。
應用
既然初志是為了輕易治理 Api,那末就做一個關於 Api 的 demo。
建立一個接口
const config = {
// 接口的名字
name: 'IApi',
// 是不是翻開此接口的 debug 開關
// 開闢時必需翻開,不然不會啟動 (要領聲明、要領完成等)入參的範例搜檢。
// 翻開這個的情況下,還會取得一些調試用的信息。
debug: true,
}
let IApi = new Interface(config)
聲明要領
最簡樸的聲明體式格局:
IApi.method({name: 'getName'})
// 等價於
IApi.method({name: 'getName', args: undefined})
如許就聲清楚明了 IApi
接口含有一個 getName
要領,它沒有任何參數,也沒有默許完成,這就要求在背面任何 IApi
的子接口或完成類必需完成該要領,不然會拋出一個非常。
假如想指定要領的參數列表:
IApi.method({ name: 'getName', args: null })
注重!
args
為 null
時示意該要領能夠接收恣意數目的恣意參數,假如重載了一個要領(細緻的請參閱背面關於重載的申明):
// 聲明一個空參要領
IApi.method({ id: 0, name: 'getName', args: null })
// 重載上面的要領,使其有且只要一個 'string' 範例,名為 name 的參數
IApi.method({ id: 1, name: 'getName', args: [
{name: 'name', type: 'string', support: val => typeof val === 'string'}
] })
注重!
在參數形貌中,type
屬性只是一個字符串值,它並不真的代表參數的現實範例。它跟 name
屬性一樣只是供應用於調試的信息,因而你能夠填入任何你以為適宜的、足以標記該參數一些信息的字符串值。
真正決議要領是不是接收該參數的是 support
屬性,當它返回 true
時會搜檢下一個參數直到一切參數搜檢終了或某個位置的參數不被接收。
假如須要,能夠在 support
中對現實入參舉行特別處置懲罰,比方轉換對象、特定屬性搜檢等等。
假如想為要領供應默許完成:
IApi.method({
name: 'getName',
// 默許完成,不能為箭頭函數!
implement: function() {
return "IApi"
}
})
回到我們的 demo,綜合應用一下:
// 聲明兩個要領,它們都沒有參數,也不須要重載因而如許就能夠了
IApi
.method({ name: 'getName' })
// 項目中應用 Axios,因而這裏須要一個要領來獵取 axios 實例
.method({ name: 'getAxios' })
// 聲明四個要求體式格局對應的要領
const methods = ['get', 'post', 'put', 'delete']
methods.forEach(method => {
IApi.method({
name: method,
args: null,
implement: function() {
// 處置懲罰了 this 指向題目,寧神用吧
return this.getAxios()[method].apply(this, arguments)
.then(responseHandler)
.catch(errorHandler)
}
})
})
繼續接口
假定我們要建立接口 A,要繼續 B、C、D、E 等接口,應用以下語句:
const A = new Interface({
name: 'A',
debug: true
}).extends([B, C, D, E])
extends
要領會將傳入的接口所持有的一切要領聲明(即經由過程 Interface.method(config)
所聲明的那些要領 )拷貝至接口 A,包含那些要領聲明的默許完成。
注重!
一般來講,不會在多個接口中重載同一個要領署名,但假如真的有如許的需求,能夠自行設置 id
的劃定規矩,比方:
const B = new Interface(...)
.method({ id: 'B00', name: 'getName', args = [...] })
.method({ id: 'B01', name: 'getName', args = [...] })
const C = new Interface(...)
.method({ id: 'C00', name: 'getName', args = [...] })
然後完成該要領時指定要完成哪個聲明:
// 注重!假如一個要領被重載,則不能在 class 中聲明該要領。
class AImpl { ... }
const AInstance = new AImpl(...)
B.implement({
object: AInstance,
id: 'B00', // 指定要完成的要領聲明
name: 'getName'
})
再次回到我們的 demo,綜合應用一下:
const IAuthenticationApi = new Interface({
name: 'IAuthentication',
debug: true
})
// 指明 IAuthenticationApi 繼續自 IApi 接口
.extends(IApi)
IAuthenticationApi
// 重載要領 login
// loin (username :string, password :string)
.method({
id: 0,
name: 'login',
args: [
{name: 'username', type: 'string', support: val => typeof val === 'string'},
{name: 'password', type: 'string', support: val => typeof val === 'string'}
]
})
// login()
.method({
id: 1,
name: 'login'
})
完成接口
// 編寫一個完成類
class AuthenticationApi {
constructor(axios) { this.axios = axios }
// 直接完成 getName 要領
getName() { return "AuthenticationApi" }
// 直接完成 getAxios 要領
getAxios() { return this.axios }
}
// 完成重載要領
IAuthenticationApi
.implement({
// 指定掛載完成到 AuthenticationApi 上
object: AuthenticationApi,
// 指定此完成是對應 id 為 0 的要領聲明
id: 0,
name: 'login',
implement: function(username, password) {
console.log('帶參數的 login')
// 還記得我們在 IApi 接口中定義了 get 要領(包含默許完成)嗎?
this.get('https://www.baidu.com')
return Promise.resolve('hello')
}
})
.implement({
object: AuthenticationApi,
id: 1,
name: 'login',
implement: function () {
console.log('無參數的 login')
},
})
IAuthenticationApi.ensureImplements(AuthenticationApi)
應用接口完成類
let authenticationService = new AuthenticationApi(axios)
// 掛載代辦函數到實例上,不然會提醒
// Uncaught TypeError: authenticationService.login is not a function
IAuthenticationApi.ensureImplements(authenticationService)
authenticationService
.login('sitdownload', '1498696873')
// login(string, string) 會返回一個 Promise 還記得嗎 :P
.then(str => alert(`${str} world!`))
authenticationService.login()
關於日記
起首確保在建立接口時翻開了 debug 開關({ debug: true }
)。
上面的 demo 運轉一般的話你將會獲得下面的日記:
// 註冊要領
Interface 註冊要領: IApi.getName()
Interface 註冊要領: IApi.getAxios()
Interface 註冊要領: IApi.get(any)
Interface 註冊要領: IApi.post(any)
Interface 註冊要領: IApi.put(any)
Interface 註冊要領: IApi.delete(any)
Interface 註冊要領: IAuthentication extends IApi.getName()
Interface 註冊要領: IAuthentication extends IApi.getAxios()
Interface 註冊要領: IAuthentication extends IApi.get(any)
Interface 註冊要領: IAuthentication extends IApi.post(any)
Interface 註冊要領: IAuthentication extends IApi.put(any)
Interface 註冊要領: IAuthentication extends IApi.delete(any)
Interface 註冊要領: [0]IAuthentication.login(username :string, password :string)
Interface 註冊要領: [1]IAuthentication.login()
// 完成要領
Interface 完成要領: 保留 [0]IAuthentication.login(...) 完成:
ƒ implement(username, password)
Interface 完成要領: 保留 [1]IAuthentication.login(...) 完成:
ƒ implement()
// 婚配要領
Interface 要領婚配: 精準婚配
IAuthentication.login({ username: "sitdownload" } :string, { password: "1498696873" } :string).
// 在控制台這行是能夠翻開完成的具體位置的
ƒ implement(username, password)
// 要領輸出
AuthenticationApi.js?7b55:25 帶參數的 login
// 婚配要領
Interface 要領婚配: 沒法精準婚配 IAuthentication.get("https://www.baidu.com"),應用 any 完成婚配:
ƒ implement()
Interface 要領婚配: 精準婚配 IAuthentication.login().
ƒ implement()
// 要領輸出
AuthenticationApi.js?7b55:35 無參數的 login
// AuthenticationApi.login(username, password) 中要求了 'https://www.baidu.com'
Failed to load https://www.baidu.com/: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1' is therefore not allowed access.
// IApi.get(any) 中將非常直接向下拋了
Uncaught (in promise) {type: "network", payload: Error: Network Error
at createError (webpack-internal:///./node_modules/_axios@0.18.0@axios/lib/…}
後續
假如要發版了,確認一切的接口要領都準確完成后,就能夠把 debug 關掉,如許就不會有 Interface
內部的一些入參搜檢和調試輸出。