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