快捷搜索:
来自 新京葡娱乐场网址 2020-01-01 16:16 的文章
当前位置: 67677新澳门手机版 > 新京葡娱乐场网址 > 正文

三个事务页面包车型大巴产生,Wechat小程序项目

微信小程序开发06-一个业务页面的完成

2018/08/07 · 基础技术 · 小程序

原文出处: 叶小钗   

【微信小程序项目实践总结】30分钟从陌生到熟悉

2018/08/13 · 基础技术 · 小程序

原文出处: 叶小钗   

前言

接上文:微信小程序开发05-日历组件的实现

github地址:

这里来说一说我们的理念,我们也学习小程序开发有一周多了,从近期的使用上来说,小程序可以作为底层,但是缺少一个框架层,这个框架层需要提供:

① 组件库

② 更好的代码组织方式,也就是让我们可以做到轻松的组件化开发

我们从最开始到现在,都在沿着这个方向去分解小程序学习,其实小程序本身的东西差不多了,但是我们代码过程中有时候却越高越复杂,多了很多封装,其实这所有的复杂都是为了设置一个基本的架构,一个标准的开发模式,让后面写业务代码的同学能更高效的写代码,经过一年多的发展,事实上这种较为成熟的框架已经有了,比如我们正在使用的:

但是,可以看到小程序基本还是原生JS,这其实是个非常好的学习整理机会,所以我这边一步步和大家对小程序进行了拆分,期望能形成一套还能用的雏形,帮助大家理解,所以我们继续今天的学习吧,为了降低单页面难度,我们将首页进行下改造。

前言

我们之前对小程序做了基本学习:

1. 微信小程序开发07-列表页面怎么做

2. 微信小程序开发06-一个业务页面的完成

3. 微信小程序开发05-日历组件的实现

4. 微信小程序开发04-打造自己的UI库

5. 微信小程序开发03-这是一个组件

6. 微信小程序开发02-小程序基本介绍

7. 微信小程序开发01-小程序的执行流程是怎么样的?

阅读本文之前,如果大家想对小程序有更深入的了解,或者一些细节的了解可以先阅读上述文章,本文后面点需要对着代码调试阅读

对应的github地址是:

首先我们来一言以蔽之,什么是微信小程序?PS:这个问题问得好像有些扯:)

小程序是一个不需要下载安装就可使用的应用,它实现了应用触手可及的梦想,用户扫一扫或者搜一下即可打开应用。也体现了用完即走的理念,用户不用关心是否安装太多应用的问题。应用将无处不在,随时可用,但又无需安装卸载。从字面上看小程序具有类似Web应用的热部署能力,在功能上又接近于原生APP。

所以说,其实微信小程序是一套超级Hybrid的解决方案,现在看来,小程序应该是应用场景最广,也最为复杂的解决方案了

很多公司都会有自己的Hybrid平台,我这里了解到比较不错的是携程的Hybrid平台、阿里的Weex、百度的糯米,但是从应用场景来说都没有微信来得丰富,这里根本的区别是:

微信小程序是给各个公司开发者接入的,其他公司平台多是给自己业务团队使用,这一根本区别,就造就了我们看到的很多小程序不一样的特性:

① 小程序定义了自己的标签语言WXML

② 小程序定义了自己的样式语言WXSS

③ 小程序提供了一套前端框架包括对应Native API

④ 禁用浏览器Dom API(这个区别,会影响我们的代码方式)

只要了解到这些区别就会知道为什么小程序会这么设计:

因为小程序是给各个公司的开发做的,其他公司的Hybrid方案是给公司业务团队用的,一般拥有Hybrid平台的公司实力都不错 但是开发小程序的公司实力良莠不齐,所以小程序要做绝对的限制,最大程度的保证框架层(小程序团队)对程序的控制 因为毕竟程序运行在微信这种体量的APP中

1
2
3
因为小程序是给各个公司的开发做的,其他公司的Hybrid方案是给公司业务团队用的,一般拥有Hybrid平台的公司实力都不错
但是开发小程序的公司实力良莠不齐,所以小程序要做绝对的限制,最大程度的保证框架层(小程序团队)对程序的控制
因为毕竟程序运行在微信这种体量的APP中

之前我也有一个疑惑为什么微信小程序会设计自己的标签语言,也在知乎看到各种各样的回答,但是如果出于设计层面以及应用层面考虑的话:这样会有更好的控制,而且我后面发现微信小程序事实上依旧使用的是webview做渲染(这个与我之前认为微信是NativeUI是向左的),但是如果我们使用的微信限制下面的标签,这个是有限的标签,后期想要换成NativeUI会变得更加轻易:

新京葡娱乐场网址 1

另一方面,经过之前的学习,我这边明确可以得出一个感受:

小程序的页面核心是标签,标签是不可控制的(我暂时没用到js操作元素的方法),只能按照微信给的玩法玩,标签控制显示是我们的view

② 标签的展示只与data有关联,和js是隔离的,没有办法在标签中调用js的方法

③ 而我们的js的唯一工作便是根据业务改变data,重新引发页面渲染,以后别想操作DOM,别想操作Window对象了,改变开发方式,改变开发方式,改变开发方式!

this.setData({'wxml': ` <my-component> <view>动态插入的节点</view> </my-component> `});

1
2
3
4
5
this.setData({'wxml': `
  <my-component>
  <view>动态插入的节点</view>
  </my-component>
`});

新京葡娱乐场网址 2

然后可以看到这个是一个MVC模型

新京葡娱乐场网址 3

每个页面的目录是这个样子的:

project ├── pages | ├── index | | ├── index.json index 页面配置 | | ├── index.js index 页面逻辑 | | ├── index.wxml index 页面结构 | | └── index.wxss index 页面样式表 | └── log | ├── log.json log 页面配置 | ├── log.wxml log 页面逻辑 | ├── log.js log 页面结构 | └── log.wxss log 页面样式表 ├── app.js 小程序逻辑 ├── app.json 小程序公共设置 └── app.wxss 小程序公共样式表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
project
├── pages
|   ├── index
|   |   ├── index.json  index 页面配置
|   |   ├── index.js    index 页面逻辑
|   |   ├── index.wxml  index 页面结构
|   |   └── index.wxss  index 页面样式表
|   └── log
|       ├── log.json    log 页面配置
|       ├── log.wxml    log 页面逻辑
|       ├── log.js      log 页面结构
|       └── log.wxss    log 页面样式表
├── app.js              小程序逻辑
├── app.json            小程序公共设置
└── app.wxss            小程序公共样式表

每个组件的目录也大概是这个样子的,大同小异,但是入口是Page层。

小程序打包后的结构(这里就真的不懂了,引用:小程序底层框架实现原理解析):

新京葡娱乐场网址 4

所有的小程序基本都最后都被打成上面的结构

1、WAService.js  框架JS库,提供逻辑层基础的API能力

2、WAWebview.js 框架JS库,提供视图层基础的API能力

3、WAConsole.js 框架JS库,控制台

4、app-config.js 小程序完整的配置,包含我们通过app.json里的所有配置,综合了默认配置型

5、app-service.js 我们自己的JS代码,全部打包到这个文件

6、page-frame.html 小程序视图的模板文件,所有的页面都使用此加载渲染,且所有的WXML都拆解为JS实现打包到这里

7、pages 所有的页面,这个不是我们之前的wxml文件了,主要是处理WXSS转换,使用js插入到header区域

从设计的角度上说,小程序采用的组件化开发的方案,除了页面级别的标签,后面全部是组件,而组件中的标签view、data、js的关系应该是与page是一致的,这个也是我们平时建议的开发方式,将一根页面拆分成一个个小的业务组件或者UI组件:

新京葡娱乐场网址 5

从我写业务代码过程中,觉得整体来说还是比较顺畅的,小程序是有自己一套完整的前端框架的,并且释放给业务代码的主要就是page,而page只能使用标签和组件,所以说框架的对业务的控制力度很好。

最后我们从工程角度来看微信小程序的架构就更加完美了,小程序从三个方面考虑了业务者的感受:

① 开发工具 调试工具

② 开发基本模型(开发基本标准WXML、WXSS、JS、JSON)

③ 完善的构建(对业务方透明)

④ 自动化上传离线包(对业务费透明离线包逻辑)

⑤ 监控统计逻辑

所以,微信小程序从架构上和使用场景来说是很令人惊艳的,至少惊艳了我……所以我们接下来在开发层面对他进行更加深入的剖析,我们这边最近一直在做基础服务,这一切都是为了完善技术体系,这里对于前端来说便是我们需要做一个Hybrid体系,如果做App,React Native也是不错的选择,但是一定要有完善的分层:

① 底层框架解决开发效率,将复杂的部分做成一个黑匣子,给页面开发展示的只是固定的三板斧,固定的模式下开发即可

② 工程部门为业务开发者封装最小化开发环境,最优为浏览器,确实不行便为其提供一个类似浏览器的调试环境

如此一来,业务便能快速迭代,因为业务开发者写的代码大同小异,所以底层框架配合工程团队(一般是同一个团队),便可以在底层做掉很多效率性能问题。

稍微大点的公司,稍微宽裕的团队,还会同步做很多后续的性能监控、错误日志工作,如此形成一套文档->开发->调试->构建->发布->监控、分析 为一套完善的技术体系

如果形成了这么一套体系,那么后续就算是内部框架更改、技术革新,也是在这个体系上改造,这块微信小程序是做的非常好的。但很可惜,很多其他公司团队只会在这个路径上做一部分,后面由于种种原因不在深入,有可能是感觉没价值,而最恐怖的行为是,自己的体系没形成就贸然的换基础框架,戒之慎之啊!好了闲话少说,我们继续接下来的学习。

我对小程序的理解有限,因为没有源码只能靠惊艳猜测,如果文中有误,请各位多多提点

文章更多面对初中级选手,如果对各位有用,麻烦点赞哟

首页

首页做了一点改造,变成了这个样式了:

新京葡娱乐场网址 6

这里需要三个点击时间点,因为日历组件,我们昨天就做好了,而他这个出发日期事实上就是我们日历组件的selecedDate,处理这块逻辑:

<template name="searchbox"> <view class="c-row search-line" data-flag="start"> <view class="c-span3"> 出发</view> <view class="c-span9 js-start search-line-txt"> 请选择出发地</view> </view> <view class="c-row search-line" data-flag="arrive"> <view class="c-span3"> 到达</view> <view class="c-span9 js-arrive search-line-txt"> 请选择到达地</view> </view> <view class="c-row search-line" data-flag="arrive"> <view class="c-span3"> 出发日期</view> <view class="c-span9 js-arrive search-line-txt"> {{calendarSelectedDate || '请选择出发日期'}} </view> </view> <view class="c-row " data-flag="arrive"> <span class="btn-primary full-width js_search_list">查询</span> </view> </template>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template name="searchbox">
  <view class="c-row search-line" data-flag="start">
    <view class="c-span3">
      出发</view>
    <view class="c-span9 js-start search-line-txt">
      请选择出发地</view>
  </view>
  <view class="c-row search-line" data-flag="arrive">
    <view class="c-span3">
      到达</view>
    <view class="c-span9 js-arrive search-line-txt">
      请选择到达地</view>
  </view>
  <view class="c-row search-line" data-flag="arrive">
    <view class="c-span3">
      出发日期</view>
    <view class="c-span9 js-arrive search-line-txt">
      {{calendarSelectedDate || '请选择出发日期'}} </view>
  </view>
  <view class="c-row " data-flag="arrive">
    <span class="btn-primary full-width js_search_list">查询</span>
  </view>
</template>

<view class="c-row search-line" data-flag="arrive"> <view class="c-span3"> 出发日期</view> <view class="c-span9 js-arrive search-line-txt"> {{calendarSelectedDate || '请选择出发日期'}} </view> </view>

1
2
3
4
5
6
<view class="c-row search-line" data-flag="arrive">
  <view class="c-span3">
    出发日期</view>
  <view class="c-span9 js-arrive search-line-txt">
    {{calendarSelectedDate || '请选择出发日期'}} </view>
</view>

点击时候我们弹出我们的日历,这个时候我们日历模块释放一个事件显示日历:

PS:template不与页面级别WXML共享一个作用域,所以我暂时都采用的include引入

新京葡娱乐场网址 7

<view class="c-row search-line" data-flag="start"> <view class="c-span3"> 出发</view> <view class="c-span9 js-start search-line-txt"> 请选择出发地</view> </view> <view class="c-row search-line" data-flag="arrive"> <view class="c-span3"> 到达</view> <view class="c-span9 js-arrive search-line-txt"> 请选择到达地</view> </view> <view class="c-row search-line" data-flag="arrive" ontap="showCalendar"> <view class="c-span3"> 出发日期</view> <view class="c-span9 js-arrive search-line-txt"> {{calendarSelectedDateStr}}</view> </view> <view class="c-row " data-flag="arrive"> <span class="btn-primary full-width js_search_list">查询</span> </view> <include src="./mod/calendar.wxml" /> <include src="../../utils/abstract-page.wxml" />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<view class="c-row search-line" data-flag="start">
  <view class="c-span3">
    出发</view>
  <view class="c-span9 js-start search-line-txt">
    请选择出发地</view>
</view>
<view class="c-row search-line" data-flag="arrive">
  <view class="c-span3">
    到达</view>
  <view class="c-span9 js-arrive search-line-txt">
    请选择到达地</view>
</view>
<view class="c-row search-line" data-flag="arrive" ontap="showCalendar">
  <view class="c-span3">
    出发日期</view>
  <view class="c-span9 js-arrive search-line-txt">
    {{calendarSelectedDateStr}}</view>
</view>
<view class="c-row " data-flag="arrive">
  <span class="btn-primary full-width js_search_list">查询</span>
</view>
<include src="./mod/calendar.wxml" />
<include src="../../utils/abstract-page.wxml" />

<view class="c-row search-line" data-flag="arrive" ontap="showCalendar"> <view class="c-span3"> 出发日期</view> <view class="c-span9 js-arrive search-line-txt"> {{calendarSelectedDateStr}}</view> </view>

1
2
3
4
5
6
<view class="c-row search-line" data-flag="arrive" ontap="showCalendar">
  <view class="c-span3">
    出发日期</view>
  <view class="c-span9 js-arrive search-line-txt">
    {{calendarSelectedDateStr}}</view>
</view>

