浅看小程序

4,523 阅读8分钟

2017年1月份小程序正式登场,我也和社团小伙伴们一起做了一些小程序,也有一些经验的积累。小程序本身以便捷轻量、贴近native使用体验为核心,成为了微信新的流量领地。从wepy到mpvue,也有多种小程序开发框架孕育而生,这些框架的诞生也意味着,微信小程序本身的开发体验并不能满足开发者的实际开发需求。本文也不是去介绍如何高效开发,亦或是去介绍这些框架,只是记录一下自己肉眼可见的对小程序的探索(要是有不完善的地方求指出)。

小程序你好!

  1. 从目录结构来看:
  • index.js
  • index.json
  • index.wxml
  • index.wxss

注定这是一个为前端开发者所设计的开发flow,wxml对于html,wxss对于css。

  1. 从调试器来看

也代表了这是一个前端项目,唯一肉眼可见的不同之处是标签名不同。

从开发的角度推测小程序的架构

  • UI层、逻辑层分离

小程序总体分为逻辑层和视图层,也就是说小程序框架本身就将逻辑层和视图层分开来了,而不是纯web开发,视图层和逻辑层混合,Vue和React之类的框架实际上也是做的这样的事情,视图层只需要去关心UI视图,逻辑层去处理数据以及用户的操作状态等等。

微信为什么要这么干呢?引用开发指南中的话:

  • 快速的加载
  • 更强大的能力
  • 原生的体验
  • 易用且安全的微信数据开放
  • 高效和简单的开发

在小程序之前,微信就提供过JS SDK去帮助开发者更高效的开发接近原生体验的应用,但是光凭SDK无法彻底解决静态资源离线、UI线程阻塞、页面切换卡顿等问题。

产生这些问题的原因大多是因为浏览器本身的限制,也就是UI线程和JS线程冲突,React 16中的Fiber调度也在一方面是解决此类的问题。孕育而生的就是小程序中的双线程模型,将逻辑层和视图层分离。

我们常常在开发小程序的过程中会遇到,模拟器正常但是真机不正常的情况,主要是因为下面这个表格:

  • 组件化系统

    • 小程序的组件化之路

    现代前端开发,已经进入了组件化开发的时代,无论是三大框架还是新Web标准中的Web Componnents,组件化会大大提高开发效率。小程序也是在后面的更新中才提出了利用Component构造器去实现组件化,在此之前,在小程序中是通过template模板渲染来实现组件化的。

    那么有了template还为什么要引入Component呢?

    1. 模板无法维护自身的状态。模板本身的数据需要通过页面传入data去渲染,这就让数据维护变得很复杂,无法实现真正的组件化,虽然我们在用三大框架开发的时候也会提出Container的概念,Container所做的是数据注入,但是模板不仅仅是被数据注入,哪怕是最基本的状态也需要被注入,wepy的组件化就是基于template的,所以wepy无法解决循环组件公用状态的问题,也就是一旦一个子组件的状态发生改变,另一个相同的子组件的状态也会发生改变;
    2. 模板没有自身的生命周期,熟悉三大框架的同学自然知道组件生命周期的重要性,我们可以完全控制组件在什么阶段应该做什么事,这对于异步数据处理是必要的,举个例子:基于template的wepy,当组件层数较深,渲染需要依赖异步数据的时候,我们无法知道什么时候存在有效数据的,甚至会因为异步数据是undefined而出现报错,tempalte的渲染是在渲染层,是和主页面渲染同步进行的。

    小程序开发团队可能注意到了这一点,于是引入了Component构造器,从小程序层面支持了真正的组件化开发,无论是properties还是onReady又或者是behaviors,都解决了上述的痛点。

    • 小程序组件化的实现

    Web开发中,类似Vue之类的框架是通过VDOM或者DocumentFragment之类的去作为Component构造函数的render模板。然后每一个Component类去维护自己的状态、数据等等。

    这都不是我们这里的重点,因为相比于这些render模式,小程序本身并不能去通过Javascript直接去控制渲染层的,而且小程序在渲染层的模式是:wxml + wxss。

    那么小程序是怎么实现组件化呢?

    我们在开发者工具里来看一下,当我渲染一个组件的时候,会如何渲染呢?

    我创建了一个List-Item组件,并在index页面引入,我们在开发者工具看下小程序是怎么渲染的:

    显而易见的,#shadow-root首先进入我的视线范围,这是个什么东东呢?查阅一下相关资料,了解到,一个重要的概念——Shadow DOM,现在它已经进入了新版Shadow DOM v1 规范,从属于Web Components,顺手大胆的预测一下,Web开发的组件化最终可能是由Web Component来实现的。因为Web Component已经支持局部作用域、slot插槽等等现有框架所提供的组件化方法,学习Web Component也是我接下来自己学习的目标之一,应该也会写博客来记录一下。

    微信开发指南中对组件化系统的实现是这么说的:

    Exparser的组件模型与WebComponents标准中的ShadowDOM高度相似。Exparser会维护整个页面的节点树相关信息,包括节点的属性、事件绑定等,相当于一个简化版的Shadow DOM实现。

    也就是说其实小程序的组件就是模仿ShadowDOM,从而实现组件化。

