微信小程序的官方開發(fā)工具中,已經(jīng)集成了 babel 插件對 ES6 語法進(jìn)行轉(zhuǎn)換,各種第三方工具自然更少不了了。
所以可以放心的嘗試使用 ES6,體驗新標(biāo)準(zhǔn)帶來的各種便利之處,省下時間后學(xué)習(xí)充電,或者早點下班、鍛煉身體、下廚做個菜,調(diào)節(jié)生活又放松身心,豈不美哉?
那么,在小程序開發(fā)的過程中,有哪些 ES6 特性是可以給我們帶來便利,提高開發(fā)效率的呢?這邊就結(jié)合實例,一一來說一說吧。
做前端開發(fā)的,開始階段基本會遇到 this 與 閉包 帶來的坑————一些異步操作中,回調(diào)函數(shù)中丟失了當(dāng)前函數(shù)的上下文對象,導(dǎo)致異步操作完成后,更新原有上下文失敗。
為了避免這個問題,以前大家都是自己用變量保存一個閉包外部上下文的引用,取的名字可能千奇百怪:
that/_this/$this/self…在異步操作完成后的回調(diào)中,通過調(diào)取這個閉包外層的變量,達(dá)到更新回調(diào)前函數(shù)上下文對象的目的。
ES6 中增加了 箭頭表達(dá)式,效果和匿名函數(shù)相似,但箭頭表達(dá)式更為簡練,且內(nèi)部執(zhí)行時的 this 與外側(cè)一致,不再需要每次都額外增加變量引用了。
微信小程序里,對每個頁面編寫的代碼邏輯,都作為生命周期鉤子函數(shù)(如:onLoad, onShow, onUnload)和自定義函數(shù)(如:各類組件回調(diào)函數(shù))寫在 AppService 內(nèi)。
這兩種函數(shù)內(nèi),this 都指向當(dāng)前 Page 對象,在這些函數(shù)里做的各種異步操作,回調(diào)內(nèi)的 this 基本都應(yīng)該仍然保持為當(dāng)前 Page 對象。在這個情況下,使用箭頭表達(dá)式可以減少重復(fù)的工作、也減少遺漏 this 時出錯的幾率。
// ES5
var net = require('../public/net');
Page({
data: {
list: []
},
onShow: function () {
var self = this;
net.get('/Index/getList', function (res) {
res = res || {};
var status = res.status,
data = res.data,
list = data.list;
if(status == 2) {
self.setData({list: list});
}
});
}
});
// ES6
import * as net from '../public/net';
Page({
data: {
list: []
},
onShow: function () {
net.get('/Index/getList', (res = {}) => {
let {status, data: {list}} = res;
if (status == 2) {
// 此處 this 的指向與 onShow 內(nèi)一致,無需增加 self 變量
this.setData({list});
}
});
}
});
雖然都說微信小程序 wxml 的 Mustache 語法與 Vue.js 很相似。但據(jù)說是為了分離 UI 線程和 AppService 線程,微信小程序暫時并不支持 {{value | filter}} 的寫法。
這時候可以借助于 ES5 中為數(shù)組對象增加的方法,之前因為瀏覽器兼容性問題,不一定全部能用。如今在移動端了,就盡情用起來吧:
輸出數(shù)據(jù)前,對后臺傳來的列表數(shù)據(jù)做一些預(yù)處理后再顯示時,通常使用 Array.prototype.forEach 和 Array.prototype.map 進(jìn)行相應(yīng)處理;
篩選掉無效數(shù)據(jù),可以使用 Array.prototype.filter。
// js
var helpers = {
// 判斷是否有時間參數(shù)
hasTime: (i) => {
return !isNaN(parseInt(i.stamp));
},
// 時間轉(zhuǎn)換處理
parseTime: (i) => {
i.time = new Date(i.stamp + '000');
return i;
}
};
net.get('/Index/getList', (res = {}) => {
let {status, data: {list}} = res;
this.set({
list: list.filter(helpers.hasTime) // 篩選掉沒有時間戳字段的數(shù)據(jù)
.map(helpers.parseTime) // 將時間戳字段轉(zhuǎn)化為 JS 的 Date 對象
});
});
直到寫這篇文章的時候,小程序官方的組件標(biāo)準(zhǔn)也仍然沒有出來。
目前的通常處理方案,一般是通過 template 配合解構(gòu)賦值不同對象的數(shù)據(jù),實現(xiàn)組件各自狀態(tài)、事件處理函數(shù)互相獨立的效果。
如,有兩個 template 都從 data 中綁定數(shù)據(jù)。
< template name="banner">
< view class="banner-wrap">
< view wx:for="{{data}}" class="banner-item">
< !--...-->
< /view>
< /view>
< /template>
< template name="comic-list">
< view class="comic-list">
< view wx:for="{{data}}" class="comic-item">
< !--...-->
< /view>
< /view>
< /template>
AppService 中對于這兩個模板創(chuàng)建兩個不同對象,即可管理自身狀態(tài),不用擔(dān)心字段名重復(fù)的問題。
Page({
onLoad: function () {
// 加載 Banner 數(shù)據(jù)并顯示
this.loadData('/bannerState/get', (data) => {
this.setData({
bannerState: data
});
});
// 加載 ComicList 數(shù)據(jù)并顯示
this.loadData('/comicListState/get', (data) => {
this.setData({
comicListState: data
});
});
},
loadData: function (url, callback) {
var data = [];
/* 從 url 加載數(shù)據(jù)的邏輯 */
setTimeout(() => {
callback({
data: data
});
}, 100);
}
});
頁面內(nèi)渲染模板時,對 bannerState 和 comicListState 字段進(jìn)行解構(gòu)即可。
< template is="banner" data="{{...bannerState}}"/>
< template is="comicList" data="{{...comicListState}}"/>
這是個非常重要且實用的特性,基于這個基礎(chǔ)可以封裝出有具有通用邏輯的基類,實現(xiàn)模塊內(nèi)部的邏輯閉環(huán),達(dá)到組件化開發(fā)的效果。
setData() 中的數(shù)據(jù)字段名與變量名一致時,不需要重復(fù)寫兩遍,上面加載數(shù)據(jù)的代碼就可以這樣簡寫:
this.loadData('/bannerState/get', (bannerState) => {
this.setData({
bannerState
});
});
數(shù)據(jù)字段較多時,效率會快很多。減少了整理和重構(gòu)代碼需要調(diào)整的地方,降低維護(hù)成本。
// 傳統(tǒng)對象字面量
this.setData({
data1: data1,
data2: data2,
data3: data3,
data4: data4,
data5: data5
});
// 增強的對象字面量
this.setData({data1, data2, data3, data4, data5});
增強的對象字面量寫法,還包括函數(shù)的簡寫:
// 傳統(tǒng)的對象字面量
var comicState = {
onTap: function (e) {
// ...
},
onScroll: function (e) {
// ...
}
};
// 增強的對象字面量
var comicState = {
onTap(e) {
// ...
},
onScroll(e) {
// ...
}
};
這種簡潔的成員函數(shù)寫法,是不是很像 class 中的函數(shù)聲明?
class ComicState {
onTap (e) {
// ...
}
onScroll (e) {
// ...
}
}
使用 ES5 的 prototype 寫法,實現(xiàn)簡單的類繼承也沒太大問題,但涉及到父類函數(shù)調(diào)用等情況,代碼耦合度會變得更高,需要一定經(jīng)驗才能寫出方便維護(hù)的代碼。
通過 ES6 語法來實現(xiàn)類繼承的話,有了統(tǒng)一的標(biāo)準(zhǔn),寫出的類繼承更加直觀,更方便調(diào)整。
class Base {
constructor (options, otherArg) {
// Do something.
}
}
class ChildType extends Base {
constructor (options) {
super(options, ChildType);
// Do something.
}
}
使用 for 對數(shù)據(jù)做迭代遍歷時,語句中聲明的 var 型變量名作用域其實提升到了函數(shù)頂部,不同迭代間忘記處理的話,可能會導(dǎo)致數(shù)據(jù)污染。
改為使用 ES6 的 let/const 可避免這一情況,放心使用塊級作用域。
for (let k in data1) {
// ...
}
// ...
let k = 0;
// ...
for (let k in data2) {
// ...
}
微信小程序使用的 babel 啟用的轉(zhuǎn)碼規(guī)則可能不是最新的,截止目前版本,測試使用以下 ES6 會有問題,需要注意。
// 以下代碼在 babel 的 repl 中能正常處理,在小程序開發(fā)工具內(nèi)會報錯
class TestClass {
static MODE = {
NORMAL: 1,
DISABLED: -1
}
}
// 輸出:1
console.log(TestClass.MODE.NORMAL);
20170329 更新:新版本開發(fā)工具似乎已經(jīng)完善了這個問題,可以使用下面的 ES6 寫法了:
function Test() {
this.a = 1;
this.b = 2;
}
Test.prototype = {
c: 3
};
var o = new Test();
// ES5
for (var k in o) {
if (k.hasOwnProperty(k)) {
console.log(o[k]);
}
} // 輸出:1 2
// ES6
for (let k of Object.keys(o)) {
console.log(o[k]);
} // 輸出:1 2
for...of 用于數(shù)組遍歷時,效果與 Array.prototype.forEach 類似,區(qū)別是可以在途中 break 中斷循環(huán),無需每次遍歷整個數(shù)組。
// Array.prototype.forEach
comicList.forEach((c) => {
// ...
});
// for...of
for (let c of comicList) {
// ...
}