/* 事实上一个mod就只是一个对象,只不过为了方便拆分,将对象分拆成一个个的mod 一个mod对应一个wxml,但是共享外部的css,暂时如此设计 所有日历模块的需求全部再此实现 */ const util = require('../../../utils/util.js') let selectedDate = new Date(); module.exports = { showCalendar: function () { this.setData({ isCalendarShow: '' }); }, onCalendarDayTap: function (e) { let data = e.detail; var date = new Date(data.year, data.month, data.day); console.log(date) this.setData({ calendarSelectedDate: date, calendarSelectedDateStr: util.dateUtil.format(date, 'Y年M月D日') }); }, data: { isCalendarShow: 'none', calendarDisplayMonthNum: 2, calendarDisplayTime: new Date(), calendarSelectedDate: selectedDate, calendarSelectedDateStr: util.dateUtil.format(selectedDate, 'Y年M月D日') } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
事实上一个mod就只是一个对象,只不过为了方便拆分,将对象分拆成一个个的mod
一个mod对应一个wxml,但是共享外部的css,暂时如此设计
所有日历模块的需求全部再此实现
*/
const util = require('../../../utils/util.js')
 
let selectedDate = new Date();
 
module.exports = {
  showCalendar: function () {
    this.setData({
      isCalendarShow: ''
    });
  },
  onCalendarDayTap: function (e) {
    let data = e.detail;
    var date = new Date(data.year, data.month, data.day);
    console.log(date)
    this.setData({
      calendarSelectedDate: date,
      calendarSelectedDateStr: util.dateUtil.format(date, 'Y年M月D日')
    });
  },
  data: {
    isCalendarShow: 'none',
    calendarDisplayMonthNum: 2,
    calendarDisplayTime: new Date(),
    calendarSelectedDate: selectedDate,
    calendarSelectedDateStr: util.dateUtil.format(selectedDate, 'Y年M月D日')
  }
}

显然,这里的日历这样摆设有点丑,我们这里将其封装成一个弹出层,所以我们这里再做一个容器类组件,专门用于装载页面样式用:

新京葡娱乐场网址 8

新京葡娱乐场网址 9

<view class="cm-modal " style="z-index: {{uiIndex}}; position: fixed; display: {{isShow}}; "> <slot ></slot> </view> <view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}" > </view>

1
2
3
4
5
<view class="cm-modal " style="z-index: {{uiIndex}}; position: fixed; display: {{isShow}}; ">
  <slot ></slot>
</view>
<view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}" >
</view>

<ui-container bindonContainerHide="onContainerHide" is-show="{{isCalendarShow}}" > <view class="calendar-wrapper-box"> <view class="box-hd"> <text class="fl icon-back js_back "></text> <text class="fr icon-next js_next"></text> </view> <ui-calendar bindonDayTap="onCalendarDayTap" displayTime="{{calendarDisplayTime}}" selectedDate="{{calendarSelectedDate}}" displayMonthNum="{{calendarDisplayMonthNum}}" is-show="{{isCalendarShow}}"></ui-calendar> </view> </ui-container>

1
2
3
4
5
6
7
8
9
10
11
<ui-container bindonContainerHide="onContainerHide" is-show="{{isCalendarShow}}" >
    <view class="calendar-wrapper-box">
      <view class="box-hd">
        <text class="fl icon-back js_back "></text>
        <text class="fr icon-next js_next"></text>
      </view>
      <ui-calendar bindonDayTap="onCalendarDayTap" displayTime="{{calendarDisplayTime}}"
selectedDate="{{calendarSelectedDate}}" displayMonthNum="{{calendarDisplayMonthNum}}"
is-show="{{isCalendarShow}}"></ui-calendar>
    </view>
</ui-container>

但是这里也引起了其他问题,因为引入了shadow-dom概念,我的样式不能重用,组件内部样式与外部是不能通信的,但是这里是页面级别容器,内容的样式肯定是来源页面的,这里没什么问题,所以我们这里显示的是正确的,但是我这里想做一个出格一点的操作,我想用样式将这里日历月标题换个位置:

新京葡娱乐场网址 10

而日历组件和外部是不能通信的,我们这里该如何处理呢,我这里想了两个方案:

① 设置一个全局使用的组件库样式,让所有组件继承,但是不知道这里对性能是否有影响,因为这样的话体积不会太小

② 小程序设计了可以传入组件的方法,比如我们这里的日历组件我们可以这样改变其样式

