【目录】


  • 小程序开发框架
  • 1 基本构成
  • (1)WXML-页面
  • (2)WXSS-样式
  • (3)WXS-对XML增强的脚本语言
  • (4)JavaScript-交互逻辑
  • 2 详细介绍
  • (1)WXML
  • (2)WXSS
  • (3)JavaScript
  • (4)WXS
  • (5)底层架构和原理
  • MINA框架
  • (6)小程序运行机制


小程序开发框架

1 基本构成

(1)WXML-页面

(2)WXSS-样式

(3)WXS-对XML增强的脚本语言

可以对请求到的数据进行过滤和计算等处理,帮助WXML快速构建出页面的内容结构。

(4)JavaScript-交互逻辑

2 详细介绍

(1)WXML

WXML(WeiXin Markup Language),这门语言以标签的形式结合组件、WXS和事件系统,构建页面结构。
语法:

<标签名 属性名="属性值"></标签名>//标签闭合 大小写敏感

语言特性:
数据绑定:
(一些网页的数据需要动态地频繁更新)
WXML中的数据都来自于.js文件中配置的data对象,一种数据绑定方法用的是Mustache的语法,即变量名两边用两个大括号包起来:

<view>
    <text>{{message}}</text>
</view>

既可以绑定文本内容,也可以绑定属性:

//.wxml中:
<view>
    <text data-name="{{theName}}"></text>
</view>
//.js中:
<view>
    <text data-name="Jack"></text>
</view>

注意:所有组件的字母都是小写

运算符绑定:

//.wxml中
<view hidden="{{flag ? true : false}}">
    Hidden
</view>//hidden为false时,显示“Hidden”字样
//.js中
Page({
    data:{
        flag:false
    }
})

hidden属性帮助显示或者隐藏掉标签的内容
类似数据绑定还有字符串运算绑定、组合式绑定和扩展运算符绑定等

标签的共同属性:
id:组件的唯一标识
class:组件的样式类
style:组件的内联样式,可以动态设置样式,而class中的只能设置静态属性
hidden:boolean类型
data:任意类型,自定义属性,组件上触发事件时,data中的数据会发送给事件处理函数(逻辑层)
bind/catch:EventHandler类型,组件的事件,绑定组件上的默认事件

列表渲染:
用于数据很多的情况,使用列表渲染,减少代码量,使代码更有条理:

//.wxml中
<view>
    <block wx:for="{{items}}" wx:for-item="item" wx:key="index">
        <view>{{index}}:{{item.name}}</view><!--wx:for-item属性可以给它指定一个变量名 wx:key属性可以确保带有该属性的item在渲染时重新排序,而不是重新渲染,从而提高渲染效率。如果在列表发生改变,需要重新渲染时,列表就可以保持自身的一个状态;这里不推荐使用index作为标识item唯一的值,最好用id等字段标识;block不是一个组件,只是一个包装元素,在渲染时,包装元素不会被渲染出来-->
    </block>
</view>
//.js中
Page({
    data:{
        items:[
            {name:"商品A"},
            {name:"商品B"},
            {name:"商品C"},
            {name:"商品D"}
        ]
    }
})

条件渲染:

//.wxml中:
<view>今天吃什么?</view>
<view wx:if="{{condition===1}}">
  饺子
</view>
<view wx:elif="{{condition===2}}">
  rice
</view>
<view wx:else>
  noodles
</view>
//.js中:
Page({
  data: {
    condition:Math.floor(Math.random()*3+1)//获取1-3的值
  }
})

hidden和if显示时的异同点:
wx:if条件渲染,在切换时框架有一个局部渲染渲染过程,从而保证在渲染时可以销毁并重新渲染;而hidden也会渲染/(ㄒoㄒ)/~~,不同的是if有更高的切换消耗,hidden有更高的初始渲染消耗。

模板引用:
可以设置自己的代码片段,在多处进行引用:

//.wxml中
<template name="tempItem">
  <view>
    <view>收件人:{{name}}</view>
    <view>联系方式:{{phone}}</view>
    <view>地址:{{address}}</view>
  </view>
