wframe不是控件庫,也不是UI庫,她是一個微信小程序面向對象編程框架,代碼只有幾百行。她的主要功能是規范小程序項目的文件結構、規范應用程序初始化、規范頁面加載及授權管理的框架,當然,wframe也提供了一些封裝好了的函數庫,方便開發者調用。
wframe目前已實現的核心功能:
1. 應用程序初始化自動從服務器獲取配置,ajax成功后觸發ready事件;
2. 每個頁面對象可以配置是否requireLogin屬性,如果需要登錄,則每個頁面在進入ready方法之前會自動完成授權、獲取用戶信息、服務器端登錄;
3. 完成ajax全局封裝:如果用戶已經登錄,則會自動在http-header添加token信息,如果session過期則會重新進入登錄流程;
本文的閱讀對象:想要自己搭建小程序框架的人,相信本文會給你提供一些思路。
我們為什么要開發wframe?
我們開發的小程序越來越多,小程序也越來越復雜,於是我們就想將每個小程序重復在寫的那一部分代碼提出來,變成一個公共的函數庫,一個跟每個項目的業務邏輯完全不相關的函數庫。除了在新項目中可以節省代碼之外,有一些復雜的代碼邏輯由於提到了公共的函數庫,我們將其優化得更優雅、更健壯。
說wframe是一個函數庫雖說也可以,但wframe更像一個框架。我們通常把一些靜態方法、靜態對象、僅處理頁面內容的JS文件集稱作函數庫,比如jQuery;我們通常把處理了應用程序和頁面生命周期,以及使用了大量的面向對象編程技術的JS文件集稱作框架。因此,wframe其實是一個框架。
重要說明:wframe框架用到了大量的面向對象編程知識,比如實例、繼承、覆寫、擴展、抽象方法等等,因此對開發人員,特別是項目中的架構師,的面向對象編程能力有較高要求。
項目源碼已上傳到GitHub並會持續更新:https://github.com/leotsai/wframe
一、wframe項目結構
wframe的最核心的職責就是規范項目文件結構。
為什么需要規范呢?因為我們小程序越來越多,如果每個小程序的文件結構都不一樣的話,那定是一件很難受的事。另外,wframe由於其框架的身份,其本職工作就是定義一個最好的文件結構,這樣基於wframe創建的所有小程序都將自動繼承wframe的優秀品質。
1. _core文件夾
wframe框架源碼,與業務毫不相干,每個小程序都可以直接將_core文件夾復制到項目中,而當wframe更新版本時,所有小程序可以直接覆蓋_core完成升級。用下划線“_”開頭的目的有2個:
(a) 將此文件夾置頂;
(b) 標記此文件夾是一個特殊文件夾,本框架中還有其他地方也會用到下划線開頭為文件夾/文件。
2. _demo文件夾
業務核心文件夾,比如定義一些擴展wframe框架的類,同時這些類又被具體的業務類繼承使用,比如ViewModelBase等。
3. pages文件夾
與微信小程序官方文檔定義一致:放置頁面的地方。
4. app.js
程序主入口,只不過基於wframe的小程序的app.js跟官方的長得很不一樣,我們定義了一個自己的Applicaiton類,然后再new的一個Application實例。稍后詳解。
5. mvcApp.js
幾乎每個js文件都會require引入的一個文件,因為這相當於是項目的靜態入口,其包含了所有的靜態函數庫,比如對wx下面方法的封裝、Array類擴展、Date類擴展、網絡請求(ajax)封裝等等。mvcApp.js幾乎只定義了一個入口,其內部的很多對象、方法都是通過require其他JS引入的。因此,大多數情況下,我們只需要require引入mvcApp.js就夠了。
寫到這里,分享一個我們的編程思想:入口要少。小程序里有哪些入口:this、getApp()、wx、mvcApp。其實也就是每一行代碼點號“.”前面的都叫代碼入口。
我們還有另一個編程規范(強制):每個文件不能超過200行代碼(最好不超100行)。這就是要求每個程序員必須學會拆分,拆分也是我們的另一個編程思想。通過拆分,每個JS文件職責清晰,極大的提高了代碼閱讀率。
二、詳解
1. app.js和Application類詳解
app.js定義了程序入口。
1 var mvcApp = require('mvcApp.js'); 2 var Application = require('_core/Application.js'); 3 4 function MvcApplication() { 5 Application.call(this); 6 this.initUrl = 'https://www.somdomain.com/api/client-config/get?key=wx_applet_wframe'; 7 this.host = 'http://localhost:18007'; 8 this.confgis = { 9 host: 'http://localhost:18007', 10 cdn: 'https://images.local-dev.cdn.somedomain.com' 11 }; 12 this.mock = true; 13 this.accessToken = null; 14 this.useDefaultConfigsOnInitFailed = false; 15 }; 16 17 MvcApplication.prototype = new Application(); 18 19 MvcApplication.prototype.onInitialized = function (configs) { 20 if (configs != null && configs !== '') { 21 this.configs = JSON.parse(configs); 22 this.host = this.configs.host; 23 } 24 }; 25 26 App(new MvcApplication());
可以看到app.js定義了一個MvcApplication類,繼承自框架中的Application類,同時重寫了父類的onInitialized方法。
下面是框架中的Application類:
1 var WebClient = require('http/WebClient.js'); 2 var AuthorizeManager = require('weixin/AuthorizeManager.js'); 3 var weixin = require('weixin.js'); 4 5 6 function Application() { 7 this.initUrl = ''; 8 this.host = ''; 9 this.session = null; 10 this.initialized = false; 11 this.mock = false; 12 this.useDefaultConfigsOnInitFailed = false; 13 this.authorizeManager = new AuthorizeManager(); 14 this._userInfo = null; 15 this._readyHandlers = []; 16 }; 17 18 Application.prototype = { 19 onLaunch: function () { 20 var me = this; 21 if(this.initUrl === ''){ 22 throw 'please create YourOwnApplication class in app.js that inerits from Application class and provide initUrl in constructor'; 23 } 24 var client = new WebClient(); 25 client.post(this.initUrl, null, function(result){ 26 if (result.success || me.useDefaultConfigsOnInitFailed){ 27 me.initialized = true; 28 me.onInitialized(result.success ? result.value : null); 29 me.triggerReady(); 30 } 31 else{ 32 weixin.alert('小程序初始化失敗', result.message); 33 } 34 }, '初始化中...'); 35 }, 36 onShow: function () { 37 38 }, 39 onHide: function () { 40 41 }, 42 onError: function () { 43 44 }, 45 onPageNotFound: function () { 46 47 }, 48 ready: function (callback) { 49 var me = this; 50 if (this.initialized === true) { 51 callback && callback(); 52 return; 53 } 54 this._readyHandlers.push(callback); 55 }, 56 triggerReady: function () { 57 for (var i = 0; i < this._readyHandlers.length; i++) { 58 var callback = this._readyHandlers[i]; 59 callback && callback(); 60 } 61 this._readyHandlers = []; 62 }, 63 onInitialized: function(configs){ 64 65 }, 66 getUserInfo: function(callback){ 67 var me = this; 68 if(this._userInfo != null){ 69 callback && callback(this._userInfo.userInfo); 70 return; 71 } 72 this.authorizeManager.getUserInfo(function(result){ 73 me._userInfo = result; 74 callback && callback(me._userInfo.userInfo); 75 }); 76 }, 77 getCurrentPage: function(){ 78 var pages = getCurrentPages(); 79 return pages.length > 0 ? pages[0] : null; 80 } 81 }; 82 83 module.exports = Application;
Applicaiton類(及其子類)在wframe框架中的主要工作:
1. 應用程序初始化的時候從服務器獲取一個配置,比如服務器域名(實現域名實時切換)、CDN域名,以及其他程序配置信息;
2. 全局存儲用戶的授權信息和登陸之后的會話信息;
3. 全局mock開關;
4. 其他快捷方法,比如獲取當前頁面等。
Application類核心執行流程:
1. 應用程序初始化時首先從服務器獲取客戶端配置信息;
2. 獲取完成之后會觸發onInitialized方法(在子類中覆寫)和ready方法。
2. PageBase類詳解
PageBase類是所有頁面都會繼承的一個基類。先看代碼:
1 console.log("PageBae.js entered"); 2 3 const app = getApp(); 4 5 function PageBase(title) { 6 this.vm = null; 7 this.title = title; 8 this.requireLogin = true; 9 }; 10 11 PageBase.prototype = { 12 onLoad: function (options) { 13 var me = this; 14 if (this.title != null) { 15 this.setTitle(this.title); 16 } 17 this.onPreload(options); 18 app.ready(function () { 19 if (me.requireLogin && app.session == null) { 20 app.getUserInfo(function (info) { 21 me.login(info, function (session) { 22 app.session = session; 23 me.ready(options); 24 }); 25 }); 26 } 27 else { 28 me.ready(options); 29 } 30 }); 31 }, 32 ready: function (options) { 33 34 }, 35 onPreload: function(options){ 36 37 }, 38 render: function () { 39 var data = {}; 40 for (var p in this.vm) { 41 var value = this.vm[p]; 42 if (!this.vm.hasOwnProperty(p)) { 43 continue; 44 } 45 if (value == null || typeof (value) === 'function') { 46 continue; 47 } 48 if (value.__route__ != null) { 49 continue; 50 } 51 data[p] = this.vm[p]; 52 } 53 this.setData(data); 54 }, 55 go: function (url, addToHistory) { 56 if (addToHistory === false) { 57 wx.redirectTo({ url: url }); 58 } 59 else { 60 wx.navigateTo({ url: url }); 61 } 62 }, 63 goBack: function () { 64 wx.navigateBack({}); 65 }, 66 setTitle: function (title) { 67 this.title = title; 68 wx.setNavigationBarTitle({ title: this.title }); 69 }, 70 login: function (userInfo, callback) { 71 throw 'please implement PageBase.login method.'; 72 }, 73 getFullUrl: function () { 74 var url = this.route.indexOf('/') === 0 ? this.route : '/' + this.route; 75 var parts = []; 76 for (var p in this.options) { 77 if (this.options.hasOwnProperty(p)) { 78 parts.push(p + "=" + this.options[p]); 79 } 80 } 81 if (parts.length > 0) { 82 url += "?" + parts.join('&'); 83 } 84 return url; 85 }, 86 isCurrentPage: function(){ 87 return this === getApp().getCurrentPage(); 88 } 89 }; 90 91 PageBase.extend = function (prototypeObject) { 92 var fn = new PageBase(); 93 for (var p in prototypeObject) { 94 fn[p] = prototypeObject[p]; 95 } 96 return fn; 97 }; 98 99 module.exports = PageBase;
由於微信小程序Application類的onLaunch不支持回調,也就是說,在wframe框架中,雖然我們在onLaunch時發起了ajax調用,但是程序並不會等待ajax返回就會立即進入Page對象的onLoad方法。這是一個非常重要的開發小程序的知識前提,但是官方文檔並沒有重要說明。
PageBase類的三個實例屬性:
1. vm:即ViewModel實例,可以理解為官方文檔中的Page實例的data屬性;
2. title:頁面標題
3. requireLogin:是否需要登錄,如果設置為true,則頁面onLoad執行后自動進入登錄流程,登錄完成后才會觸發頁面的ready方法;
PageBase類的實例方法:
1. onLoad:對應官方文檔中的onLoad事件。wframe框架自動會處理requireLogin屬性,處理完成后才觸發ready方法;
2. ready:每個業務級頁面的主入口,每個業務級頁面都應該實現ready方法,而不一定實現onLoad方法;
3. onPreload:在執行onLoad之前執行的方法,不支持異步;
4. render:非常常用的方法,功能是將ViewModel(即data)呈現到頁面上,在業務頁面中直接使用this.render()即可將更新的數據呈現出來;
5. go:頁面跳轉,相比官方的wx.navigateTo簡化了很多;
6. goBack:等於wx.navigateBack;
7. setTitle:直接設置頁面標題;
8. login:可以理解成抽象方法,必須由子類實現,在我們demo中由業務級框架中的DemoPageBase實現;
9. getFullUrl:獲取頁面完整地址,包括路徑和參數,便於直接跳轉;
10. isCurrentPage:判斷該頁面實例是否在應用程序頁面棧中處於當前頁面,主要用於setInterval函數中判斷用戶是否已離開了頁面;
3. DemoPageBase類詳解
這是業務層級的框架內容。我們建議每個頁面都繼承自該類,這個類可以封裝跟業務相關的很多邏輯,方便子類(業務頁面)直接通過this調用相關方法。
在wframe的demo框架中,我們實現了PageBase類的抽象方法login。
這里請注意同目錄的api.js文件。在我們的編碼規范中,所有ajax訪問都需要提到專門的api.js文件,通常與頁面類處於同一目錄,這是為了方便mock API。請看示例代碼:
1 var mvcApp = require('../mvcApp.js'); 2 3 var api = { 4 login: function (userInfo, code, callback) { 5 var data = mvcApp.serializeToKeyValues(userInfo) + "&code=" + code; 6 mvcApp.ajax.busyPost('/demo/api/login', data, function(result){ 7 callback(result.value); 8 }, '登陸中...', true); 9 } 10 }; 11 if (getApp().mock) { 12 var api = { 13 login: function (userInfo, code, callback) { 14 setTimeout(function(){ 15 callback({ 16 token: '98c2f1bd7beb3bef3b796a5ebf32940498cb5586ddb4a5aa8e' 17 }); 18 }, 2000); 19 } 20 }; 21 } 22 23 module.exports = api;
4. 頁面類的實現
請看pages/index目錄下的文件列表:
1. IndexViewModel:該頁面的ViewModel;
2. api.js:該頁面所有ajax的封裝;
3. index.js:頁面入口;
4. index.wxml:HTML;
5. index.wxss:樣式;
先看入口index.js,代碼如下:
1 var mvcApp = require('../../mvcApp.js'); 2 var DemoPageBase = require('../DemoPageBase.js'); 3 var IndexViewModel = require('IndexViewModel.js'); 4 5 function IndexPage() { 6 DemoPageBase.call(this, 'index'); 7 }; 8 9 IndexPage.prototype = new DemoPageBase(); 10 11 IndexPage.prototype.onPreload = function(options){ 12 this.vm = new IndexViewModel(this); 13 this.render(); 14 }; 15 16 IndexPage.prototype.ready = function () { 17 var me = this; 18 this.vm.load(); 19 }; 20 21 IndexPage.prototype.goDetails = function (e) { 22 var item = e.target.dataset.item; 23 wx.navigateTo({ 24 url: '/pages/details/details?id=' + item.id 25 }); 26 }; 27 28 Page(new IndexPage());
index.js核心邏輯:繼承自DemoPageBase,onPreload時設置了ViewModel,ready時(自動登錄完成后)調用ViewModel的數據加載方法,完成。
5. ViewModel的實現
在微信小程序官方文檔中,並沒有提ViewModel的概念,這會導致一些稍微有點復雜的頁面的data對象的處理變得很凌亂,更別說復雜頁面的data處理,那根本無從維護。ViewModel的設計思想是專門用來封裝視圖數據的一層代碼,不管是MVC,還是MVVM,ViewModel都是拆分數據層代碼的最佳實踐。因此,wframe框架強烈建議每個頁面都建一個對應的ViewModel,封裝數據結構,以及獲取、處理數據。
在我們的編程思想中,ViewModel不僅僅是放數據的地方,更是封裝業務邏輯的最佳位置之一。所以我們的ViewModel會很肥(fat model),會包含相關的很多業務邏輯處理。
如果項目需要,還可以封裝一個DemoViewModelBase類,將其他頁面ViewModel常用的方法封裝進來,比如this.getUserName()等方法。
請看示例代碼:
1 var api = require('api.js'); 2 var mvcApp = require('../../mvcApp.js'); 3 4 function IndexViewModel(page){ 5 this.users = []; 6 this.showLoading = true; 7 this.males = 0; 8 this.females = 0; 9 this.page = page; 10 }; 11 12 IndexViewModel.prototype.load = function(){ 13 var me = this; 14 api.getUsers(function(users){ 15 me.showLoading = false; 16 me.females = users._count(function(x){ 17 return x.gender === 'female'; 18 }); 19 me.males = users._count(function (x) { 20 return x.gender === 'male'; 21 }); 22 me.users = users._orderByDescending(null, function(first, second){ 23 if(first.gender === 'male'){ 24 if(second.gender === 'male'){ 25 return first.birthYear > second.birthYear; 26 } 27 return true; 28 } 29 if(second.gender === 'female'){ 30 return first.birthYear > second.birthYear; 31 } 32 return false; 33 }); 34 me.page.render(); 35 }); 36 }; 37 38 module.exports = IndexViewModel;
api.js就不貼代碼了,跟上一小節中的api.js一樣的。html和css部分也忽略不講。
至此,頁面級實現就完成了。
下面,筆者再對wframe框架中的其他特殊部分進行特殊說明。繼續。
6. pages/_authorize文件夾
這個文件夾定義了一個授權頁面,這是因為新版小程序API強制要求用戶自己點授權按鈕才能彈出授權。這個雖然集成在wframe框架中,但是每個項目應該自行修改此頁面的樣式以符合項目UI設計。
這個目錄下面只有一個_authorize.js值得貼一下代碼,其實都非常簡單:
1 var DemoPageBase = require('../DemoPageBase.js'); 2 3 4 function AuthPage() { 5 DemoPageBase.call(this, 'auth'); 6 this.requireLogin = false; 7 }; 8 9 AuthPage.prototype = new DemoPageBase(); 10 11 AuthPage.prototype.onPreload = function (options) { 12 this.returnUrl = decodeURIComponent(options.returnUrl); 13 }; 14 15 AuthPage.prototype.onGotUserInfo = function (event) { 16 var me = this; 17 if (event.detail.userInfo == null) { 18 return; 19 } 20 var app = getApp(); 21 app._userInfo = event.detail; 22 DemoPageBase.prototype.login.call(this, app._userInfo.userInfo, function () { 23 me.go(me.returnUrl, false); 24 }) 25 } 26 27 Page(new AuthPage())
請注意onPreload方法中對returnUrl的獲取,以及獲取用戶授權信息后對DemoPageBase.login方法的調用。
7. _core文件夾其他文件詳解
_core文件夾之前已經講了Application和PageBase類。繼續。
1. weixin.js
主要封裝了toast、busy(增加延時功能)、alert、confirm方法,后期可能會增加更多常用方法的封裝。代碼如下:

1 var weixin = { 2 _busyTimer: null, 3 _busyDelay: 1500, 4 toast: function (message, icon) { 5 wx.showToast({ 6 title: message, 7 icon: icon == null || icon == '' ? 'none' : icon 8 }); 9 }, 10 toastSuccess: function (message) { 11 this.toast(message, 'success'); 12 }, 13 busy: function (option, delay) { 14 clearTimeout(this._busyTimer); 15 if (option === false) { 16 wx.hideLoading(); 17 return; 18 } 19 if (delay === 0) { 20 wx.showLoading({ 21 title: option, 22 mask: true 23 }); 24 } 25 else { 26 this._busyTimer = setTimeout(function () { 27 wx.showLoading({ 28 title: option, 29 mask: true 30 }); 31 }, delay == null ? this._busyDelay : delay); 32 } 33 }, 34 alert: function (title, content, callback) { 35 content = content == undefined ? '' : content; 36 wx.showModal({ 37 title: title, 38 content: content, 39 showCancel: false, 40 confirmText: "確定", 41 success: res => { 42 callback && callback(); 43 } 44 }); 45 }, 46 confirm: function (title, content, buttons) { 47 var buttonList = []; 48 for (var p in buttons) { 49 if (buttons.hasOwnProperty(p)) { 50 buttonList.push({ 51 text: p, 52 handler: buttons[p] 53 }) 54 } 55 } 56 content = content == undefined ? '' : content; 57 wx.showModal({ 58 title: title, 59 content: content, 60 showCancel: true, 61 cancelText: buttonList[0].text, 62 confirmText: buttonList[1].text, 63 success: res => { 64 if (res.confirm) { 65 buttonList[1].handler && buttonList[1].handler(); 66 } else if (res.cancel) { 67 buttonList[0].handler && buttonList[0].handler(); 68 } 69 } 70 }); 71 } 72 }; 73 74 module.exports = weixin;
2. extensions/ArrayExtensions.js
一大堆數組擴展方法,非常常用,非常好用。引入mvcApp的業務層代碼均可直接使用。代碼如下:

