小书童
发布时间

编译系统结构

作者

小程序

小程序发展史

小程序与普通网页开发的区别

以下内容引用微信小程序文档【小程序简介 > 小程序与普通网页开发的区别】

网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。而如上文所述,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。

​ 网页开发者需要面对的环境是各式各样的浏览器,PC 端需要面对 IE、Chrome、QQ 浏览器等,在移动端需要面对 Safari、Chrome 以及 iOS、Android 系统中的各式 WebView 。而小程序开发过程中需要面对的是两大操作系统 iOS 和 Android 的微信客户端,以及用于辅助开发的小程序开发者工具,小程序中三大运行环境也是有所区别的,如下表所示。

运行环境逻辑层渲染层
iOSjavascriptCoreWKWebView
安卓V8chromium 定制内核
小程序开发者工具NWJSChrome WebView

开发的准备流程

小程序注册&发布流程一览

申请帐号

  • 邮箱注册
  • 主体类型: 个人、企业、企业(个体工商户)、政府、媒体、其他组织
  • ​ 除个体工商户类型可认证 5 个小程序外,其他类型一个主体可认证 50 个小程序

基础语法

配置

{
  "pages": [              // 页面路径列表
  "pages/index/index",
 ],
 "entryPagePath": "pages/index/index", // 小程序默认启动首页
  "window": {              // 全局的默认窗口表现
    "backgroundColor": "#F6F6F6",
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#F6F6F6",
    "navigationBarTitleText": "前端小书童",
    "navigationBarTextStyle": "black"
  },
 "tabBar": {              // 底部 tab 栏的表现
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页"
      },
      {
        "pagePath": "pages/logs/logs",
        "text": "我的"
      }
    ]
  },
  "sitemapLocation": "sitemap.json",
  "style": "v2",
  "lazyCodeLoading": "requiredComponents",
  "networkTimeout": {           // 网络超时时间
    "request": 10000,
    "downloadFile": 10000
  },
  "debug": true,              // 是否开启 debug 模式,默认关闭
  "functionalPages": false,         // 是否启用插件功能页,默认关闭
 "requiredBackgroundModes": ["audio", "location"] // 申明需要后台运行的能力,类型为数组 audio: 后台音乐播放 location: 后台定位
 "plugins":{},        // 使用到的插件
 "resizable": true
}
{
  "navigationBarBackgroundColor": "#ffffff",   // 导航栏背景颜色
  "navigationBarTextStyle": "black",       // 导航栏标题颜色
  "navigationBarTitleText": "前端小书童",     // 导航栏标题文字内容
  "navigationStyle": "default",          // 导航栏样式,仅支持以下值:default 默认样式;custom 自定义导航栏,只保留右上角胶囊按钮
  "homeButton": true,               // 在非首页、非页面栈最底层页面或非 tabbar 内页面中的导航栏展示 home 键
  "backgroundColor": "#eeeeee",         //  窗口的背景色
  "backgroundTextStyle": "light",        // 下拉 loading 的样式,仅支持 dark / light
  "enablePullDownRefresh": true,         // 是否开启当前页面下拉刷新
}

WXML(模板)

WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。

对比 Vue 模板语法

  • WXML 数据绑定
<view> {{message}} </view>
  • Vue 数据绑定
<div>{{message}}</div>

:::tip WXML 不支持 html 标签(div、span 等),对于使用 view、block、text :::


  • WXML 循环渲染
<view wx:for="{{array}}" wx:for-index="index" wx:for-item="item" wx:key="index">
  {{index}}: {{item.message}}
</view>
  • Vue 循环渲染
<div v-for="(item,index) in array" :key="index">
 {{index}}: {{item.message}}
</div>

  • WXML 条件渲染
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
  • Vue 条件渲染
<div  v-if="length > 5">
 1
</div>
<div  v-if="length > 2">
 2
</div>
<div v-else>3</div>

组件(模板)-引用

定义组件(模板)

<!-- child.wxml -->
<template name="child">
  <text>{{text}}</text>
</template>

在另外一个模板文件中引用该组件(模板)

<!-- parent.wxml -->
<import src="child.wxml"/>
<template is="child" data="{{text: 'forbar'}}"/>

:::tip 在开发中组件的引入注册使用在页面的 json 文件中通过 usingComponents 引入会更易于维护 :::

WXS(脚本)

WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。

WXS 与 javascript 是不同的语言,有自己的语法,并不和 javascript 一致。

通过以下方式引入 WXML 文件中

<wxs src="./demo.wxs" module="demoWxs"></wxs>

微信小程序全局事件订阅

在Vue开发中,我们可能用过eventBus来解决全局范围内的事件订阅及触发逻辑,在微信小程序的开发中我们可能也也会遇到同样的需求,那么我们尝试下在小程序(原生小程序开发)中实现类似eventBus的事件订阅功能。

全局事件订阅

  1. 全局实例 在Vue中我们有new Vue得到的全局对象,小程序中对应的则是app对象,在小程序组件或者页面中可以通过getApp()获取;
  2. 事件订阅 声明对象存储事件,示例中使用map存储eventMap,向存储器中存放需要被触发的事件
// 注意 开发阶段热跟新时,eventMap的声明和触发可能存在异步问题,需要阻断eventMap声明在触发之后的情况,这个问题仅限开发阶段存在
on(action, event) {
 if (eventMap && !eventMap.has(action)) {
  eventMap.set(action, event)
 }
  }
  1. 事件触发 当业务逻辑需要触发时,调用emit触发指定事件
emit(action, arg) {
 if (eventMap && eventMap.has(action)) {
  eventMap.get(action) && eventMap.get(action)(arg)
 }
}
  1. 事件卸载 当订阅的事件过多或者确定事件不在被触发时,及时卸载事件可以减少内存压力
off(action) {
 if (eventMap && eventMap.has(action)) {
  eventMap.delete(action)
 }
   }

整体代码如下(文件:app.js):

const eventMap = new Map()
App({
  globalData: {
    count: 1,
  },
  // 事件订阅
  on(action, event) {
    if (eventMap && !eventMap.has(action)) {
      eventMap.set(action, event)
    }
  },
  // 事件卸载
  off(action) {
    if (eventMap && eventMap.has(action)) {
      eventMap.delete(action)
    }
  },
  // 事件触发
  emit(action, arg) {
    if (eventMap && eventMap.has(action)) {
      eventMap.get(action) && eventMap.get(action)(arg)
    }
  },
})
页面或者组件中使用
  1. 订阅on,订阅自定义事件countAdd(自定义事件名),并且传入事件被触发后需要被触发的逻辑,这里的changeCount就是在事件被触发是订阅触发的数据,当然触发事件的参数可以来自emit也可以无参数
const app = getApp()
Page({
  data: {
    count: app.globalData.count,
  },
  created() {
    // 注册事件
    app.on('countAdd', this.changeCount.bind(this))
  },
  changeCount(count) {
    this.setData({
      count,
    })
  },
})
  1. 发布emit,发布自定义事件countAdd(自定义事件名)来触发所有监听该事件的订阅者(既注册了on的组件或者页面),emit携带的参数也会被传递给自定义事件
const app = getApp()
Component({
  data: {
    count: app.globalData.count,
  },
  // 触发事件
  bindEvent() {
    app.emit('countAdd', this.data.count++)
  },
})

这里changeCount是最终被触发的事件,countAdd是在订阅服务中自定义的事件名,之所以不使用相同的事件名,主要是区分下。

整体事件触发逻辑如下

  1. 先订阅事件 changeCount
  2. 业务需要触发的时候触发bindEvent
  3. emit到全局来调用监听的事件