</template>
<template is="tempItem" data="{{...item}}">
</template>
//is属性声明所使用的是哪个模板,data属性是数据信息,模板有自己的作用域,信息只能通过data属性传入
//.js中
Page({
  data: {
    //模板引用所使用的数据
    item:{
      name:"张三",
      phone:"18888888888",
      address:"China"
    }
  }
})

模板引用-文件引用-import:

//.wxml中:
<import src="mubanfile.wxml"></import>
<template is="a" data="{{...item}}">
</template>
//被引用的mubanfile.wxml中
<view>Hello World</view>
<template name="a">
  <view>Hello World!</view>
</template>

import引用有一个作用域的问题:只能引用目标文件中定义的template模板,如果目标文件中引用了其它template模板,是不会被引用的,避免了死循环。
模板引用-文件引用-include:

//.wxml中
<!-- 模板引用-include引用 -->
<include src="mubanfile.wxml"></include>

会把目标文件中除了模板之外的所有内容都引入,相当于是拷贝到include的位置

(2)WXSS

WXSS(WeiXin Style Sheets),决定了视觉效果,它与web开发中的CSS(Cascading Style Sheets)极为相似,在它的基础上增加和修改了一些内容,更方便小程序的开发。

所做的修改和补充:
①尺寸单位rpx–响应像素

几个概念:
设备像素(device pixels):设备能控制显示的最小物理单位 CSS像素(CSS pixels):外部编程中所使用的逻辑像素
PPI/DPI(pixel per inch):每英尺所拥有的像素数目,赋值越高表示显示屏能够以越高的密度显示图像:

PPI=x2+y2√屏幕尺寸 P P I = x 2 + y 2 屏 幕 尺 寸 −−−−> − − − − > 7502+133424.7=325.6 750 2 + 1334 2 4.7 = 325.6 (iphone6的像素和尺寸)

DPR(device pixel ratio):在某一方向上设备像素和CSS像素之比,在开发工具中的模拟器中有用到,WXSS刚开始是不能适配各种设备的,虽然WXSS支持rem,但rem是根据根元素的font-size的大小来适配的,但WXSS并不能直接去操作html的样式属性,所以rem失效。

基于以上,推出了rpx,规定宽度为750个rpx,从而可以根据屏幕宽度来进行自适应,其实现原理与rem相似,且其最终也是转换为rem。

②样式导入–外联样式导入
通过@import标识引入,可以把相同样式抽离成外联样式文件,以减少资源文件占的空间
例:

//.wxml中:
<!-- 外联样式 ,需要在wxss文件中用@import导入外联样式文件-->
<view class='container'>
外联样式
</view>
//.wxss中:
@import './assets.wxss';
.container{
  color: red;
}
//assets.wxss中:
/**外联样式文件**/
.container{
  border: 1px solid #000;
}

③内联样式
style例子:

//.wxml中:
<!-- 内联样式 -->
<view style='width:500rpx;height:30px;background-color:{{colorValue}};'>
内联样式
</view>
//.js中:
Page({
  data: {
    // 内联样式数据
    colorValue:'red'
  }
})

④选择器
目前支持的选择器有:

选择器

样例

样例描述

.class

.intro

选择所有拥有class=”intro”的组件

#id

#firstname

选择拥有id=”firstname”的组件

element

view

选择所有view组件

element,element

view,checkbox

选择所有文档的view组件和所有的checkbox组件

::after

view::after

在view组件后边插入内容

::before

view::before

在view组件前边插入内容

优先级:

选择器

优先级

!important(会破坏样式固有的级联规则)


style

1000

#element

100

.element

10

element

1

从上到下优先级从高到低

(3)JavaScript

JavaScript 是一种轻量的、解释型的、面向对象的头等函数语言,是一种动态的基于原型和多范式的脚本语言,支持面向对象、命令式和函数式的编程风格。
“Java和JavaScript的关系就像雷锋和雷峰塔的关系。”
《JavaScript高级程序设计》《JavaScript权威指南》

