面试题
1 项目结构的文件类型:
WXML
(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。内部主要是微信自己定义的一套组件WXSS
(WeiXin Style Sheets)是一套样式语言,用于描述WXML
的组件样式js
逻辑处理,网络请求json
小程序设置,如页面注册,页面标题及tabBar
2 微信小程序原理:
微信小程序采用 JavaScript
、WXML
、WXSS
三种技术进行开发,本质就是一个单页面应用,所有的页面渲染和事件处理,都在一个页面内进行,但又可以通过微信客户端调用原生的各种接口
微信的架构,是数据驱动的架构模式,它的 UI
和数据是分离的,所有的页面更新,都需要通过对数据的更改来实现;
小程序分为两个部分 webview
和 appService
。其中 webview
主要用来展现 UI
,appService
有来处理业务逻辑、数据及接口调用。它们在两个进程中运行,通过系统层 JSBridge
实现通信,实现 UI
的渲染、事件的处理;
小程序逻辑和UI执行在2个独立的Webview里面,这个是跟当前流行的react,agular,vue本质的差别;
小程序开发框架的目标是通过尽可能简单高效的方式让开发者可以在微信中开发具有原生app体验的服务,JSBridge下架起上层开发与Native(系统层)的桥梁,使得小程序可通过API使用原生的功能,且部分组件为原生组件实现,从而有良好体验:
AppService
可以理解AppService即一个简单的页面,主要功能是负责逻辑处理部分的执行,底层提供一个WAService.js的文件来提供各种api接口,主要是以下几个部分:
1、日志组件Reporter封装 ;
2、wx对象下面的api方法 ;
3、全局的App,Page,getApp,getCurrentPages等全局方法 ;
4、还有就是对AMD模块规范的实现;
3 小程序的双向绑定和vue区别:
小程序直接 this.data
修改属性是不能更改视图,必须调用:
this.setData({ // 这里设置 })
4 wxss和css有哪些不一样的地方:
WXSS 和 CSS 类似,不过在 CSS 的基础上做了一些补充和修改
尺寸单位 rpx:rpx 是响应式像素,可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx,如在 iPhone6 上,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素
使用 @import 标识符来导入外联样式。@import 后跟需要导入的外联样式表的相对路径,用;表示语句结束:
/** index.wxss **/ @import './base.wxss'; .container{ color: red; }
5 小程序传递数据的方法:
使用全局变量实现数据传递,在 app.js 文件中定义全局变量 globalData, 将需要存储的信息存放在里面:
// app.js App({ // 全局变量 globalData: { userInfo: null } }) // 直接使用 getApp() 拿到存储的信息
使用 wx.navigateTo 与 wx.redirectTo 的时候,可以将部分数据放在 url 里面,并在新页面 onLoad 的时候初始化
//pageA.js // Navigate wx.navigateTo({ url: '../pageD/pageD?name=raymond&gender=male', }) // Redirect wx.redirectTo({ url: '../pageD/pageD?name=raymond&gender=male', }) // pageB.js ... Page({ onLoad: function(option){ console.log(option.name + 'is' + option.gender) this.setData({ option: option }) } }) // 注意: // wx.navigateTo 和 wx.redirectTo 不允许跳转到 tab 所包含的页面 // onLoad 只执行一次
使用本地缓存 Storage 相关
6 小程序的生命周期
onLoad() 页面加载时触发。一个页面只会调用一次,可以在 onLoad 的参数中获取打开当前页面路径中的参数;
onShow() 页面显示/切入前台时触发;
onReady() 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互;
onHide() 页面隐藏/切入后台时触发。 如 navigateTo 或底部 tab 切换到其他页面,小程序切入后台等;
onUnload() 页面卸载时触发。如 redirectTo 或 navigateBack 到其他页面时;
微信小程序生命周期分“应用生命周期”和“页面生命周期”。
应用生命周期含onLaunch, onShow, onHide状态,onLaunch, onShow可获取打开小程序时的相关参数path, query, scene, shareTicket, referrerInfo,注册为App({}),一个小程序只有一个App({})。
页面生命周期含onLoad, onShow, onHide, onReady, onUnload,onLoad可获取其他页面打开当前页面时所所调用的query参数,注册为Page({}),每个页面有且必须有一个Page({})。
7 封装小程序请求
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) })
登录
小程序并没有登录界面,使用的是 wx.login 。 wx.login 会获取到一个 code,拿着该 code 去请求我们的后台会最后返回一个token到小程序这边,保存这个值为 token 每次请求的时候带上这个值。
一般还需要把用户的信息带上比如用户微信昵称,微信头像等,这时候就需要使用 wx.getUserInfo ,这里涉及到一个用户授权的问题
我们的项目不可能只有小程序,相应的微信公众平台可能还有相应的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; // 如果有token if (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 } })
授权
getUserInfo: function () { // 先调用wx.getSetting 获取用户权限设置 wx.getSetting({ success(res) { console.log('1'); if (!res.authSetting['scope.userInfo']) { wx.authorize({ scope: 'scope.userInfo', success() { // 用户已经同意小程序使用录音功能,后续调用 wx.getUserInfo接口不会弹窗询问 wx.getUserInfo({ success: (data) => { this.globalData.userInfo = data.userInfo; wx.setStorage({ key: 'userInfo', data: data.userInfo }) return this.globalData.userInfo; } }) } }) } else { console.log(2); } } }) },
8 小程序优劣势
优点:
即用即走,不用安装,省流量,省安装时间,不占用桌面;
依托微信流量,天生推广传播优势;
开发成本比 App 低;
缺点:
入口相对传统 App 要深很多;
限制较多,页面大小不能超过2M。不能打开超过10个层级的页面;
9 微信下小程序公众号等如何确定用户的唯一性
如果开发者拥有多个移动应用、网站应用、和公众帐号(包括小程序),可通过 unionid 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号(包括小程序),用户的 unionid 是唯一的。换句话说,同一用户,对同一个微信开放平台下的不同应用,unionid 是相同的
10 如何实现下拉刷新
首先在全局 config 中的 window 配置 enablePullDownRefresh;
在 Page 中定义 onPullDownRefresh 钩子函数,到达下拉刷新条件后,该钩子函数执行,发起请求方法;
请求返回后,调用 wx.stopPullDownRefresh 停止下拉刷新;
11 bindtap和catchtap的区别
相同点:首先他们都是作为点击事件函数,就是点击时触发;
不同点:他们的不同点主要是bindtap是不会阻止冒泡事件的,catchtap是阻值冒泡的;
12 wx.navigateTo(), wx.redirectTo(), wx.switchTab(), wx.navigateBack(), wx.reLaunch()的区别
wx.navigateTo():保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面;
wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面;
wx.switchTab():跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面;
wx.navigateBack():关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层;
wx.reLaunch():关闭所有页面,打开到应用内的某个页面;
13 小程序的双线程模式
14 为什么小程序最多允许打开5个层级的页面?
1 上述的渲染层上面运行这wxml文件已经wxss文件,渲染层使用是的webview线程进行渲染(一个程序会有多个页面,也就会有多个view线程进行运作)
2 js文件是运行在逻辑层,逻辑层的js是通过jscore进行运行的。
开发
1 用微信开发者工具初始化项目;
2 初始化后文件夹里包含以下文件:
小程序会读取app.js、app.json、app.wxss这三个文件初始化实例。
app.js是小程序的初始化脚本,可以在这个文件中监听小程序的生命周期,定义全局变量和调用API等。使用App()来注册一个小程序,必须在app.js
中注册,且不能注册多个
App({//如下为小程序的生命周期 onLaunch: function() { },//监听初始化 onShow: function() { },//监听显示(进入前台) onHide: function() { },//监听隐藏(进入后台:按home离开微信) onError: function(msg) { },//监听错误 //如下为自定义的全局方法和全局变量 globalFun:function(){}, globalData: 'I am global data' })
app.json是对小程序的全局配置。主要包括pages:页面组,window:框架样式(状态栏、导航条、标题、窗口背景色),tabBar:底部菜单,networkTimeout:网络超时设置,debug:开启debug模式;
//app.json { "pages":[ "pages/index/index", "pages/logs/logs" ], "window":{ "backgroundTextStyle":"light", "navigationBarBackgroundColor": "#000", "navigationBarTitleText": "WeChat", "navigationBarTextStyle":"white" } }
app.wxss 默认为全局样式,作用所有页面。
创建页面:在pages目录下的文件夹是由四个同名不同类型文件组成。.js
是脚本文件,.json
是配置文件,.wxss
是样式表文件,.wxml
是页面结构文件,其中json和wxss文件为非必须(默认会继承app的json和wxss默认设置)。
在pages目录下的文件夹中的.js
脚本文件使用Page()注册一个页面
Page({ data: {text: "This is page data."},//页面数据,用来维护视图,json格式 onLoad: function(options) { },//监听加载 onReady: function() { },//监听初次渲染完成 onShow: function() { },//监听显示 onHide: function() { },//监听隐藏 onUnload: function() { },//监听卸载 onPullDownRefresh: function() { },//监听下拉 onReachBottom: function() { },//监听上拉触底 onShareAppMessage: function () { },//监听右上角分享 //如下为自定义的事件处理函数(视图中绑定的) viewTap: function() {//setData设置data值,同时将更新视图 this.setData({text: 'Set some data for updating view.'}) } })
视图与事件绑定
在每个页面中的wxml文件中,对页面相应的js中data进行数据绑定,以及自定义事件的绑定。
<!--{{}}绑定data中的指定数据并渲染到视图--> <view class="title">{{text}}</view> <!--wx:for获取数组数据进行循环渲染,item为数组的每项--> <view wx:for="{{array}}"> {{item}} </view> <!--wx:if条件渲染--> <view wx:if="{{view == 'WEBVIEW'}}"> WEBVIEW </view> <view wx:elif="{{view == 'APP'}}"> APP </view> <view wx:else="{{view == 'MINA'}}"> MINA </view> <!--模板--> <template name="staffName"> <view>FirstName: {{firstName}}, LastName: {{lastName}}</view> </template> <template is="staffName" data="{{...template.staffA}}"></template> <template is="staffName" data="{{...template.staffB}}"></template> <!--bindtap指定tap事件处理函数为ViewTap--> <view bindtap="ViewTap"> 点我点我 </view>
Page({ data: {//data数据主要用于视图绑定 text:"我是一条测试", array:[0,1,2,3,4], view:"APP", template:{ staffA: {firstName: 'Hulk', lastName: 'Hu'}, staffB: {firstName: 'Shang', lastName: 'You'} } }, ViewTap:function(){console.log('额,点到我了了~')}//自定义事件,主要用于事件绑定 })
样式
在每个页面中的wxss文件中,对wxml中的结构进行样式设置,等同于css,扩展了rpx单位。
rpx
小程序编译后,rpx会做一次px换算(小程序开发者工具在初始渲染一个页面时会首先获取设备宽度deviceWidth和dpr)
rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
由此可以看出,小程序在实现rpx转换时,不论是什么屏幕的手机,都是将屏幕宽度固定设为750rpx,然后根据实际屏幕的设备像素比dpr
(dpr = 设备像素 / css像素)来进行转换的。具体对应关系如下:
1rpx = (number/ 750) * 设备宽度 px
原文