1 var ArrayExtensions = {}; 2 3 Array.prototype._each = function (func) { 4 for (var i = 0; i < this.length; i++) { 5 var item = this[i]; 6 var result = func(i, item); 7 if (result === false) { 8 return; 9 } 10 } 11 }; 12 13 Array.prototype._sum = function (propertyOrFunc) { 14 var total = 0; 15 var isFunc = typeof (propertyOrFunc) == "function"; 16 this._each(function (i, item) { 17 if (isFunc) { 18 total += propertyOrFunc(item); 19 } else { 20 var value = item[propertyOrFunc]; 21 if (value != undefined) { 22 value = value * 1; 23 if (!isNaN(value)) { 24 total += value; 25 } 26 } 27 } 28 }); 29 return total; 30 }; 31 32 Array.prototype._where = function (predicateFunction) { 33 var results = new Array(); 34 this._each(function (i, item) { 35 if (predicateFunction(item)) { 36 results.push(item); 37 } 38 }); 39 return results; 40 }; 41 42 Array.prototype._orderBy = function (property, isFirstGreaterThanSecondFunction) { 43 var items = this; 44 for (var i = 0; i < items.length - 1; i++) { 45 for (var j = 0; j < items.length - 1 - i; j++) { 46 if (isFirstGreaterThanSecond(items[j], items[j + 1])) { 47 var temp = items[j + 1]; 48 items[j + 1] = items[j]; 49 items[j] = temp; 50 } 51 } 52 } 53 function isFirstGreaterThanSecond(first, second) { 54 if (isFirstGreaterThanSecondFunction != undefined) { 55 return isFirstGreaterThanSecondFunction(first, second); 56 } 57 else if (property == undefined || property == null) { 58 return first > second; 59 } 60 else { 61 return first[property] > second[property]; 62 } 63 } 64 65 return items; 66 }; 67 68 Array.prototype._orderByDescending = function (property, isFirstGreaterThanSecondFunction) { 69 var items = this; 70 for (var i = 0; i < items.length - 1; i++) { 71 for (var j = 0; j < items.length - 1 - i; j++) { 72 if (!isFirstGreaterThanSecond(items[j], items[j + 1])) { 73 var temp = items[j + 1]; 74 items[j + 1] = items[j]; 75 items[j] = temp; 76 } 77 } 78 } 79 function isFirstGreaterThanSecond(first, second) { 80 if (isFirstGreaterThanSecondFunction != undefined) { 81 return isFirstGreaterThanSecondFunction(first, second); 82 } 83 else if (property == undefined || property == null) { 84 return first > second; 85 } 86 else { 87 return first[property] > second[property]; 88 } 89 } 90 91 return items; 92 }; 93 94 Array.prototype._groupBy = function (property) { 95 var results = []; 96 var items = this; 97 98 var keys = {}, index = 0; 99 for (var i = 0; i < items.length; i++) { 100 var selector; 101 if (typeof property === "string") { 102 selector = items[i][property]; 103 } else { 104 selector = property(items[i]); 105 } 106 if (keys[selector] === undefined) { 107 keys[selector] = index++; 108 results.push({ key: selector, value: [items[i]] }); 109 } else { 110 results[keys[selector]].value.push(items[i]); 111 } 112 } 113 return results; 114 }; 115 116 Array.prototype._skip = function (count) { 117 var items = new Array(); 118 for (var i = count; i < this.length; i++) { 119 items.push(this[i]); 120 } 121 return items; 122 }; 123 124 Array.prototype._take = function (count) { 125 var items = new Array(); 126 for (var i = 0; i < this.length && i < count; i++) { 127 items.push(this[i]); 128 } 129 return items; 130 }; 131 132 Array.prototype._firstOrDefault = function (predicateFunction) { 133 if (this.length == 0) { 134 return null; 135 } 136 if (predicateFunction == undefined) { 137 return this[0]; 138 } 139 var foundItem = null; 140 this._each(function (i, item) { 141 if (predicateFunction(item)) { 142 foundItem = item; 143 return false; 144 } 145 return true; 146 }); 147 return foundItem; 148 }; 149 150 Array.prototype._any = function (predicateFunction) { 151 if (predicateFunction == undefined) { 152 return this.length > 0; 153 } 154 var hasAny = false; 155 this._each(function (i, item) { 156 if (predicateFunction(item)) { 157 hasAny = true; 158 return false; 159 } 160 return true; 161 }); 162 return hasAny; 163 }; 164 165 Array.prototype._select = function (newObjectFunction) { 166 if (newObjectFunction == undefined) { 167 throw "parameter newObjectFunction cannot be null or undefined"; 168 } 169 var items = []; 170 this._each(function (i, item) { 171 items.push(newObjectFunction(item)); 172 }); 173 return items; 174 }; 175 176 Array.prototype._insert = function (index, item) { 177 this.splice(index, 0, item); 178 }; 179 180 Array.prototype._insertMany = function (index, items) { 181 if (items == null) { 182 return; 183 } 184 for (var i = 0; i < items.length; i++) { 185 this._insert(index + i, items[i]); 186 } 187 }; 188 189 Array.prototype._add = function (item) { 190 this.push(item); 191 }; 192 193 Array.prototype._addMany = function (items) { 194 if (items == null) { 195 return; 196 } 197 for (var i = 0; i < items.length; i++) { 198 this.push(items[i]); 199 } 200 }; 201 202 Array.prototype._clear = function () { 203 this.splice(0, this.length); 204 }; 205 206 Array.prototype._count = function (predicateFunction) { 207 var count = 0; 208 this._each(function (i, item) { 209 if (predicateFunction(item)) { 210 count++; 211 } 212 }); 213 return count; 214 }; 215 216 217 /************************************** */ 218 module.exports = ArrayExtensions;
3. http/WebClient.js
封裝網絡請求,我們叫ajax。增加busy、header、自動異常處理等邏輯。非常常用,非常好用。代碼如下:

1 var weixin = require('../weixin.js'); 2 3 function WebClient() { 4 this.busyText = null; 5 this.busyDelay = 1500; 6 this.url = ''; 7 this.data = null; 8 this.method = 'GET'; 9 this.contentType = 'application/x-www-form-urlencoded'; 10 this.dataType = 'json'; 11 this.onlyCallbackOnSuccess = false; 12 this._request = null; 13 this._callback = null; 14 this._header = {}; 15 }; 16 17 WebClient.prototype = { 18 setHeader: function(key, value){ 19 this._header[key] = value; 20 }, 21 removeHeader: function(key){ 22 delete this.header[key]; 23 }, 24 get: function (url, callback, busyText, onlyCallbackOnSuccess){ 25 this.method = 'GET'; 26 this.url = url; 27 this.data = null; 28 this._callback = callback; 29 this.busyText = busyText; 30 this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess; 31 this.execute(); 32 }, 33 post: function (url, data, callback, busyText, onlyCallbackOnSuccess) { 34 this.method = 'POST'; 35 this.url = url; 36 this.data = data; 37 this._callback = callback; 38 this.busyText = busyText; 39 this.onlyCallbackOnSuccess = onlyCallbackOnSuccess == null ? false : onlyCallbackOnSuccess; 40 this.execute(); 41 }, 42 execute: function () { 43 var me = this; 44 if (this.busyText != null && this.busyText !== '') { 45 weixin.busy(me.busyText, me.busyDelay); 46 } 47 this._request = wx.request({ 48 url: me.url, 49 data: me.data, 50 method: me.method, 51 header: me._getRequestHeader(), 52 success: (response) => { 53 if (me.busyText != null) { 54 weixin.busy(false); 55 } 56 me._onResponse(response); 57 }, 58 fail: res => { 59 if (me.busyText != null) { 60 weixin.busy(false); 61 } 62 me._handleError({ statusCode: 500 }); 63 } 64 }); 65 }, 66 _getRequestHeader: function(){ 67 var header = {}; 68 if(this.contentType != null && this.contentType !== ''){ 69 header['content-type'] = this.contentType; 70 } 71 for(var p in this._header){ 72 if(this._header.hasOwnProperty(p)){ 73 header[p] = this._header[p]; 74 } 75 } 76 return header; 77 }, 78 _onResponse: function (response) { 79 if (response.statusCode === 200) { 80 if (this.onlyCallbackOnSuccess === false) { 81 this._callback && this._callback(response.data); 82 } else { 83 if (response.data.success === true) { 84 this._callback && this._callback(response.data); 85 } else { 86 weixin.alert("提示", response.data.message); 87 } 88 } 89 } 90 else { 91 this._handleError(response); 92 } 93 }, 94 _handleError: function (response) { 95 if (response.statusCode === 0 && err.statusText === "abort") { 96 return; 97 } 98 if (this.onlyCallbackOnSuccess) { 99 weixin.alert("網絡錯誤", "錯誤碼:" + response.statusCode); 100 } else { 101 this._callback && this._callback({ 102 success: false, 103 message: "網絡錯誤:" + response.statusCode, 104 code: response.statusCode 105 }); 106 } 107 } 108 }; 109 110 module.exports = WebClient;
4. weixin/AuthorizeManager.js
wframe框架自帶的授權管理器,在Application初始化時已賦值到Application.authorizeManager實例屬性上面,因此,如果想要自定義實現AuthorizeManager,那么可以繼承框架中的默認AuthorizeManager,然后再重寫部分方法,然后在初始化Applicaiton的時候注入不同的實現類即可。
這個類的實例已經添加到Application實例,所以可以通過 getApp().authorizeManager.authorize('your-scope-name', callback) 彈出授權。

1 var weixin = require('../weixin.js'); 2 3 4 5 function AuthorizeManager() { 6 this.pageUrl = '/pages/_authorize/_authorize'; 7 }; 8 9 AuthorizeManager.scopes = { 10 userInfo: 'scope.userInfo' 11 }; 12 13 AuthorizeManager.prototype = { 14 authorize: function (scope, callback) { 15 var me = this; 16 me._isAuthorized(scope, function (authorized) { 17 if (authorized) { 18 callback(); 19 } 20 else { 21 me._showAuthorize(scope, callback); 22 } 23 }); 24 }, 25 getUserInfo: function (callback) { 26 var me = this; 27 var scope = AuthorizeManager.scopes.userInfo; 28 function handleAuthorized() { 29 wx.getUserInfo({ 30 success: function (res) { 31 callback && callback(res); 32 }, 33 fail: function (res) { 34 var url = getApp().getCurrentPage().getFullUrl(); 35 wx.redirectTo({ 36 url: me.pageUrl + "?returnUrl=" + encodeURIComponent(url) 37 }); 38 } 39 }) 40 }; 41 me.authorize(scope, handleAuthorized); 42 }, 43 _isAuthorized: function (scope, callback) { 44 wx.getSetting({ 45 success: function (res) { 46 callback(res.authSetting[scope] === true); 47 } 48 }); 49 }, 50 _showAuthorize: function (scope, callback) { 51 var me = this; 52 wx.authorize({ 53 scope: scope, 54 success: function () { 55 callback(); 56 }, 57 fail: function (res) { 58 if (scope === AuthorizeManager.scopes.userInfo) { 59 callback(); 60 } 61 else { 62 me._openAuthorizeSetting(scope, callback); 63 } 64 } 65 }) 66 }, 67 _openAuthorizeSetting: function (scope, calback) { 68 var me = this; 69 weixin.alert('提示', '您需要授權才能繼續操作', function () { 70 wx.openSetting({ 71 success: function (res) { 72 if (!res.authSetting[scope]) { 73 me._openAuthorizeSetting(scope, callback); 74 } else { 75 callback && callback(); 76 } 77 } 78 }) 79 }); 80 } 81 }; 82 83 module.exports = AuthorizeManager;
三、結語
wframe會持續更新,我們會持續將項目中的最佳實踐、框架優化等添加進來。
使用wframe框架開發小程序,那才能真正的體會JS面向對象的編程體驗,這種體驗是相當的美妙。希望小程序官方可以盡早引入wframe的設計思想,讓小程序開發體驗變成完完全全的面向對象開發體驗。
THE END.