Nodejs中的JavaScript:
由ECMAScript、Native(原生模块,通过它使用一些JavaScript不具有的能力)和NPM(是一个包管理系统,是目前世界上最大的开源库生态系统,可以快速地通过一些NPM扩展包实现一些功能)组成

Nodejs是基于谷歌的V8引擎实现的JavaScript运行时,它使用了高效、轻量级的事件驱动,以及非阻塞的IO模型,通常将Nodejs作为一门后端的语言来使用。

小程序中的JavaScript:
由ECMAScript、小程序框架、小程序API组成,和浏览器中的JavaScript相比是没有BOM和DOM对象的,所以类似于JQuery等类库没有办法使用;同样,它也缺少Nodejs中的Native和NPM模块。

ECMAScript是一门美国262标准国际化的脚本程序设计语言,关键部分:语法、类型、语句、关键字、操作符、对象,JavaScript是它的一种实现。

浏览器中的JavaScript:
由ECMAScript、DOM、BOM组成。DOM是Html和Xml的应用程序接口,DOM表示加载到当前窗口的一个网页,可通过JS读取DOM对象,BOM主要是处理浏览器窗口和框架,描述了与浏览器交互的方法和接口,如修改页面url、打开新窗口。

在小程序中,在不同平台的JavaScript运行环境也不同:

平台

环境

IOS

jSCore,由WKWebView来渲染

Android

X5JSCore来解析,由X5内核渲染

IDE

nwjs(提供桌面应用的运行环境),由chromewebview来渲染

大部分环境运行的是ES5和ES6,IOS8和IOS9不能完全兼容ES6,可以使用远程调试功能进行真机调试。

(4)WXS

WXS(WeiXinScript),小程序自己的一套脚本语言,可以通过WXS结合WXML构建出页面视图的结构内容。一般用WXS做过滤和计算处理。

语言特性包括:模块、变量、注释、运算符、语句、数据类型、基础类库
模块特性:
可以通过标签(.wxs)来声明,也可以通过模块来声明;
每一个模块里的变量、函数的作用域都是私有的,需要用module.export将其暴露出来供外界访问:

//直接在同一个文件夹中写模块和访问:
//.wxml中:
<!--WXS的模块-->
<wxs module="m1">
  module.exports={
    message:'Hello World!'
  }
  <!-- module.exports是让变量message暴露出来,外界就可以访问了 -->
</wxs>
<view>{{m1.message}}</view>

大多数情况下是在另一个文件编写模块,再引入:

//.wxml中:
<wxs src="./m2.wxs" module="m2"></wxs>
<view>{{m2.message}}</view>
//m2.wxs中:
// 通过require来请求m3中的数据
module.exports=require('./m3.wxs')
//m3.wxs中:
module.exports={
  message:'Hello World!------m3'
}

运算符:
等值运算符(等号运算、全等运算、非全等运算、非等运算)、赋值运算符、二元逻辑运算符(&&、||)、基本运算符(+-*/)、一元运算符(正+负-,自增++、自减–)、位运算符(<<、>>、&、|)、比较运算符(>、<、>=、<=、==)

语句:
与JS相比,不支持try-catch语句

数据类型:

数据类型

备注

number:

string:

boolean:

object:

array:

function:

date:

生成date对象用getDate函数,不能用new运算符直接生成

regexp:

生成regexp对象使用getRegExp函数,不能用new运算符直接生成

判断wxs的数据类型时,可以用construct来判断
基础类库:
Number:
Date:
Global:
console:
Math:
JSON:

注意:这里的基础类库和JS用到的es5基本是一样的,区别在于console类库只提供console.log()这个方法,Date只提供了es5中三个构造函数的方法,parse(解析Date中的日期时间2018/07/25 16:04:50,返回日期时间的Unix时间戳–类似于1532505890)、now(返回当前日期时间的Unix时间戳)、UTC(返回指定时间的Unix时间戳)
WXS其实是对ES上层做了一些封装和限制。

通过以上4点方便构造一些复杂内容

(5)底层架构和原理

小程序的底层架构实现以及它的启动和运行机制

MINA框架

小程序容器技术 开源方案 开源小程序底层框架_运算符