.calendar-cm-month { position: absolute; top: 0; height: 90rpx; line-height: 90rpx; width: 100%; color: #00b358; text-align: center; }

1
2
3
4
5
6
7
8
9
.calendar-cm-month {
    position: absolute;
    top: 0;
    height: 90rpx;
    line-height: 90rpx;
    width: 100%;
    color: #00b358;
    text-align: center;
}

Component({ externalClasses: ['ex-class'], behaviors: [ View ], properties: { displayMonthNum: { type: Number }, displayTime: { type: Date }, selectedDate: { type: Date } }, data: { weekDayArr: ['日', '一', '二', '三', '四', '五', '六'], }, attached: function () { //console.log(this) // debugger }, methods: { onDayTap: function (e) { this.triggerEvent('onDayTap', e.currentTarget.dataset) } } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Component({
  externalClasses: ['ex-class'],
  behaviors: [
    View
  ],
  properties: {
    displayMonthNum: {
      type: Number
    },
    displayTime: {
      type: Date
    },
    selectedDate: {
      type: Date
    }
  },
  data: {
    weekDayArr: ['日', '一', '二', '三', '四', '五', '六'],
  },
 
  attached: function () {
    //console.log(this)
    // debugger
  },
  methods: {
    onDayTap: function (e) {
      this.triggerEvent('onDayTap', e.currentTarget.dataset)
    }
  }
})

<ui-container bindonContainerHide="onContainerHide" is-show="{{isCalendarShow}}" > <view class="calendar-wrapper-box"> <view class="box-hd"> <text class="fl icon-back js_back "></text> <text class="fr icon-next js_next"></text> </view> <ui-calendar ex-class="calendar-cm-month" bindonDayTap="onCalendarDayTap" displayTime="{{calendarDisplayTime}}" selectedDate="{{calendarSelectedDate}}" displayMonthNum="{{calendarDisplayMonthNum}}" is-show="{{isCalendarShow}}"></ui-calendar> </view> </ui-container>

1
2
3
4
5
6
7
8
9
10
11
<ui-container bindonContainerHide="onContainerHide" is-show="{{isCalendarShow}}" >
    <view class="calendar-wrapper-box">
      <view class="box-hd">
        <text class="fl icon-back js_back "></text>
        <text class="fr icon-next js_next"></text>
      </view>
      <ui-calendar ex-class="calendar-cm-month" bindonDayTap="onCalendarDayTap"
displayTime="{{calendarDisplayTime}}" selectedDate="{{calendarSelectedDate}}"
displayMonthNum="{{calendarDisplayMonthNum}}" is-show="{{isCalendarShow}}"></ui-calendar>
    </view>
</ui-container>

具体各位去github上查看,总而言之,我们的页面变成了这个样子了:

新京葡娱乐场网址 11

PS:这里发现一个不知道是不是坑点的点,我们这里属性传递的是一个date对象,但是到了组件层之间变成了对象,不知微信底层做了什么:

JavaScript

calendarDisplayTime: new Date()

1
calendarDisplayTime: new Date()

新京葡娱乐场网址 12

好像变成了一个空对象,这里可能发生的情况是,经过传递的日期对象会被某种特殊处理,但是具体发生了什么事情就不知道了,这个却引起了我们不小的麻烦,这里大概去翻开了一下源码:

新京葡娱乐场网址 13

极有可能,小程序本身就不支持date属性的传递,我们的日历组件能跑起来的原因是什么,我这里都有点疑惑了……

而且就算以对象方式传递到组件的date类型都会变成莫名其妙的东西:

ttt: { key: 'date', value: selectedDate },

1
2
3
4
ttt: {
   key: 'date',
   value: selectedDate
},

新京葡娱乐场网址 14

这个特性有点令人抓不住头脑了,这里根据探查,很有可能Component将date对象传入WXML解释时候,自动转为了日期字符串了,所以我们这里看上去是对象的东西其实是字符串,这里的建议是:跟组件的date传递,暂时全部使用字符串代替,以免自我麻烦,然后我们先将之前的日历操作全部变成字符串,再为我们的前后按钮加上事件:

module.exports = { showCalendar: function () { this.setData({ isCalendarShow: '' }); }, hideCalendar: function () { this.setData({ isCalendarShow: 'none' }); }, preMonth: function () { this.setData({ calendarDisplayTime: util.dateUtil.preMonth(this.data.calendarDisplayTime).toString() }); }, nextMonth: function () { this.setData({ calendarDisplayTime: util.dateUtil.nextMonth(this.data.calendarDisplayTime).toString() }); }, onCalendarDayTap: function (e) { let data = e.detail; var date = new Date(data.year, data.month, data.day); console.log(date) this.setData({ isCalendarShow: 'none', calendarSelectedDate: date.toString(), calendarSelectedDateStr: util.dateUtil.format(date, 'Y年M月D日') }); }, onContainerHide: function () { this.hideCalendar(); }, data: { ttt: { key: 'date', value: selectedDate }, isCalendarShow: '', calendarDisplayMonthNum: 1, calendarDisplayTime: new Date(2018, 9).toString(), calendarSelectedDate: selectedDate, calendarSelectedDateStr: util.dateUtil.format(new Date(selectedDate), 'Y年M月D日') } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
module.exports = {
  showCalendar: function () {
    this.setData({
      isCalendarShow: ''
    });
  },
  hideCalendar: function () {
    this.setData({
      isCalendarShow: 'none'
    });
  },
  preMonth: function () {
 
    this.setData({
      calendarDisplayTime: util.dateUtil.preMonth(this.data.calendarDisplayTime).toString()
    });
  },
  nextMonth: function () {
    this.setData({
      calendarDisplayTime: util.dateUtil.nextMonth(this.data.calendarDisplayTime).toString()
    });
  },
  onCalendarDayTap: function (e) {
    let data = e.detail;
    var date = new Date(data.year, data.month, data.day);
    console.log(date)
    this.setData({
      isCalendarShow: 'none',
      calendarSelectedDate: date.toString(),
      calendarSelectedDateStr: util.dateUtil.format(date, 'Y年M月D日')
    });
  },
  onContainerHide: function () {
    this.hideCalendar();
  },
 
  data: {
    ttt: {
      key: 'date',
      value: selectedDate
    },
    isCalendarShow: '',
    calendarDisplayMonthNum: 1,
    calendarDisplayTime: new Date(2018, 9).toString(),
    calendarSelectedDate: selectedDate,
    calendarSelectedDateStr: util.dateUtil.format(new Date(selectedDate), 'Y年M月D日')
  }
}

虽然看上去恶心了一点,但是总是不会出什么明显的问题,忍一忍吧……日期部分基本结束了,还有些小的限制没有做上,比如哪些时段能选,哪些不能,这块就有待各位发现吧,我们这里毕竟是学习,做细了很花功夫,我们接下来做出发目的地选择部分。

微信小程序的执行流程

微信小程序为了对业务方有更强的控制,App层做的工作很有限,我后面写demo的时候根本没有用到app.js,所以我这里认为app.js只是完成了一个路由以及初始化相关的工作,这个是我们看得到的,我们看不到的是底层框架会根据app.json的配置将所有页面js都准备好。

我这里要表达的是,我们这里配置了我们所有的路由:

"pages":[ "pages/index/index", "pages/list/list", "pages/logs/logs" ],

1
2
3
4
5
"pages":[
  "pages/index/index",
  "pages/list/list",
  "pages/logs/logs"
],

微信小程序一旦载入,会开3个webview,装载3个页面的逻辑,完成基本的实例化工作,只显示首页!这个是小程序为了优化页面打开速度所做的工作,也势必会浪费一些资源,所以到底是全部打开或者预加载几个,详细底层Native会根据实际情况动态变化,我们也可以看到,从业务层面来说,要了解小程序的执行流程,其实只要能了解Page的流程就好了,关于Page生命周期,除了释放出来的API:onLoad -> onShow -> onReady -> onHide等,官方还出了一张图进行说明:

新京葡娱乐场网址 15

Native层在载入小程序时候,起了两个线程一个的view Thread一个是AppService Thread,我这边理解下来应该就是程序逻辑执行与页面渲染分离,小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

因为之前我认为页面是使用NativeUI做渲染跟Webview没撒关系,便觉得这个图有问题,但是后面实际代码看到了熟悉的shadow-dom以及Android可以看到哪部分是Web的,其实小程序主体还是使用的浏览器渲染的方式,还是webview装载HTML和CSS的逻辑,最后我发现这张图是没有问题的,有问题的是我的理解,哈哈,这里我们重新解析这张图:

WXML先会被编译成JS文件,引入数据后在WebView中渲染,这里可以认为微信载入小程序时同时初始化了两个线程,分别执行彼此逻辑:

① WXML&CSS编译形成的JS View实例化结束,准备结束时向业务线程发送通知

② 业务线程中的JS Page部分同步完成实例化结束,这个时候接收到View线程部分的等待数据通知,将初始化data数据发送给View

③ View线程接到数据,开始渲染页面,渲染结束执行通知Page触发onReady事件

这里翻开源码,可以看到,应该是全局控制器完成的Page实例化,完成后便会执行onLoad事件,但是在执行前会往页面发通知:

__appServiceSDK__.invokeWebviewMethod({ name: "appDataChange", args: o({}, e, { complete: n }), webviewIds: [t] })

1
2
3
4
5
6
7
__appServiceSDK__.invokeWebviewMethod({
    name: "appDataChange",
    args: o({}, e, {
        complete: n
    }),
    webviewIds: [t]
})

新京葡娱乐场网址 16

新京葡娱乐场网址 17

真实的逻辑是这样的,全局控制器会完成页面实例化,这个是根据app.json中来的,全部完成实例化存储起来然后选择第一个page实例执行一些逻辑,然后通知view线程,即将执行onLoad事件,因为view线程和业务线程是两个线程,所以不会造成阻塞,view线程根据初始数据完成渲染,而业务线程继续后续逻辑,执行onLoad,如果onLoad中有setData,那么会进入队列继续通知view线程更新。

所以我个人感觉微信官网那张图不太清晰,我这里重新画了一个图:

新京葡娱乐场网址 18

再引用一张其他地方的图:

新京葡娱乐场网址 19

数据请求

模拟实现

都这个时候了,不来个简单的小程序框架实现好像有点不对,我们做小程序实现的主要原因是想做到一端代码三端运行:web、小程序、Hybrid甚至Servce端

我们这里没有可能实现太复杂的功能,这里想的是就实现一个基本的页面展示带一个最基本的标签即可,只做Page一块的简单实现,让大家能了解到小程序可能的实现,以及如何将小程序直接转为H5的可能走法

新京葡娱乐场网址 20

<view> <!-- 以下是对一个自定义组件的引用 --> <my-component inner-text="组件数据"></my-component> <view>{{pageData}}</view> </view>

1
2
3
4
5
<view>
  <!-- 以下是对一个自定义组件的引用 -->
  <my-component inner-text="组件数据"></my-component>
  <view>{{pageData}}</view>
</view>

Page({ data: { pageData: '页面数据' }, onLoad: function () { console.log('onLoad') }, })

1
2
3
4
5
6
7
8
Page({
  data: {
    pageData: '页面数据'
  },
  onLoad: function () {
    console.log('onLoad')
  },
})

<!-- 这是自定义组件的内部WXML结构 --> <view class="inner"> {{innerText}} </view> <slot></slot>

1
2
3
4
5
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
  {{innerText}}
</view>
<slot></slot>

Component({ properties: { // 这里定义了innerText属性,属性值可以在组件使用时指定 innerText: { type: String, value: 'default value', } }, data: { // 这里是一些组件内部数据 someData: {} }, methods: { // 这里是一个自定义方法 customMethod: function () { } } })

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Component({
  properties: {
    // 这里定义了innerText属性,属性值可以在组件使用时指定
    innerText: {
      type: String,
      value: 'default value',
    }
  },
  data: {
    // 这里是一些组件内部数据
    someData: {}
  },
  methods: {
    // 这里是一个自定义方法
    customMethod: function () { }
  }
})

我们直接将小程序这些代码拷贝一份到我们的目录:

新京葡娱乐场网址 21

我们需要做的就是让这段代码运行起来,而这里的目录是我们最终看见的目录,真实运行的时候可能不是这个样,运行之前项目会通过我们的工程构建,变成可以直接运行的代码,而我这里思考的可以运行的代码事实上是一个模块,所以我们这里从最终结果反推、分拆到开发结构目录,我们首先将所有代码放到index.html,可能是这样的:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript" src="libs/zepto.js" ></script> <script type="text/javascript"> class View { constructor(opts) { this.template = '<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>'; //由控制器page传入的初始数据或者setData产生的数据 this.data = { pageShow: 'pageshow', pageData: 'pageData', pageShow1: 'pageShow1' }; this.labelMap = { 'view': 'div', '#text': 'span' }; this.nodes = {}; this.nodeInfo = {}; } /* 传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变 并且将其中包含{{}}标志的节点信息记录下来 */ _handlerNode (node) { let reg = /{{([sS] ?)}}/; let result, name, value, n, map = {}; let attrs , i, len, attr; name = node.nodeName; attrs = node.attributes; value = node.nodeValue; n = document.createElement(this.labelMap[name.toLowerCase()] || name); //说明是文本,需要记录下来了 if(node.nodeType === 3) { n.innerText = this.data[value] || ''; result = reg.exec(value); if(result) { n.innerText = this.data[result[1]] || ''; if(!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'text', node: n }); } } if(attrs) { //这里暂时只处理属性和值两种情况,多了就复杂10倍了 for (i = 0, len = attrs.length; i < len; i ) { attr = attrs[i]; result = reg.exec(attr.value); n.setAttribute(attr.name, attr.value); //如果有node需要处理则需要存下来标志 if (result) { n.setAttribute(attr.name, this.data[result[1]] || ''); //存储所有会用到的节点,以便后面动态更新 if (!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'attr', name: attr.name, node: n }); } } } return { node: n, map: map } } //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止 _runAllNode(node, map, root) { let nodeInfo = this._handlerNode(node); let _map = nodeInfo.map; let n = nodeInfo.node; let k, i, len, children = node.childNodes; //先将该根节点插入到上一个节点中 root.appendChild(n); //处理map数据,这里的map是根对象,最初的map for(k in _map) { if(map[k]) { map[k].push(_map[k]); } else { map[k] = _map[k]; } } for(i = 0, len = children.length; i < len; i ) { this._runAllNode(children[i], map, n); } } //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录 splitTemplate () { let nodes = $(this.template); let map = {}, root = document.createElement('div'); let i, len; for(i = 0, len = nodes.length; i < len; i ) { this._runAllNode(nodes[i], map, root); } window.map = map; return root } //拆分目标形成node,这个方法过长,真实项目需要拆分 splitTemplate1 () { let template = this.template; let node = $(this.template)[0]; let map = {}, n, name, root = document.createElement('div'); let isEnd = false, index = 0, result; let attrs, i, len, attr; let reg = /{{([sS] ?)}}/; window.map = map; //开始遍历节点,处理 while (!isEnd) { name = node.localName; attrs = node.attributes; value = node.nodeValue; n = document.createElement(this.labelMap[name] || name); //说明是文本,需要记录下来了 if(node.nodeType === 3) { n.innerText = this.data[value] || ''; result = reg.exec(value); if(result) { n.innerText = this.data[value] || ''; if(!map[value]) map[value] = []; map[value].push({ type: 'text', node: n }); } } //这里暂时只处理属性和值两种情况,多了就复杂10倍了 for(i = 0, len = attrs.length; i < len; i ) { attr = attrs[i]; result = reg.exec(attr.value); n.setAttribute(attr.name, attr.value); //如果有node需要处理则需要存下来标志 if(result) { n.setAttribute(attr.name, this.data[result[1]] || ''); //存储所有会用到的节点,以便后面动态更新 if(!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'attr', name: attr.name, node: n }); } } debugger if(index === 0) root.appendChild(n); isEnd = true; index ; } return root; console.log(node) } } let view = new View(); document.body.appendChild(window.node) </script> </body> </html> 模拟核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
 
<script type="text/javascript" src="libs/zepto.js" ></script>
<script type="text/javascript">
 
  class View {
    constructor(opts) {
      this.template = '<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>';
 
      //由控制器page传入的初始数据或者setData产生的数据
      this.data = {
        pageShow: 'pageshow',
        pageData: 'pageData',
        pageShow1: 'pageShow1'
      };
 
      this.labelMap = {
        'view': 'div',
        '#text': 'span'
      };
 
      this.nodes = {};
      this.nodeInfo = {};
    }
 
    /*
      传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变
      并且将其中包含{{}}标志的节点信息记录下来
    */
    _handlerNode (node) {
 
      let reg = /{{([sS] ?)}}/;
      let result, name, value, n, map = {};
      let attrs , i, len, attr;
 
      name = node.nodeName;
      attrs = node.attributes;
      value = node.nodeValue;
      n = document.createElement(this.labelMap[name.toLowerCase()] || name);
 
      //说明是文本,需要记录下来了
      if(node.nodeType === 3) {
        n.innerText =  this.data[value] || '';
 
        result =  reg.exec(value);
        if(result) {
          n.innerText =  this.data[result[1]] || '';
 
          if(!map[result[1]]) map[result[1]] = [];
          map[result[1]].push({
            type: 'text',
            node: n
          });
        }
      }
 
      if(attrs) {
        //这里暂时只处理属性和值两种情况,多了就复杂10倍了
        for (i = 0, len = attrs.length; i < len; i ) {
          attr = attrs[i];
          result = reg.exec(attr.value);
 
          n.setAttribute(attr.name, attr.value);
          //如果有node需要处理则需要存下来标志
          if (result) {
            n.setAttribute(attr.name, this.data[result[1]] || '');
 
            //存储所有会用到的节点,以便后面动态更新
            if (!map[result[1]]) map[result[1]] = [];
            map[result[1]].push({
              type: 'attr',
              name: attr.name,
              node: n
            });
 
          }
        }
      }
 
      return {
        node: n,
        map: map
      }
 
    }
 
    //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止
    _runAllNode(node, map, root) {
 
      let nodeInfo = this._handlerNode(node);
      let _map = nodeInfo.map;
      let n = nodeInfo.node;
      let k, i, len, children = node.childNodes;
 
      //先将该根节点插入到上一个节点中
      root.appendChild(n);
 
      //处理map数据,这里的map是根对象,最初的map
      for(k in _map) {
        if(map[k]) {
          map[k].push(_map[k]);
        } else {
          map[k] = _map[k];
        }
      }
 
      for(i = 0, len = children.length; i < len; i ) {
        this._runAllNode(children[i], map, n);
      }
 
    }
 
    //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录
    splitTemplate () {
      let nodes = $(this.template);
      let map = {}, root = document.createElement('div');
      let i, len;
 
      for(i = 0, len = nodes.length; i < len; i ) {
        this._runAllNode(nodes[i], map, root);
      }
 
      window.map = map;
      return root
    }
 
      //拆分目标形成node,这个方法过长,真实项目需要拆分
    splitTemplate1 () {
      let template = this.template;
      let node = $(this.template)[0];
      let map = {}, n, name, root = document.createElement('div');
      let isEnd = false, index = 0, result;
 
      let attrs, i, len, attr;
      let reg = /{{([sS] ?)}}/;
 
      window.map = map;
 
      //开始遍历节点,处理
      while (!isEnd) {
        name = node.localName;
        attrs = node.attributes;
        value = node.nodeValue;
        n = document.createElement(this.labelMap[name] || name);
 
        //说明是文本,需要记录下来了
        if(node.nodeType === 3) {
          n.innerText =  this.data[value] || '';
 
          result =  reg.exec(value);
          if(result) {
            n.innerText =  this.data[value] || '';
 
            if(!map[value]) map[value] = [];
            map[value].push({
              type: 'text',
              node: n
            });
          }
        }
 
        //这里暂时只处理属性和值两种情况,多了就复杂10倍了
        for(i = 0, len = attrs.length; i < len; i ) {
          attr = attrs[i];
          result =  reg.exec(attr.value);
 
          n.setAttribute(attr.name, attr.value);
          //如果有node需要处理则需要存下来标志
          if(result) {
            n.setAttribute(attr.name, this.data[result[1]] || '');
 
            //存储所有会用到的节点,以便后面动态更新
            if(!map[result[1]]) map[result[1]] = [];
            map[result[1]].push({
              type: 'attr',
              name: attr.name,
              node: n
            });
 
          }
        }
 
debugger
 
        if(index === 0) root.appendChild(n);
        isEnd = true;
        index ;
 
      }
 
      return root;
 
 
      console.log(node)
    }
 
  }
 
  let view = new View();
 
  document.body.appendChild(window.node)
 
</script>
</body>
</html>
 
模拟核心代码

这段代码,非常简单:

① 设置了一段模板,甚至,我们这里根本不关系其格式化状态,直接写成一行方便处理

this.template = '<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>';

1
this.template = '<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>';

② 然后我们将这段模板转为node节点(这里可以不用zepto,但是模拟实现怎么简单怎么来吧),然后遍历处理所有节点,我们就可以处理我们的数据了,最终形成了这个html:

<div><div><span>ffsd</span></div><div class="ddd" is-show="pageshow"><span>pageshow</span><div class="c1"><span>pageData</span></div></div></div>

1
<div><div><span>ffsd</span></div><div class="ddd" is-show="pageshow"><span>pageshow</span><div class="c1"><span>pageData</span></div></div></div>

③ 与此同时,我们存储了一个对象,这个对象包含所有与之相关的节点:

新京葡娱乐场网址 22

这个对象是所有setData会影响到node的一个映射表,后面调用setData的时候,便可以直接操作对应的数据了,这里我们分拆我们代码,形成了几个关键部分,首先是View类,这个对应我们的模板,是核心类:

//View为模块的实现,主要用于解析目标生产node class View { constructor(template) { this.template = template; //由控制器page传入的初始数据或者setData产生的数据 this.data = {}; this.labelMap = { 'view': 'div', '#text': 'span' }; this.nodes = {}; this.root = {}; } setInitData(data) { this.data = data; } //数据便会引起的重新渲染 reRender(data, allData) { this.data = allData; let k, v, i, len, j, len2, v2; //开始重新渲染逻辑,寻找所有保存了的node for(k in data) { if(!this.nodes[k]) continue; for(i = 0, len = this.nodes[k].length; i < len; i ) { for(j = 0; j < this.nodes[k][i].length; j ) { v = this.nodes[k][i][j]; if(v.type === 'text') { v.node.innerText = data[k]; } else if(v.type === 'attr') { v.node.setAttribute(v.name, data[k]); } } } } } /* 传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变 并且将其中包含{{}}标志的节点信息记录下来 */ _handlerNode (node) { let reg = /{{([sS] ?)}}/; let result, name, value, n, map = {}; let attrs , i, len, attr; name = node.nodeName; attrs = node.attributes; value = node.nodeValue; n = document.createElement(this.labelMap[name.toLowerCase()] || name); //说明是文本,需要记录下来了 if(node.nodeType === 3) { n.innerText = this.data[value] || ''; result = reg.exec(value); if(result) { n.innerText = this.data[result[1]] || ''; if(!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'text', node: n }); } } if(attrs) { //这里暂时只处理属性和值两种情况,多了就复杂10倍了 for (i = 0, len = attrs.length; i < len; i ) { attr = attrs[i]; result = reg.exec(attr.value); n.setAttribute(attr.name, attr.value); //如果有node需要处理则需要存下来标志 if (result) { n.setAttribute(attr.name, this.data[result[1]] || ''); //存储所有会用到的节点,以便后面动态更新 if (!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'attr', name: attr.name, node: n }); } } } return { node: n, map: map } } //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止 _runAllNode(node, map, root) { let nodeInfo = this._handlerNode(node); let _map = nodeInfo.map; let n = nodeInfo.node; let k, i, len, children = node.childNodes; //先将该根节点插入到上一个节点中 root.appendChild(n); //处理map数据,这里的map是根对象,最初的map for(k in _map) { if(!map[k]) map[k] = []; map[k].push(_map[k]); } for(i = 0, len = children.length; i < len; i ) { this._runAllNode(children[i], map, n); } } //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录 splitTemplate () { let nodes = $(this.template); let map = {}, root = document.createElement('div'); let i, len; for(i = 0, len = nodes.length; i < len; i ) { this._runAllNode(nodes[i], map, root); } this.nodes = map; this.root = root; } render() { let i, len; this.splitTemplate(); for(i = 0, len = this.root.childNodes.length; i< len; i ) document.body.appendChild(this.root.childNodes[0]); } } 核心模板处理类View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//View为模块的实现,主要用于解析目标生产node
class View {
  constructor(template) {
    this.template = template;
 
    //由控制器page传入的初始数据或者setData产生的数据
    this.data = {};
 
    this.labelMap = {
      'view': 'div',
      '#text': 'span'
    };
 
    this.nodes = {};
    this.root = {};
  }
 
  setInitData(data) {
    this.data = data;
  }
 
  //数据便会引起的重新渲染
  reRender(data, allData) {
    this.data = allData;
    let k, v, i, len, j, len2, v2;
 
    //开始重新渲染逻辑,寻找所有保存了的node
    for(k in data) {
      if(!this.nodes[k]) continue;
      for(i = 0, len = this.nodes[k].length; i < len; i ) {
        for(j = 0; j < this.nodes[k][i].length; j ) {
          v = this.nodes[k][i][j];
          if(v.type === 'text') {
            v.node.innerText = data[k];
          } else if(v.type === 'attr') {
            v.node.setAttribute(v.name, data[k]);
          }
        }
      }
    }
  }
  /*
    传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变
    并且将其中包含{{}}标志的节点信息记录下来
  */
  _handlerNode (node) {
 
    let reg = /{{([sS] ?)}}/;
    let result, name, value, n, map = {};
    let attrs , i, len, attr;
 
    name = node.nodeName;
    attrs = node.attributes;
    value = node.nodeValue;
    n = document.createElement(this.labelMap[name.toLowerCase()] || name);
 
    //说明是文本,需要记录下来了
    if(node.nodeType === 3) {
      n.innerText =  this.data[value] || '';
 
      result =  reg.exec(value);
      if(result) {
        n.innerText =  this.data[result[1]] || '';
 
        if(!map[result[1]]) map[result[1]] = [];
        map[result[1]].push({
          type: 'text',
          node: n
        });
      }
    }
 
    if(attrs) {
      //这里暂时只处理属性和值两种情况,多了就复杂10倍了
      for (i = 0, len = attrs.length; i < len; i ) {
        attr = attrs[i];
        result = reg.exec(attr.value);
 
        n.setAttribute(attr.name, attr.value);
        //如果有node需要处理则需要存下来标志
        if (result) {
          n.setAttribute(attr.name, this.data[result[1]] || '');
 
          //存储所有会用到的节点,以便后面动态更新
          if (!map[result[1]]) map[result[1]] = [];
          map[result[1]].push({
            type: 'attr',
            name: attr.name,
            node: n
          });
 
        }
      }
    }
 
    return {
      node: n,
      map: map
    }
 
  }
 
  //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止
  _runAllNode(node, map, root) {
 
    let nodeInfo = this._handlerNode(node);
    let _map = nodeInfo.map;
    let n = nodeInfo.node;
    let k, i, len, children = node.childNodes;
 
    //先将该根节点插入到上一个节点中
    root.appendChild(n);
 
    //处理map数据,这里的map是根对象,最初的map
    for(k in _map) {
      if(!map[k]) map[k] = [];
      map[k].push(_map[k]);
    }
 
    for(i = 0, len = children.length; i < len; i ) {
      this._runAllNode(children[i], map, n);
    }
 
  }
 
  //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录
  splitTemplate () {
    let nodes = $(this.template);
    let map = {}, root = document.createElement('div');
    let i, len;
 
    for(i = 0, len = nodes.length; i < len; i ) {
      this._runAllNode(nodes[i], map, root);
    }
 
    this.nodes = map;
    this.root = root;
  }
 
  render() {
    let i, len;
    this.splitTemplate();
    for(i = 0, len = this.root.childNodes.length; i< len; i )
      document.body.appendChild(this.root.childNodes[0]);
  }
 
}
 
核心模板处理类View

这个类主要完成的工作是:

① 接受传入的template字符串(直接由index.wxml读出)

② 解析template模板,生成字符串和兼职与node映射表,方便后期setData导致的改变

③ 渲染和再次渲染工作

然后就是我们的Page类的实现了,这里反而比较简单(当然这里的实现是不完善的):

//这个为js罗杰部分实现,后续会释放工厂方法 class PageClass { //构造函数,传入对象 constructor(opts) { //必须拥有的参数 this.data = {}; Object.assign(this, opts); } //核心方法,每个Page对象需要一个模板实例 setView(view) { this.view = view; } //核心方法,设置数据后会引发页面刷新 setData(data) { Object.assign(this.data, data); //只影响改变的数据 this.view.reRender(data, this.data) } render() { this.view.setInitData(this.data); this.view.render(); if(this.onLoad) this.onLoad(); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//这个为js罗杰部分实现,后续会释放工厂方法
class PageClass {
  //构造函数,传入对象
  constructor(opts) {
 
    //必须拥有的参数
    this.data = {};
    Object.assign(this, opts);
  }
 
  //核心方法,每个Page对象需要一个模板实例
  setView(view) {
    this.view = view;
  }
 
  //核心方法,设置数据后会引发页面刷新
  setData(data) {
    Object.assign(this.data, data);
 
    //只影响改变的数据
    this.view.reRender(data, this.data)
  }
 
  render() {
    this.view.setInitData(this.data);
    this.view.render();
 
    if(this.onLoad) this.onLoad();
  }
 
}

现在轮着我们实际调用方,Page方法出场了:

function Page (data) { let page = new PageClass(data); return page; }

1
2
3
4
function Page (data) {
  let page = new PageClass(data);
  return page;
}

基本上什么都没有干的感觉,调用层代码这样写:

function main() { let view = new View('<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>'); let page = Page({ data: { pageShow: 'pageshow', pageData: 'pageData', pageShow1: 'pageShow1' }, onLoad: function () { this.setData({ pageShow: '我是pageShow啊' }); } }); page.setView(view); page.render(); } main();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function main() {
  let view = new View('<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>');
  let page = Page({
    data: {
      pageShow: 'pageshow',
      pageData: 'pageData',
      pageShow1: 'pageShow1'
    },
    onLoad: function () {
      this.setData({
        pageShow: '我是pageShow啊'
      });
    }
  });
 
  page.setView(view);
  page.render();
}
 
main();

于是,我们可以看到页面的变化,由开始的初始化页面到执行onLoad时候的变化:

新京葡娱乐场网址 23

新京葡娱乐场网址 24

这里是最终完整的代码:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script type="text/javascript" src="libs/zepto.js" ></script> <script type="text/javascript"> //这个为js罗杰部分实现,后续会释放工厂方法 class PageClass { //构造函数,传入对象 constructor(opts) { //必须拥有的参数 this.data = {}; Object.assign(this, opts); } //核心方法,每个Page对象需要一个模板实例 setView(view) { this.view = view; } //核心方法,设置数据后会引发页面刷新 setData(data) { Object.assign(this.data, data); //只影响改变的数据 this.view.reRender(data, this.data) } render() { this.view.setInitData(this.data); this.view.render(); if(this.onLoad) this.onLoad(); } } //View为模块的实现,主要用于解析目标生产node class View { constructor(template) { this.template = template; //由控制器page传入的初始数据或者setData产生的数据 this.data = {}; this.labelMap = { 'view': 'div', '#text': 'span' }; this.nodes = {}; this.root = {}; } setInitData(data) { this.data = data; } //数据便会引起的重新渲染 reRender(data, allData) { this.data = allData; let k, v, i, len, j, len2, v2; //开始重新渲染逻辑,寻找所有保存了的node for(k in data) { if(!this.nodes[k]) continue; for(i = 0, len = this.nodes[k].length; i < len; i ) { for(j = 0; j < this.nodes[k][i].length; j ) { v = this.nodes[k][i][j]; if(v.type === 'text') { v.node.innerText = data[k]; } else if(v.type === 'attr') { v.node.setAttribute(v.name, data[k]); } } } } } /* 传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变 并且将其中包含{{}}标志的节点信息记录下来 */ _handlerNode (node) { let reg = /{{([sS] ?)}}/; let result, name, value, n, map = {}; let attrs , i, len, attr; name = node.nodeName; attrs = node.attributes; value = node.nodeValue; n = document.createElement(this.labelMap[name.toLowerCase()] || name); //说明是文本,需要记录下来了 if(node.nodeType === 3) { n.innerText = this.data[value] || ''; result = reg.exec(value); if(result) { n.innerText = this.data[result[1]] || ''; if(!map[result[1]]) map[result[新京葡娱乐场网址 ,1]] = []; map[result[1]].push({ type: 'text', node: n }); } } if(attrs) { //这里暂时只处理属性和值两种情况,多了就复杂10倍了 for (i = 0, len = attrs.length; i < len; i ) { attr = attrs[i]; result = reg.exec(attr.value); n.setAttribute(attr.name, attr.value); //如果有node需要处理则需要存下来标志 if (result) { n.setAttribute(attr.name, this.data[result[1]] || ''); //存储所有会用到的节点,以便后面动态更新 if (!map[result[1]]) map[result[1]] = []; map[result[1]].push({ type: 'attr', name: attr.name, node: n }); } } } return { node: n, map: map } } //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止 _runAllNode(node, map, root) { let nodeInfo = this._handlerNode(node); let _map = nodeInfo.map; let n = nodeInfo.node; let k, i, len, children = node.childNodes; //先将该根节点插入到上一个节点中 root.appendChild(n); //处理map数据,这里的map是根对象,最初的map for(k in _map) { if(!map[k]) map[k] = []; map[k].push(_map[k]); } for(i = 0, len = children.length; i < len; i ) { this._runAllNode(children[i], map, n); } } //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录 splitTemplate () { let nodes = $(this.template); let map = {}, root = document.createElement('div'); let i, len; for(i = 0, len = nodes.length; i < len; i ) { this._runAllNode(nodes[i], map, root); } this.nodes = map; this.root = root; } render() { let i, len; this.splitTemplate(); for(i = 0, len = this.root.childNodes.length; i< len; i ) document.body.appendChild(this.root.childNodes[0]); } } function Page (data) { let page = new PageClass(data); return page; } function main() { let view = new View('<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>'); let page = Page({ data: { pageShow: 'pageshow', pageData: 'pageData', pageShow1: 'pageShow1' }, onLoad: function () { this.setData({ pageShow: '我是pageShow啊' }); } }); page.setView(view); page.render(); } main(); </script> </body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
 
<script type="text/javascript" src="libs/zepto.js" ></script>
<script type="text/javascript">
 
//这个为js罗杰部分实现,后续会释放工厂方法
class PageClass {
  //构造函数,传入对象
  constructor(opts) {
 
    //必须拥有的参数
    this.data = {};
    Object.assign(this, opts);
  }
 
  //核心方法,每个Page对象需要一个模板实例
  setView(view) {
    this.view = view;
  }
 
  //核心方法,设置数据后会引发页面刷新
  setData(data) {
    Object.assign(this.data, data);
 
    //只影响改变的数据
    this.view.reRender(data, this.data)
  }
 
  render() {
    this.view.setInitData(this.data);
    this.view.render();
 
    if(this.onLoad) this.onLoad();
  }
 
}
 
//View为模块的实现,主要用于解析目标生产node
class View {
  constructor(template) {
    this.template = template;
 
    //由控制器page传入的初始数据或者setData产生的数据
    this.data = {};
 
    this.labelMap = {
      'view': 'div',
      '#text': 'span'
    };
 
    this.nodes = {};
    this.root = {};
  }
 
  setInitData(data) {
    this.data = data;
  }
 
  //数据便会引起的重新渲染
  reRender(data, allData) {
    this.data = allData;
    let k, v, i, len, j, len2, v2;
 
    //开始重新渲染逻辑,寻找所有保存了的node
    for(k in data) {
      if(!this.nodes[k]) continue;
      for(i = 0, len = this.nodes[k].length; i < len; i ) {
        for(j = 0; j < this.nodes[k][i].length; j ) {
          v = this.nodes[k][i][j];
          if(v.type === 'text') {
            v.node.innerText = data[k];
          } else if(v.type === 'attr') {
            v.node.setAttribute(v.name, data[k]);
          }
        }
      }
    }
  }
  /*
    传入一个节点,解析出一个节点,并且将节点中的数据以初始化数据改变
    并且将其中包含{{}}标志的节点信息记录下来
  */
  _handlerNode (node) {
 
    let reg = /{{([sS] ?)}}/;
    let result, name, value, n, map = {};
    let attrs , i, len, attr;
 
    name = node.nodeName;
    attrs = node.attributes;
    value = node.nodeValue;
    n = document.createElement(this.labelMap[name.toLowerCase()] || name);
 
    //说明是文本,需要记录下来了
    if(node.nodeType === 3) {
      n.innerText =  this.data[value] || '';
 
      result =  reg.exec(value);
      if(result) {
        n.innerText =  this.data[result[1]] || '';
 
        if(!map[result[1]]) map[result[1]] = [];
        map[result[1]].push({
          type: 'text',
          node: n
        });
      }
    }
 
    if(attrs) {
      //这里暂时只处理属性和值两种情况,多了就复杂10倍了
      for (i = 0, len = attrs.length; i < len; i ) {
        attr = attrs[i];
        result = reg.exec(attr.value);
 
        n.setAttribute(attr.name, attr.value);
        //如果有node需要处理则需要存下来标志
        if (result) {
          n.setAttribute(attr.name, this.data[result[1]] || '');
 
          //存储所有会用到的节点,以便后面动态更新
          if (!map[result[1]]) map[result[1]] = [];
          map[result[1]].push({
            type: 'attr',
            name: attr.name,
            node: n
          });
 
        }
      }
    }
 
    return {
      node: n,
      map: map
    }
 
  }
 
  //遍历一个节点的所有子节点,如果有子节点继续遍历到没有为止
  _runAllNode(node, map, root) {
 
    let nodeInfo = this._handlerNode(node);
    let _map = nodeInfo.map;
    let n = nodeInfo.node;
    let k, i, len, children = node.childNodes;
 
    //先将该根节点插入到上一个节点中
    root.appendChild(n);
 
    //处理map数据,这里的map是根对象,最初的map
    for(k in _map) {
      if(!map[k]) map[k] = [];
      map[k].push(_map[k]);
    }
 
    for(i = 0, len = children.length; i < len; i ) {
      this._runAllNode(children[i], map, n);
    }
 
  }
 
  //处理每个节点,翻译为页面识别的节点,并且将需要操作的节点记录
  splitTemplate () {
    let nodes = $(this.template);
    let map = {}, root = document.createElement('div');
    let i, len;
 
    for(i = 0, len = nodes.length; i < len; i ) {
      this._runAllNode(nodes[i], map, root);
    }
 
    this.nodes = map;
    this.root = root;
  }
 
  render() {
    let i, len;
    this.splitTemplate();
    for(i = 0, len = this.root.childNodes.length; i< len; i )
      document.body.appendChild(this.root.childNodes[0]);
  }
 
}
 
function Page (data) {
  let page = new PageClass(data);
  return page;
}
 
function main() {
  let view = new View('<view>{{pageShow}}</view><view class="ddd" is-show="{{pageShow}}" >{{pageShow}}<view class="c1">{{pageData}}</view></view>');
  let page = Page({
    data: {
      pageShow: 'pageshow',
      pageData: 'pageData',
      pageShow1: 'pageShow1'
    },
    onLoad: function () {
      this.setData({
        pageShow: '我是pageShow啊'
      });
    }
  });
 
  page.setView(view);
  page.render();
}
 
main();
 
</script>
</body>
</html>

我们简单的模拟便先到此结束,这里结束的比较仓促有一些原因:

① 这段代码可以是最终打包构建形成的代码,但是我这里的完成度只有百分之一,后续需要大量的构建相关介入

② 这篇文章目的还是接受开发基础,而本章模拟实现太过复杂,如果篇幅大了会主旨不清

③ 这个是最重要的点,我一时也写不出来啊!!!,所以各位等下个长篇,小程序前端框架模拟实现吧

④ 如果继续实现,这里马上要遇到组件处理、事件模型、分文件构建等高端知识,时间会拉得很长

所以我们继续下章吧……

本文由67677新澳门手机版发布于新京葡娱乐场网址,转载请注明出处:三个事务页面包车型大巴产生,Wechat小程序项目

关键词: