小程序----面试题总结
1. 簡單描述下微信小程序的相關文件類型
微信小程序項目結構主要有四個文件類型
- WXML(WeiXin Markup Language)是框架設計的一套標簽語言,結合基礎組件、事件系統,可以構建出頁面的結構。內部主要是微信自己定義的一套組件
- WXSS (WeiXin Style Sheets)是一套樣式語言,用于描述 WXML 的組件樣式
- js 邏輯處理,網絡請求
- json 小程序設置,如頁面注冊,頁面標題及tabBar
主要文件
-
app.json
必須要有這個文件,如果沒有這個文件,項目無法運行,因為微信框架把這個作為配置文件入口,整個小程序的全局配置。包括頁面注冊,網絡設置,以及小程序的
window 背景色,配置導航條樣式,配置默認標題 -
app.js 必須要有這個文件,沒有也是會報錯!但是這個文件創建一下就行
什么都不需要寫以后我們可以在這個文件中監聽并處理小程序的生命周期函數、聲明全局變量 -
app.wxss 可選
2. 簡述微信小程序原理
微信小程序采用 JavaScript、WXML、WXSS 三種技術進行開發,本質就是一個單頁面應用,所有的頁面渲染和事件處理,都在一個頁面內進行,但又可以通過微信客戶端調用原生的各種接口
微信的架構,是數據驅動的架構模式,它的 UI 和數據是分離的,所有的頁面更新,都需要通過對數據的更改來實現
小程序分為兩個部分 webview 和 appService 。其中 webview 主要用來展現 UI ,appService 有來處理業務邏輯、數據及接口調用。它們在兩個進程中運行,通過系統層 JSBridge 實現通信,實現 UI 的渲染、事件的處理
3. 小程序的雙向綁定和vue哪里不一樣
小程序直接 this.data 的屬性是不可以同步到視圖的,必須調用:
this.setData({// 這里設置 })4. 小程序的wxss和css有哪些不一樣的地方
WXSS 和 CSS 類似,不過在 CSS 的基礎上做了一些補充和修改
- 尺寸單位 rpx
rpx 是響應式像素,可以根據屏幕寬度進行自適應。規定屏幕寬為 750rpx。如在 iPhone6 上,屏幕寬度為 375px,共有 750 個物理像素,則 750rpx = 375px = 750 物理像素
- 使用 @import 標識符來導入外聯樣式。@import 后跟需要導入的外聯樣式表的相對路徑,用;表示語句結束
5. 小程序頁面間有哪些傳遞數據的方法
- 使用全局變量實現數據傳遞 在 app.js 文件中定義全局變量 globalData, 將需要存儲的信息存放在里面
使用的時候,直接使用 getApp() 拿到存儲的信息
- 使用 wx.navigateTo 與 wx.redirectTo 的時候,可以將部分數據放在 url 里面,并在新頁面 onLoad
的時候初始化
需要注意的問題:
wx.navigateTo 和 wx.redirectTo 不允許跳轉到 tab 所包含的頁面
onLoad 只執行一次
- 使用本地緩存 Storage 相關
6. 小程序的生命周期函數
-
onLoad 頁面加載時觸發。一個頁面只會調用一次,可以在 onLoad 的參數中獲取打開當前頁面路徑中的參數
-
onShow() 頁面顯示/切入前臺時觸發
-
onReady() 頁面初次渲染完成時觸發。一個頁面只會調用一次,代表頁面已經準備妥當,可以和視圖層進行交互
-
onHide() 頁面隱藏/切入后臺時觸發。 如 navigateTo 或底部 tab 切換到其他頁面,小程序切入后臺等
-
onUnload() 頁面卸載時觸發。如 redirectTo 或 navigateBack 到其他頁面時
詳見 生命周期回調函數
7. 怎么封裝微信小程序的數據請求
參考 這里
網絡請求小程序提供了wx.request, 仔細看一下 api,這不就是n年前的 $.ajax 嗎,好古老啊。
小程序支持ES6,那么就應該支持Promise 了,很開心~, 話不多說直接上代碼吧
Promise封裝
const baseUrl = 'https://api.it120.cc';const http = ({ url = '', param = {}, ...other } = {}) => {wx.showLoading({title: '請求中,請耐心等待..'});let timeStart = Date.now();return new Promise((resolve, reject) => {wx.request({url: getUrl(url),data: param,header: {'content-type': 'application/json' // 默認值 ,另一種是 "content-type": "application/x-www-form-urlencoded"},...other,complete: (res) => {wx.hideLoading();console.log(`耗時${Date.now() - timeStart}`);if (res.statusCode >= 200 && res.statusCode < 300) {resolve(res.data)} else {reject(res)}}})}) }const getUrl = (url) => {if (url.indexOf('://') == -1) {url = baseUrl + url;}return url }// get方法 const _get = (url, param = {}) => {return http({url,param}) }const _post = (url, param = {}) => {return http({url,param,method: 'post'}) }const _put = (url, param = {}) => {return http({url,param,method: 'put'}) }const _delete = (url, param = {}) => {return http({url,param,method: 'put'}) } module.exports = {baseUrl,_get,_post,_put,_delete }// 使用 const api = require('../../utils/api.js') // 單個請求 api.get('list').then(res => {console.log(res) }).catch(e => {console.log(e) })// 一個頁面多個請求 Promise.all([api.get('list'),api.get(`detail/${id}`) ]).then(result => {console.log(result) }).catch(e => {console.log(e) })登陸問題
做一個應用,肯定避免不了登錄操作。用戶的個人信息啊,相關的收藏列表等功能都需要用戶登錄之后才能操作。一般我們使用token做標識。
小程序并沒有登錄界面,使用的是 wx.login 。 wx.login 會獲取到一個 code,拿著該 code 去請求我們的后臺會最后返回一個token到小程序這邊,保存這個值為 token 每次請求的時候帶上這個值。
一般還需要把用戶的信息帶上比如用戶微信昵稱,微信頭像等,這時候就需要使用 wx.getUserInfo ,這里涉及到一個用戶授權的問題
帶上用戶信息就夠了嘛? too young too simple!我們的項目不可能只有小程序,相應的微信公眾平臺可能還有相應的App,我們需要把賬號系統打通,讓用戶在我們的項目中的賬戶是同一個。這就需要用到微信開放平臺提供的 UnionID 。
登陸
//app.js App({onLaunch: function () {console.log('App onLaunch');var that = this;// 獲取商城名稱wx.request({url: 'https://api.it120.cc/'+ that.globalData.subDomain +'/config/get-value',data: {key: 'mallName'},success: function(res) {wx.setStorageSync('mallName', res.data.data.value);}})this.login();this.getUserInfo();},login : function () {var that = this;var token = that.globalData.token;// 如果有tokenif (token) {// 檢查token是否有效wx.request({url: 'https://api.it120.cc/' + that.globalData.subDomain + '/user/check-token',data: {token: token},success: function (res) {// 如果token失效了if (res.data.code != 0) {that.globalData.token = null;that.login(); // 重新登陸}}})return;}// 【1】調用微信自帶登陸wx.login({success: function (res) {// 【2】 拿到code去訪問我們的后臺換取其他信息wx.request({url: 'https://api.it120.cc/'+ that.globalData.subDomain +'/user/wxapp/login',data: {code: res.code},success: function(res) {// 如果說這個code失效的if (res.data.code == 10000) {// 去注冊that.registerUser();return;}// 如果返回失敗了if (res.data.code != 0) {// 登錄錯誤 wx.hideLoading();// 提示無法登陸wx.showModal({title: '提示',content: '無法登錄,請重試',showCancel:false})return;}// 【3】 如果成功后設置token到本地that.globalData.token = res.data.data.token;// 保存用戶信息wx.setStorage({key: 'token',data: res.data.data.token})}})}})},// 注冊?? [這個看需求]registerUser: function () {var that = this;wx.login({success: function (res) {var code = res.code; // 微信登錄接口返回的 code 參數,下面注冊接口需要用到wx.getUserInfo({success: function (res) {var iv = res.iv;var encryptedData = res.encryptedData;// 下面開始調用注冊接口wx.request({url: 'https://api.it120.cc/' + that.globalData.subDomain +'/user/wxapp/register/complex',data: {code:code,encryptedData:encryptedData,iv:iv}, // 設置請求的 參數success: (res) =>{wx.hideLoading();that.login();}})}})}})},// 獲取用戶信息getUserInfo:function() {wx.getUserInfo({success:(data) =>{this.globalData.userInfo = data.userInfo;wx.setStorage({key: 'userInfo',data: data.userInfo})return this.globalData.userInfo;}})},globalData:{userInfo:null,subDomain:"34vu54u7vuiuvc546d",token: null} })授權問題
授權2
小程序登錄流程
這里引用下官方的一張登錄流程圖,我就按照登錄流程圖來講下我的理解。
第一步
客戶端(小程序)獲取當前微信登錄用戶的登錄憑證(code)
可通過wx.login api獲得。這里有地方需要注意
1.wx.login不會彈授權彈框
2.wx.login換取的code只能使用一次,如果需要新code只能重新調用wx.login接口
第二步
通過上一步獲得的臨時登錄憑證傳給服務器端獲取openid和session_key.服務器端需要通過appid、appsecret、(這里的數據可以從小程序管理后臺獲得)code(第一步獲取到的code)向微信服務端發送請求獲取seeeion_key和openid。為了安全。建議將獲得的session_key加密后再傳給客戶端。
第三步
客戶端獲得加密后的登錄態后把登錄態存在本地以便后面進行業務請求。由于小程序中不存在cookie機制。所以可以把登錄態存儲在storage中。
以上就是微信官方登錄流程圖的一個大體過程。
但是在實際應用中可能要復雜點?我們接下來看。
登錄態在實際應用中的維護
這里看一下微信官方的說明
通過 wx.login 接口獲得的用戶登錄態擁有一定的時效性。
用戶越久未使用小程序,用戶登錄態越有可能失效。
反之如果用戶一直在使用小程序,則用戶登錄態一直保持有效。
具體時效邏輯由微信維護,對開發者透明。
開發者只需要調用 wx.checkSession 接口檢測當前用戶登錄態是否有效。
這說明如果用戶一直在使用小程序。登錄態就不會過期。反之就會過期。這里可以通過wx.checkSession api 來判斷登錄態是否過期。
接下來上代碼。來看下在應用中的登錄態維護。
目前在小程序中需要拉起微信登錄授權的彈框。需要在wxml文件中調用button組件來調用:如下
這樣用戶點擊按鈕的時候會彈出授權獲取用戶信息的彈窗。用戶點擊允許我們就可以拿到數據進行登錄并進行業務請求。 如果點擊拒絕可以獲取不需要登錄可查看的數據請求,并安利用戶拒絕后的結果。重新引導用戶進行授權。
下面是用戶非首次進入應用的一個登錄態維護(首次進入通過button來授權。所以success回調是不會執行的。直接fail的回調。)
// 小程序啟動判斷用戶是否授權,根據是否授權來請求不同的業務數據 wx.getSetting({success: (res) => {//用戶已授權if (res.authSetting['scope.userInfo']) {// 判斷登錄態是否過期wx.checkSession({// 登錄態未過期,直接進行業務請求success: (res) => {//業務請求代碼。。。},// 登錄態已過期 。重新調用wx.login進行登錄換取codefail: (res) => {// 可以在這里進行重新登錄后的回調wx.login({success: function(res) {let code = res.code;}})}})}// 為授權 else {// 執行未授權的業務代碼}} })附上登錄態過期的回調。
/*** 登錄失敗后重新登錄*/getToken: function(fn) {let that = this;let getLogin = new Promise((resolve, reject) => {//登錄獲取codewx.login({success: function(res) {var code = res.code;that.globalData.code = code;resolve([fn, code]);},fail: function(res) {reject();}})});getLogin.then(([fn, code]) => {return new Promise((resolve, reject) => {//使用該api需要在頁面通過button組件觸發授權彈窗wx.getUserInfo({success: function(res) {//這里的iv,encryptedData等數據是用來服務器端進行解密的。let requestData = {"Data": {"IV": res.iv,"EncryptedData": res.encryptedData,"JsCode": code,},}//發送請求wx.request({url: that.apiList.login.getLogin,data: requestData,method: "POST",success: function(res) {//獲取到自定義登錄態存入storageif (res.data && res.data.Success) {that.globalData.token = res.data.Data.Key;wx.setStorageSync('LoginSessionKey', res.data.Data.Key);resolve(fn);} else {reject();}},fail: function() {Hq.tipMaskNoneIcon('您的網絡開小差了');}})}})});}).then((fn) => {that.getCountryInfo(fn);}, function() {}))},//執行fn回調函數getCountryInfo: function(fn) {if (typeof fn == 'function') {//登錄成功后進行業務請求。fn();} else {Hq.afterSend();}},以上就是我的一些理解。有語句不通,邏輯不清晰的地方,請不吝留言賜教!
8. 哪些方法可以用來提高微信小程序的應用速度
1、提高頁面加載速度
2、用戶行為預測
3、減少默認 data 的大小
4、組件化方案
9. 微信小程序的優劣勢
優勢
- 即用即走,不用安裝,省流量,省安裝時間,不占用桌面
- 依托微信流量,天生推廣傳播優勢
- 開發成本比 App 低
缺點
- 用戶留存,即用即走是優勢,也存在一些問題
- 入口相對傳統 App 要深很多
- 限制較多,頁面大小不能超過2M。不能打開超過10個層級的頁面
10. 怎么解決小程序的異步請求問題
小程序支持大部分 ES6 語法
- 在返回成功的回調里面處理邏輯
- Promise 異步
11. 小程序關聯微信公眾號如何確定用戶的唯一性
如果開發者擁有多個移動應用、網站應用、和公眾帳號(包括小程序),可通過 unionid 來區分用戶的唯一性,因為只要是同一個微信開放平臺帳號下的移動應用、網站應用和公眾帳號(包括小程序),用戶的 unionid 是唯一的。換句話說,同一用戶,對同一個微信開放平臺下的不同應用,unionid 是相同的
12. 如何實現下拉刷新
- 首先在全局 config 中的 window 配置 enablePullDownRefresh
- 在 Page 中定義 onPullDownRefresh 鉤子函數,到達下拉刷新條件后,該鉤子函數執行,發起請求方法
- 請求返回后,調用 wx.stopPullDownRefresh 停止下拉刷新
參考 這里:(https://juejin.im/post/5a781c756fb9a063606eb742)
下拉刷新和上拉加載是業務上一個很常見的需求,在微信小程序里,提供了下拉刷新的方法 onPullDownRefresh 。而實現上拉加載相對來說就比較不方便了。
下拉刷新
雖然微信的官方文檔有很多坑,但下拉刷新介紹的還是很全面的。在這里稍稍帶過。
- 首先在全局 config 中的 window 配置 enablePullDownRefresh .
- 在 Page 中定義 onPullDownRefresh 鉤子函數。到達下拉刷新條件后,該鉤子函數執行,發起請求方法。
- 請求返回后,調用 wx.stopPullDownRefresh 停止下拉刷新。
config
config = {pages: ['pages/index'],window: {backgroundTextStyle: 'light',navigationBarBackgroundColor: '#ccc',navigationBarTitleText: 'WeChat',navigationBarTextStyle: '#000',enablePullDownRefresh: true}}page
onPullDownRefresh() {wepy.showNavigationBarLoading() setTimeout(()=>{this.getData = '數據拿到了'wepy.stopPullDownRefresh()wepy.hideNavigationBarLoading()this.$apply()},3000) }效果如下:
你會發現下拉的過程有些僵硬。這實際上是沒有添加背景色的原因,加上背景色后再試試。
現在感覺好多了吧。下拉刷新有現成的配置和方法,很容易實現,可上拉加載就不同了。
上拉加載
首先看一下要實現的效果,這是3g端的上拉加載。小程序要實現同樣的效果。
首先功能有
- 點擊回到頂部 這個很好實現,有對應的回到頂部函數
- 滑動屏幕記錄當前頁數 這個也很好實現,主要是監聽滾動事件,判斷對應滾動條高度,去計算其與子容器的高度即可。
- 上拉加載動畫
這里有兩個實現的方案。一個是 page 自帶的下拉觸底鉤子事件 onReachBottom 能做的只是下拉到底部的時候通知你觸底了,一個是 scroll-view 標簽自帶事件。現在用兩個方法分別實現一下上拉加載。
上拉觸底事件 onReachBottom
模板
<template><view class="loading"></view><view class="container" @touchmove="moveFn" @touchstart="startFn" @touchend="endFn"style="transform:translate3d(0,{{childTop}}px,0)"><repeat for="{{list}}" key="index" index="index" item="item"><view>{{ item }}<text>{{index}}</text></view></repeat></view> </template>鉤子函數
data = {getData: '',top: 0,lastTop: 0,canDrag: false,list: [] } onReachBottom() {this.canDrag = true } methods = {moveFn(ev) {let nowY = ev.changedTouches[0].clientYnowY = nowY-this.lastTopif(nowY > 0 )this.canDrag = falseif( nowY<=0 && this.canDrag ) {this.top = nowY}if( -this.top>= this.maxTop )this.top = -this.maxTop},startFn(ev) {this.lastTop = ev.changedTouches[0].clientY },endFn() {if(this.top <= -this.maxTop) {this.text = "去請求數據了"setTimeout(()=>{this.text = "請求回來了"this.canDrag = falsethis.list.push(...["數據","數據","數據"])this.$apply()this.top = 0;return},1000)}},gotoTop() {wepy.pageScrollTo({scrollTop: 0})} }滾動容器實現上拉加載
scroll-view: 可滾動視圖區域。
它的具體用法不贅述,看官方文檔就行了。這里提解決上述問題的方法即可。
- bindscrolltolower 類比原生全局鉤子 onReachBottom
模板
<scroll-view scroll-y id="content" @scroll="scroll" @scrolltolower="lower" scroll-top="{{gotoTopNum}}" lower-threshold="100" style="transform:translate3d(0,{{childTop}}px,0)"><view class="sty-search" @touchmove="moveContent" @touchstart="startContent" @touchend="endContent">...</view> </scroll-view>以上就是最終的模板,你可能在想為什么這么復雜。雖然復雜,但每個屬性都是有用的,當然這其中有幾個坑在等著我們。
首先節點分為滾動容器和子容器。
Q:為什么滾動容器里嵌套一個子容器,并且將拖動的三個方法綁定在它上面。
A:這是第一個坑,因為 scroll-view 容器不能綁定 touchmove 事件,那如果綁定了會怎么樣呢?不會怎么樣,事件鉤子不會調用。(這個坑在官方文檔查不出來,當時綁定了不調用,在社區找到了解決方法,就是將touchmove事件綁定到子容器)
再來看代碼
methods = {async lower() {this.canDrag = true},scroll (ev) {this.scrollTop = ev.detail.scrollTopif (ev.detail.deltaY > 0) {this.canDrag = false}let nowSet = this.documentHeight+this.scrollTop-this.contentHeaderlet num = Math.ceil(nowSet/this.listHeight) - 1num = Math.floor(num / this.pageBean.pageSize) + 1num = (num > this.pageBean.pageNo) ? this.pageBean.pageNo : num if(num != this.page) {this.page = numthis.$apply()}},startContent(ev) {this.lastTop = ev.changedTouches[0].clientYif(!this.documentHeight){this.documentHeight = wx.getSystemInfoSync().windowHeight}/* 這句是解決回到頂部的bug */if (this.gotoTopNum || this.gotoTopNum==0) { this.gotoTopNum = undefined }},moveContent (ev) {let {pageNo,pageSize,totalCount} = this.pageBeanlet nowY = ev.changedTouches[0].clientYnowY = nowY-this.lastTopif (this.canDrag && nowY) {this.state = 1;if (nowY <= -this.maxMove) {nowY = -this.maxMove}if (nowY <= 0) {this.childTop = nowY} }},async endContent(ev) {let {pageNo,pageSize,totalCount} = this.pageBeanif (this.childTop === -this.maxMove) {/* 狀態 */if (pageNo >= this.maxPage || pageNo * pageSize >= totalCount) {this.state = 0} else {this.pageBean.pageNo++ await this.fillData()this.childTop = 0this.canDrag = falsethis.$apply()}}/* 如果沒超過刷新高度則重置 */this.childTop = 0},gotoTop() {this.gotoTopNum = 0}, }Q: 為什么要在 touchStart 的時候 將 gotoTopNum 置為 undefined?
A: 因為這個頁面有一個回到頂部的功能,當回到頂部時,gotoTopNum 置為0,再次下翻時,雖然實際的 scrollTop 改變了,但是 gotoTopNum 還為0,再次點擊回到頂部時,因為數據未改變,視圖層就不會去更新。所以在 touchStart 的時候給 gotoTopNum 一個無效的值,再次點擊回到頂部時,視圖層也就更新了。
原生滾動 OR scroll-view
END…了嗎…
并沒有。
真機測試
實現的上拉加載在模擬器上跑的很流暢,不存在問題。可是。
如果是蘋果機的話(暫時測試iphone5 和 iPhone7),存在這樣一個問題,上拉或下拉回彈效果,這個效果會影響上拉的距離。
這個問題想了很久,目前不能優雅的解決。
所以就找產品經理修改了需求,去掉了上拉動畫效果 所以最終的效果就變成:
總結
1.在微信小程序里操作節點是昂貴的,比在瀏覽器里操作還昂貴(這是通過比較上拉加載功能在3g端和微信小程序的流暢度得來的),在 1.4.0 版本發布之后,雖然給出了很多操作節點的方法,比如得到一個節點的寬高、或者通過 id 選擇器得到一個節點。請盡量減少這些方法的調用頻率( 函數節流 )或 緩存結果
2.動手之前先動腦!!!不然會走很多彎路…
13. bindtap和catchtap的區別是什么
相同點:首先他們都是作為點擊事件函數,就是點擊時觸發。在這個作用上他們是一樣的,可以不做區分
不同點:他們的不同點主要是bindtap是不會阻止冒泡事件的,catchtap是阻值冒泡的
14. 簡述下 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的區別
- wx.navigateTo():保留當前頁面,跳轉到應用內的某個頁面。但是不能跳到 tabbar 頁面
- wx.redirectTo():關閉當前頁面,跳轉到應用內的某個頁面。但是不允許跳轉到 tabbar 頁面
- wx.switchTab():跳轉到 abBar 頁面,并關閉其他所有非 tabBar 頁面
- wx.navigateBack()關閉當前頁面,返回上一頁面或多級頁面。可通過 getCurrentPages()
獲取當前的頁面棧,決定需要返回幾層 - wx.reLaunch():關閉所有頁面,打開到應用內的某個頁面
總結
以上是生活随笔為你收集整理的小程序----面试题总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JS----面试题总结(持续更新中...
- 下一篇: 邓超的电视剧(邓超主演的最好看电视剧)