好的代碼通常符合一個(gè)特點(diǎn):高內(nèi)聚,低耦合。通俗地說(shuō)就是用更少地代碼完成更多地功能,比如web前端地MVVM框架就是把對(duì)DOM的操作和事件監(jiān)聽(tīng)抽象出來(lái),通過(guò)數(shù)據(jù)綁定來(lái)更新數(shù)據(jù)和視圖。“組件化”的思想也是如此,組件 ...
好的代碼通常符合一個(gè)特點(diǎn):高內(nèi)聚,低耦合。
通俗地說(shuō)就是用更少地代碼完成更多地功能,比如web前端地MVVM框架就是把對(duì)DOM的操作和事件監(jiān)聽(tīng)抽象出來(lái),通過(guò)數(shù)據(jù)綁定來(lái)更新數(shù)據(jù)和視圖。
“組件化”的思想也是如此,組件的目的不是簡(jiǎn)單的代碼分割,更重要的是方便代碼的復(fù)用。
這些都是針對(duì)視圖層面的優(yōu)化措施,針對(duì)數(shù)據(jù)層,其實(shí)也有方法寫(xiě)出更優(yōu)秀的代碼。
其中一個(gè)簡(jiǎn)單有效的方法就是 盡量編寫(xiě)純函數(shù)。
什么是純函數(shù)?可以用一個(gè)表達(dá)式來(lái)描述
輸入?yún)?shù)x => 執(zhí)行代碼 => 輸出結(jié)果y
這看起來(lái)好像和普通函數(shù)也沒(méi)啥區(qū)別,那么它和普通的函數(shù)相比,“純”在哪里?
從輸入來(lái)看,參數(shù)是必傳的,而且不能被修改。比如 Math.random 這類(lèi)沒(méi)有參數(shù)的函數(shù)就不是純函數(shù)。
從執(zhí)行來(lái)看,不能引用外部變量或函數(shù)。
從結(jié)果來(lái)看,執(zhí)行必有結(jié)果,而且輸入相同的參數(shù),執(zhí)行的結(jié)果必須相同。
純函數(shù)是將邏輯分離到極致:一段功能單一,邏輯封閉的代碼。
思考:function(x) { return 1 } 是不是純函數(shù)?
如果你的代碼中使用純函數(shù),會(huì)帶來(lái)以下好處:
易測(cè)試。寫(xiě)過(guò)單元測(cè)試的前端開(kāi)發(fā)都知道,前端的單元測(cè)試是很難寫(xiě)的,其中很大一個(gè)原因就是“不純”的函數(shù)太多,一個(gè)函數(shù)可能既要操作DOM,又要發(fā)送ajax請(qǐng)求,還可能引用了不知名的全局變量…AngularJS在官方文檔中就直接指出有些耦合性高的代碼是難以測(cè)試的,所以這也是近些年MVVM框架流行的原因,框架承擔(dān)了DOM操作,開(kāi)發(fā)者只負(fù)責(zé)寫(xiě)邏輯,從而讓代碼邏輯更清晰。
可復(fù)用。 使用Node.js編寫(xiě)服務(wù)端的同學(xué)對(duì)一個(gè)詞肯定熟悉——“同構(gòu)”,也就是說(shuō),一份js代碼可以同時(shí)在服務(wù)端和瀏覽器端正常運(yùn)行,而純函數(shù)是支持同構(gòu)的。
無(wú)副作用。比如多個(gè)不純的函數(shù)同時(shí)修改一個(gè)變量(或操作一個(gè)DOM元素),再加上異步等情況,這樣就很容易引起沖突。而純函數(shù)既不修改入?yún)⒁膊恍薷耐獠孔兞?,所以完全不用?dān)心。
符合純函數(shù)特點(diǎn)的第三方開(kāi)源庫(kù)有非常著名的underscore和lodash,以及更加強(qiáng)大的RxJS。
RxJS是微軟推出的ReactiveX系列(RxJava,Rx.NET,RxScala,RxSwift等)中的一員,目前Github上star數(shù)已經(jīng)超過(guò)17k。
它可以用來(lái)優(yōu)雅地處理異步和事件。主要通過(guò)它的核心類(lèi)型 Observable,以及強(qiáng)大的操作符 (map、filter、reduce、every等,其中大部分都是純函數(shù))來(lái)實(shí)現(xiàn)。
官方給它最直白的定義是
來(lái)一段官方的代碼體驗(yàn)一下:
// 使用普通的 JavaScript 控制按鈕一秒鐘內(nèi)只允許點(diǎn)擊一次
var count = 0;
var rate = 1000;
var lastClick = Date.now() - rate;
var button = document.querySelector('button');
button.addEventListener('click', () => {
if (Date.now() - lastClick >= rate) {
console.log(`Clicked ${++count} times`);
lastClick = Date.now();
}
});
// 使用Rx.js實(shí)現(xiàn)
var button = document.querySelector('button');
Rx.Observable.fromEvent(button, 'click')
.throttleTime(1000)
.scan(count => count + 1, 0)
.subscribe(count => console.log(`Clicked ${count} times`));
使用RxJS的代碼消除了一些中間變量,使用操作符來(lái)分步執(zhí)行邏輯,可讀性更強(qiáng)、耦合性更低,更方便測(cè)試和修改。
關(guān)于RxJS更詳細(xì)介紹在以后的文章中再敘,本文先談一談它的應(yīng)用。關(guān)于RxJS在web端和node.js服務(wù)端的應(yīng)用都不乏文章,所以這一次突破常規(guī),來(lái)講一講在微信小程序(以下簡(jiǎn)稱(chēng)“小程序”)開(kāi)發(fā)中的使用。
直接在小程序中使用RxJS是會(huì)報(bào)錯(cuò)的,所以我建立了一個(gè)開(kāi)源項(xiàng)目來(lái)解決這個(gè)問(wèn)題:RxWX(項(xiàng)目地址:https://github.com/yalishizhude/RxJS )。
封裝了兩個(gè)js文件。
Rx.js。對(duì)Rx.js進(jìn)行了一些修改使其能在小程序中運(yùn)行。
RxWX.js?;赗x.js對(duì)微信的api進(jìn)行了封裝,調(diào)用同名API不再使用回調(diào),而是返回Observalbe對(duì)象。
提供兩種安裝途徑
git clone https://github.com/yalishizhude/RxWX.git
可以直接下載項(xiàng)目,將根目錄的Rx.js和RxWX.js復(fù)制到小程序項(xiàng)目中,也可以訪(fǎng)問(wèn)該網(wǎng)址復(fù)制粘貼這兩個(gè)文件內(nèi)容。
npm i rxjs-wx
將node_modules/rxjs-wx目錄下的Rx.js和RxWX.js復(fù)制到小程序項(xiàng)目中。
小程序的API大多數(shù)都不是按照純函數(shù)的思想設(shè)計(jì)的,把返回結(jié)果賦值給入?yún)⒌膕uccess、fail、complete屬性。
在邏輯簡(jiǎn)單復(fù)雜的情況下很容墮入“回調(diào)地獄”,而且同步和異步的接口調(diào)用方式也不一致。而使用RxJS就可以解決這些問(wèn)題,下面來(lái)看幾個(gè)例子。
假設(shè)有這樣一個(gè)需求,先通過(guò) wx.getUserInfo 獲取用戶(hù)信息,然后傳給后端服務(wù)獲取該用戶(hù)其它信息,顯示在頁(yè)面上。
// 普通代碼
let self = this
wx.getUserInfo({
success: (res) => {
wx.request({
method: 'GET',
url: 'xxx/user',
data: res.userInfo,
success(r) {
self.setData({userInfo:r})
},
fail(e) {
self.setData({userInfo:'not found'})
}
})
},
fail(e) {
console.error(e)
}
})
// 使用RxWX
import obs from './RxWX'
obs.getUserInfo()
.catch(e => console.error(e))
.switchMap(({ userInfo }) => obs.request({ method: 'GET', url: 'xxx/user', data: user }))
.subscribe(userInfo => self.setData({ userInfo: r }), e => self.setData({ userInfo: 'not found' }))
曾經(jīng)在開(kāi)發(fā)小程序的時(shí)候使用navigator組件碰到一個(gè)比較嚴(yán)重的問(wèn)題:快速多次點(diǎn)擊的時(shí)候會(huì)發(fā)生多次頁(yè)面跳轉(zhuǎn),跳轉(zhuǎn)完成后需要多次點(diǎn)擊“返回”才能退回到原頁(yè)面。
為了解決這個(gè)問(wèn)題,一般可以手動(dòng)綁定事件,然后進(jìn)行一個(gè)防抖操作。
// 普通代碼
let tapping = false
...
tap(e) {
if(!tapping) {
wx.navigateTo({ url: '../demo/demo' })
tapping = true
setTimeout(() => tapping=false, 1000)
}
}
// 使用RxWX
import obs from './RxWX'
tap(e) {
obs.navigateTo({ url: '../demo/demo' })
.debounce(1000)
.subscribe()
}
RxWX同時(shí)還支持wx對(duì)象的其它非函數(shù)屬性,比如:
import obs from './RxWX'
console.log(obs.version)
// {info:"", updateTime:"2017.7.10 19:35:05", version:"1.4.0"}
RxJS和RxWX是第三方庫(kù),也是進(jìn)入純函數(shù)世界的大門(mén),更是一種編寫(xiě)更好代碼的思維方式。
本文作者小程序聯(lián)盟社區(qū)博主 搜索關(guān)注個(gè)人公眾號(hào)“web學(xué)習(xí)社”~
本文可被轉(zhuǎn)發(fā)或分享,但必須保留完整圖文信息和出處,作者保留追究一切法律責(zé)任的權(quán)利和手段~