我們在開始微信小程序開發的時候,對JS,HTML等前端知識一無所知,完完全全就是門外漢在嘗試一個新的方向。
在下載好開發工具,微信就已經提供了一個DEMO例子:
從程序開發的角度來看這個陌生的目錄結構,pages是存放頁面的,utils是存放工具類的,而app開頭的三個文件既然放在根目錄級別,那么按理講,應該是和配置有關。
我們看app.js文件的內容:
//app.js App({ onLaunch: function () { //調用API從本地緩存中獲取數據 var logs = wx.getStorageSync('logs') || [] logs.unshift(Date.now()) wx.setStorageSync('logs', logs) }, getUserInfo:function(cb){ var that = this if(this.globalData.userInfo){ typeof cb == "function" && cb(this.globalData.userInfo) }else{ //調用登錄接口 wx.login({ success: function () { wx.getUserInfo({ success: function (res) { that.globalData.userInfo = res.userInfo typeof cb == "function" && cb(that.globalData.userInfo) } }) } }) } }, globalData:{ userInfo:null } })
根據官方文檔的說明,這個文件用於編寫微信小程序的頁面邏輯。
App函數用於注冊一個小程序,onLanuch用於處理小程序的初始化,當小程序初始化完成的時候會調用一次。
onLanuch這里的處理是取出wx中的log,然后再把當前日期添加進去。
wx是一個命名空間,相當於一個庫,它有很多公共方法。
我們這里的操作和Android中使用SP(SharePreferences)是差不多的,wx有個本地緩存,這個緩存可以根據相關的key值取出對應的內容。這里有個有趣的語法:|| [],在javascript中,表示如果這個變量如果是undefined,null,NAN,false,0中的任意一種,就設置為一個空的數組,可以理解為?:的用法。然后調用javascript的unshift方法,把當前日期插入數組的第一個元素。
getUserInfo是自定義的函數,傳入一個函數作為參數,而且這里還定義了全局對象globalData,它有一個字段userInfo,初始值為null,通過判斷userInfo是否為空,非空則在cb為函數類型的情況下調用cb,空的情況下,則通過調用wx.login方法,在success的情況下調用wx的getUserInfo獲取userInfo。
這個js文件已經大概的展示了javascript的很多基本語法,因為javascript是動態語言,它是面向函數語言,因此和java這種面向對象語言在語義實現上,差別相當大,我們可以簡單的理解為java操作的是對象,而javascript操作的是函數,而wx.login中的參數就是一個對象,因此用{}包起來,函數其實也是一個對象,所以它后面同樣也有{},這個對象就是loginObject,在它success的時候調用wx.getUserInfo函數。
無論是面向對象還是面向函數,本質上都是體現開發人員理解問題的思路,只是語義實現上不同而已,畢竟javascript和java要解決的問題所在的領域有着相當大的差異。
我們再看看app.json這個文件:
{ "pages":[ "pages/index/index", "pages/logs/logs" ], "window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "WeChat", "navigationBarTextStyle":"black" } }
根據文檔的解釋,這個文件是對微信小程序進行全局配置,決定頁面文件的路徑、窗口表現、設置網絡超時時間、設置多tab等。
pages用於設置頁面的路徑,是一個數組,我們這個DEMO的頁面只有兩個:index和logs,其中第一個元素,index,是小程序的第一個頁面,每次新增或者刪除某個頁面,都要在這里進行修改。
window用來設置默認的窗口的屬性,顯然app.json是設置整個小程序窗口的默認屬性,會被具體頁面的相關屬性覆蓋。
最后是app.wxss:
/**app.wxss**/ .container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; padding: 200rpx 0; box-sizing: border-box; }
這個文件其實就是CSS文件,不過微信自己本身做了一些處理,這個文件的內容就是設置了container這個節點視圖的公共屬性。
從這三個根目錄的文件,我們也大概知道一個小程序的頁面的組成結構,但是還少了一個文件,那就是頁面視圖的文件。
DEMO的logs頁面就包含了四個文件:.json,.js,.wxss和.wxml。
我們看一下logs.wxml這個文件:
<!--logs.wxml--> <view class="container log-list"> <block wx:for="{{logs}}" wx:for-item="log" wx:key="*this"> <text class="log-item">{{index + 1}}. {{log}}</text> </block> </view>
<view/>表示一個視圖的節點,相當於Android中的ViewGroup,然后通過class賦予這個view一個或多個類名,wxss就是通過這個class來控制對應視圖的渲染效果,這里是兩個類名:container和log-list,目的就是在一些屬性上覆蓋.container的設置。
<block/>表示這是一個多節點的視圖,也就是列表組件,通過wx:for表示這個列表組件的數組來源,在微信的設計中,{{}}表示數據綁定,這里綁定了logs這個數組作為數據來源。
wx:for-item指定了當前數組的元素名,我們可以理解為java中的增強for的用法:for(item : array)。
wx:key表示一個唯一標識,因為這個數組是動態數組,會不斷增加自己本身的長度,而我們不希望已經創建好的元素在重新渲染的時候會被修改,因此通過wx:key指定*this,表示for循環中的item只是被重新排序,而不是被重新創建,這樣是為了提高渲染的效率。
最后我們看一下<block/>里面的子視圖,是一個text視圖,渲染的內容是數組中的元素,默認數組的下標變量名是index,元素名稱是item,因此{{index + 1}}表示取當前元素的下標,因為下標是從0開始,所以這里加1來和人類世界中下標從1開始的共識達成一致,而log就是logs中的元素的內容,因為我們已經通過wx:for-item指定了item的名稱為log。
logs目錄下的logs.js也是相當有意思的:
//logs.js var util = require('../../utils/util.js') Page({ data: { logs: [] }, onLoad: function () { this.setData({ logs: (wx.getStorageSync('logs') || []).map(function (log) { return util.formatTime(new Date(log)) }) }) } })
util.js是我們utils目錄的文件,這里的"../../utils/util.js"是通過相對路徑來導入這個js文件,按照我們的理解,相當於import一個庫。
Page函數是用來注冊一個頁面,data聲明頁面的初始數據,這里是一個logs數組,而data里面的數據是通過json傳遞到頁面,因此這里面的格式要確保完全符合json格式。然后調用onLoad函數,在加載頁面的時候通過setData將邏輯層的數據傳遞到視圖層,也就是所謂的數據綁定,並且改變this.data的內容。
setData中的函數通過調用數組的map函數,將數組的內容重新映射成新的內容,這里相當於初始化數組,map就是用來對數組內容進行賦值的。
我們看一下util.js的內容:
function formatTime(date) { var year = date.getFullYear() var month = date.getMonth() + 1 var day = date.getDate() var hour = date.getHours() var minute = date.getMinutes() var second = date.getSeconds() return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') } function formatNumber(n) { n = n.toString() return n[1] ? n : '0' + n } module.exports = { formatTime: formatTime }
這里的內容很簡單,就是對log的日期進行格式化,不過我們注意到最后的內容,module.exports執行了賦值操作。
這個其實是和require搭配的,require返回的就是這個module.exports,然后定義了一個formatTime對象,也就是可以調用的對象,而這個對象就是formatTime函數。
通過require和module.exports來確定了這個js文件暴露出來的API。
我們現在來關注一個很重要的語法:=和:這兩個操作符到底是怎么用的。
=就是賦值操作符,這個毋庸置疑,而:其實也是賦值操作,對於a:function,其實表示key值為a的value的內容為function,所以formatTime:formatTime就是表示util.formatTime這個屬性對應的是formatTime函數。
我們再來看一下index目錄下的文件。
先看一下index.js:
//index.js //獲取應用實例 var app = getApp() Page({ data: { motto: 'Hell World', userInfo: {} }, //事件處理函數 bindViewTap: function() { wx.navigateTo({ url: '../logs/logs' }) }, onLoad: function () { console.log('onLoad') var that = this //調用應用實例的方法獲取全局數據 app.getUserInfo(function(userInfo){ //更新數據 that.setData({ userInfo:userInfo }) }) } })
我們通過getApp函數來獲取小程序實例,因為我們需要調用app.js中的函數,這里調用的是getUserInfo,可以理解為app.js中定義的方法都是公共方法,因為這里並沒有require和module.exports的調用。
這里有一個新的知識點:bindViewTap。
這個是一個事件處理函數,事件是邏輯層到視圖層的通訊方式,將用戶的行為反饋到邏輯層進行處理,bindViewTap這個事件函數是用戶在點擊時候觸發的,相當於onClick,wx.navigateTo表示跳轉到url指定的頁面。
我們看一下inde.wxml文件就知道了:
<!--index.wxml--> <view class="container"> <view bindtap="bindViewTap" class="userinfo"> <image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image> <text class="userinfo-nickname">{{userInfo.nickName}}</text> </view> <view class="usermotto"> <text class="user-motto">{{motto}}</text> </view> </view>
要通過bindtap指定點擊事件函數。
事件分為兩種:冒泡事件和非冒泡事件,冒泡事件會把事件往上傳遞,而非冒泡則反之。冒泡事件前綴是bind,而非冒泡事件是catch。
通過對這個DEMO,我們大概了解到小程序的目錄結構,和一些相關的基礎知識,后面會在具體的開發工作中繼續補充相關的知識。