我们在开始微信小程序开发的时候,对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,我们大概了解到小程序的目录结构,和一些相关的基础知识,后面会在具体的开发工作中继续补充相关的知识。