从Source来看小程序架构

在web开发中,我们通常可以从Source panel中看到当前正在执行的脚本文件,小程序开发者工具同样可以,指南中是这样说的:

Sources panel 用于显示当前项目的脚本文件,同浏览器开发不同,微信小程序框架会对脚本文件进行编译的工作,所以在 Sources panel 中开发者看到的文件是经过处理之后的脚本文件,开发者的代码都会被包裹在 define 函数中,并且对于 Page 代码,在尾部会有 require 的主动调用。

我们看下截图:

当开启ES6转ES5的开关之后,每个js文件会生成一个同名的[name].js[sm],这个文件是转换前的初始文件,而[name].js就被替换成了ES5版本的代码。

进一步的,我们看到这个应用运行在本地51516端口,这个端口其实是每次重启开发者工具,随机分配的一个闲置端口。我们在浏览器中打开127.0.0.1:51516,可以看到:

简单分析一下这个html文件。

  • 打开第一个script标签,显而易见,小程序的前端代码也是通过webpack打包的;
  • 在这个script中,定义了的全局变量包括路由、当前文件、Component的构造函数、插件等等。我们顺手在控制台打印一下此时的window对象:

    可以发现,此时window对象上已经被挂载了很多额外的属性,这也就使得我们去直接调用wx、Page、Component等方法成为了可能,与此同时,我们点开WeixinJSBridge来看:

    这个JSBridge主要承担的责任就是去连接JS Core和Native层,是一个典型的事件注册、监听、发布、订阅的行为。

从这段代码可以看出几点:

  1. 小程序的渲染层确实是基于html的,也就是说wxml最终转化成的就是html,这个非常类似于VDom的另一种表达形式,只是进一步的,;
  2. 这个__wxConfig是将所有页面对应的json文件以及默认配置取了并集,然后生成的一个配置对象。
  • 这个__devtoolsConfig显而易见得是用户信息以及wx这个对象上所挂载的去调用Native层服务的方法,以及需要授权时的提示信息等,比较让人意外的是,我在清除所有授权之后,这里仍然能看到我的openid以及个人敏感信息,但是在实际开发中这些敏感信息其实大多都是通过前端获取code,然后从后端解密获取的,所以这一块是否存在安全问题,还需要进一步的研究。

  • 这段代码可以看出,一开始定义的__wxRouteBegain这个变量就是去检测页面加载是否成功的。

总结

在开发小程序的过程中,可能会因为很多限制因素,而导致开发体验并不是非常友好,比如复杂的文件结构,明明可以官方支持通过loader去做成类似Vue的template的开发模式又或者是jsx之类,更贴近现在前端开发的体验;更比如说逻辑层和渲染层的分离导致渲染延迟,无法有效操作dom节点之类的问题。

但是,个人觉得,有机会还是可以深入小程序底层实现,明白它为什么要这么设计,也可以帮助我们成长。相信小程序也会变得越来越好,更符合开发直觉~~~