其中App Service层是数据交互中心,Manager负责逻辑处理,另一部分是WAService.js的文件,来封装各种API的接口,让各个平台的运行环境都能通过API使用微信客户端。整个小程序只有一个AppService,并且整个生命周期常驻内存,由AppService线程来加载运行;
Native层接入了微信客户端的原生能力。视图层和逻辑层双线通信,在视图层和逻辑层之间提供了数据传输和逻辑系统;
视图层和逻辑层之间通过JSBridge进行通信,在逻辑层发生数据变更的时候,通过AppService的setData方法把数据从逻辑层传递到视图层,触发视图层的页面更新,(WXML的节点数结构具有的上下文结构可以使它被渲染成js对象(AST抽象语法树的概念emmm),从而将数据在视图层展示出来),视图层将事件通知给逻辑层进行业务处理。WebView容器在渲染节点内容时,会把传过来的数据进行对比差异(用diff算法emmm),把差异应用在原来的节点树上,渲染出正确的UI界面,
WXS做了很多优化:它和WXML和WXSS一起运行在UI线程,平常从数据库请求到数据库之后通常会做一个转换或filter处理,如果把这部分内容放到UI线程去做,会避免跨进程通信的消耗,当逻辑层的AppService遇到阻塞时,UI线程照样可以正常地处理和渲染。

(6)小程序运行机制

小程序的启动:
①冷启动:首次打开或被微信主动销毁(小程序进入后台超过时间(5min)或短时间(5s)收到2次系统告警时)。
更新机制:若有更新,会异步下载代码包,同时启动本地包启动,新版本在下次打开时被用,如果需要立即使用新版本,则需要用到后面的一个API。
②热启动:不是第一次打开小程序,只需把后台态的小程序切换到前台来使用。

小程序加载机制:

小程序容器技术 开源方案 开源小程序底层框架_数据_02


程序启动时,会向CDN(CDN是内容分发网络,主要的作用是帮我们把请求的内容分发到距离我们最近的网络节点服务器,提高用户响应的速度及成功率,解决带宽、服务器性能带来的一些延迟问题)请求最新的代码包,第一次启动时,要等到代码包下载完毕,注入到WebView容器,执行之后,才能看到小程序页面(所以在网络不好的时候会感到启动时间会长一些),客户端会帮我们把代码包缓存到本地,下一次启动程序时,会先检查是否有最新版本的代码包。

小程序内,还可以访问第三方服务器,发起一个Ajax请求,服务器会返回给我们一个JSON格式的数据。应用生命周期:

四个钩子:

onLaunch:

onShow:

onHide:

onError:

应用生命周期中的globalData:表示应用的全局数据

第一次进入小程序时,客户端会帮我们初始化好运行环境,初始化好后,逻辑层会给app.js实例派发onLaunch事件,在app.js的参数里onLaunch方法被调用;按右上角的关闭键或者按home键,会调用o’nHide方法;再次打开小程序时,小程序进入前台状态,调用的是onShow方法;当小程序出现错误或API调用失败时会触发onError方法,并会向该方法中传入一些错误信息。

页面生命周期:

五个钩子:

onLoad:页面初次加载时,会给页面实例派发,页面没被销毁之前只被调用一次。调用时可以拿到该页面打开所需的一些参数。

onShow:页面显示之后,配置构造器的onShow方法调用。从其它页面返回当前页面时也会调用。

onReady:页面初次渲染完成之后,配置构造器中的onReady方法就会被调用,在onShow方法之后被调用,页面没被销毁之前只能调用一次。触发之后,逻辑层和视图层就可以进行交互。

onHide:在当前页打开一个新的窗口时,当前页会触发onHide。

onUnload:关闭当前页

页面生命周期中的data:当前页面的数据

view线程和AppService线程共同完成生命周期的调用,在onLoad和onShow中请求服务器数据,拿到数据后会触发首次渲染,渲染好后会通知AppService,AppService接收到onReady调用,会把数据再次发送到视图层。

下面是绳命周期的图图:

小程序容器技术 开源方案 开源小程序底层框架_小程序_03

页面路由:
小程序页面不止一个,页面路由由框架进行管理,它以栈的形式维护了所有的小程序页面,下面是一个总结表,包括栈的表现、路由触发方式以及对应页面生命周期函数:

路由方式

页面栈表现

触发时机

路由前页面

路由后页面

初始化

新页面入栈

打开的第一个页面

onLoad,onShow

打开新页面

新页面入栈

调用API wx.navigateTo 或使用组件 < navigator open-type=”navigateTo”/>

onHide

onLoad,onShow

页面重定向

当前页面出栈,新页面入栈

调用wx.redirectTo或使用组件< navigator open-type=”redirectTo”/>

onUnload

onLoad,onShow

页面返回

页面不断出栈,直到目标返回页,新页面入栈

调用APIwx.navigateBack或使用组件< navigator oprn-type=”navigateBack”/>或用户按左上角返回按钮

onUnload

onShow

Tab切换

页面全部出栈,只留下新的Tab页面

调用APIwx.switchTab或使用组件< navigator open-type=”switchTab”/>

各种情况

重加载

页面全部出栈,只留下新的页面

调用APIwx.reLaunch或使用组件< navigator open-type=”relaunch”/>或用户切换Tab

onUnload

onLoad,onShow

小程序事件流:

事件:视图层到逻辑层的通讯方式,可以将用户行为反馈到逻辑层,事件可以绑定在组件上,触发事件后,就会执行逻辑层中对应的事件处理函数

小程序的事件模型:

小程序容器技术 开源方案 开源小程序底层框架_小程序容器技术 开源方案_04


①事件捕获阶段

事件从最外层节点传递到目标节点元素,依次检查每个节点是否绑定了同一事件类型的监听回调函数,有则执行

②事件处理阶段

事件在到达目标节点之后,触发目标节点所绑定的监听回调函数

③事件冒泡阶段

事件从目标节点依次向最外层节点,一次检查每个节点是否绑定了同一事件类型的监听回调函数,有则执行

target:触发事件的根源组件

current-target:触发事件的当前组件

小程序容器技术 开源方案 开源小程序底层框架_运算符_05


touchcancel是被来电事件等打断的事件

tap事件是手指点击一次屏幕的操作(手指按下到离开小于350ms)

longpress和longtap都是>350ms的事件,后者在触发之后还会继续触发tap的一个操作,所以更多地使用longpress

可冒泡事件包括可捕获事件,加上一些动画事件。

transitionend动画结束后的事件,animationstart表示wxss动画开始事件,animationiteration表示wxss动画迭代一次之后的事件,animationend动画结束事件,touchforcechange表示触发了3d touch事件

案例:

//.wxml中:
<view style="background:yellow;width:100%;height:200rpx;border:solid 1rpx black;" class="container-A" bindtap='clickA' capture-bind:tap='captureClickA'>
  Container-A
  <view style="background:green;width:90%;height:100rpx;border:solid 1rpx black;" class='container-B' bindtap='clickB' capture-bind:tap='captureClickB'>
    Container-B
    <view style="background:red;width:80%;height:50rpx;border:solid 1rpx black;" class='container-C' bindtap='clickC' capture-catch:tap='captureClickC'>
      Container-C
    </view>
  </view>
</view>
//.js中:
Page({
  clickA(){
    console.log('click container-A')
  },
  clickB() {
    console.log('click container-B')
  },
  clickC() {
    console.log('click container-C')
  },
  captureClickA(){
    console.log('click capturebind-A')
  },

  captureClickB() {
    console.log('click capturebind-B')
  },
  captureClickC() {
    console.log('click capturecatch-C')
  }
})

界面效果:

小程序容器技术 开源方案 开源小程序底层框架_数据_06

依次点击Container-A、Container-B、Container-C,控制台会打印出以下信息:

小程序容器技术 开源方案 开源小程序底层框架_小程序容器技术 开源方案_07


案例说明bind只有捕获阶段,bind-catch:tap会阻止捕获阶段,但不会阻止冒泡阶段;capture-bind:tap可以绑定事件的捕获阶段和冒泡阶段