offer收割之移动端

1,167 阅读13分钟

1.说说小程序的三层架构

三层架构就是为了符合“高内聚,低耦合”思想,把各个功能模块划分为表示层(UI)、业务逻辑层(BLL)和数据访问层(DAL)三层架构。 表示层(UI):位于三层构架的最上层,与用户直接接触,表示层就是实现用户的界面功能,也是系统数据的输入与输出,是为用户传达和反馈信息的。

业务逻辑层(BLL):是针对具体的问题的操作,也可以理解成对数据层的操作,对数据业务逻辑处理。同时也是表示层与数据层的桥梁,实现三层之间的数据连接和指令传达。

数据访问层(DAL):有时候也称为是持久层,主要功能是对原始数据(数据库或者文本文件等存放数据的形式)的操作(实现数据的增加、删除、修改、查询等)。具体为业务逻辑层或表示层提供数据服务。

2. 微信小程序的架构以及为什么要用到双线程

微信小程序的架构

微信小程序视图层是WebView,逻辑层是JS引擎。三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的

运行环境

  • Android
  • IOS
  • 小程序开发工具

逻辑层

  • V8
  • JavaScriptCore
  • NWJS

渲染层

  • Chromium定制内核
  • WKWebview
  • Chrome Webview

双线程模型

小程序的渲染层和逻辑层分别由2个线程管理:

  • 渲染层: 界面渲染相关的任务全都在WebView里执行。一个小程序存在多个界面,所以渲染层存在多个WebView线程。
  • 逻辑层:采用JsCore线程运行JS脚本。 视图层和逻辑层通过系统层的WeixinJsBridge进行通信:逻辑层把数据变化通知到视图层,触发视图层页面更新,视图层把触发的事件通知到逻辑层进行业务处理。

页面渲染的具体流程是: 在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的DOM树上,渲染出正确的UI界面。

双线程模型是小程序框架与页面大多数前端Web框架不同之处。基于这个模型,可以更好地管控以及提供更安全的环境。缺点是带来了无处不在的异步问题(任何数据传递都是线程间的通信,也就是都会有一定的延时),不过小程序在框架层面已经封装好了异步带来的时序问题。

微信小程序的组件系统

小程序是有自己的组件的,这些基本组件就是基于Exparser 框架。Exparser 基于 WebComponentsShadowDOM模型,但是不依赖浏览器的原生支持,而且可以在纯JS环境中运行。

Exparser内置在小程序基础库中,为小程序的各种组件提供基础的支持。小程序内的所有组件,包括内置组件和自定义组件,Exparser会维护整个页面的节点树相关信息,包括节点的熟悉、事件绑定等,相当于一个简化的Shadow DOM实现。

原生组件

在内置组件中,有一些组件并不完全在Exparser的渲染体系下,而是由客户端原生参与组件的渲染。比如说Map组件。它渲染的层级比在WebView层渲染的普通组件要高。

运行机制

1.启动
  • 热启动:假如用户已经打开某小程序,然后在一定时间内再次打开小程序,此时无需重新启动,只需将后台小程序切换到前台,这个过程就是热启动;
  • 冷启动:用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动,即冷启动。
2.销毁

只有当小程序进入后台一定时间(不知道是多久),或者系统资源占用过高,才会被真正的销毁。

5. 更新机制

开发者在后台发布新版本之后,无法立刻影响到所有现网用户,但最差情况下,也在发布之后24小时之内下发新版本信息到用户。

小程序每次冷启动时,都会检查是否有更新版本,如果发现有新版本,将会异步下载最新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。

所以如果想让用户使用最新版本的小程序,可以利用wx.getUpateManager做个检查更新的功能:

checkNewVersion() {
  const updateManager = wx.getUpdateManager()
  updateManager.onCheckForUpdate(res => {
    console.log('hasUpdate', res.hasUpadate) // 请求完新版本信息的回调
    if (res.hasUpdate) {
      updateManager.onUpdateReady(() => {
        this.setData({
          hasNewVersion: true
        })
      })
    }
  })
}

小程序为什么使用双线程

小程序的渲染层和逻辑层分别由两个线程管理:渲染层的界面使用WebView进行渲染;逻辑层采用JSCore运行JavaScript代码。一个小程序存在多个界面,所以渲染层存在多个WebView。这两个线程间的通信经由小程序Native侧中转,逻辑层发送网络请求也经由Native侧转发。

image.png

小程序架构设计时,要求渲染快,加载快,渲染页面的技术主要分为三种:

  1. 纯客户端原生技术渲染(纯客户端技术需要与微信代码一起编包,跟随微信发布版本,这样的开发节奏不好,较难控制);
  2. web技术渲染(纯web技术,那么一些复杂交互页面可能面临一些性能问题,因为Web技术中,UIJS的脚本在单线程中,这样容易导致逻辑任务抢占UI渲染资源。)
  3. 介于客户端技术和Web技术之间(Hybrid技术)。

但由于小程序是多WebView的架构,所以每一个页面都是不同的WebView渲染显示,所以单独创建了一个线程去执行JS,也就是逻辑层,而界面渲染的任务都在WebView线程里执行(渲染层)。即双线程模型,将逻辑层与视图层进行分离,视图层和逻辑层之间只有数据的通信,可以防止开发者随意操作界面,更好的保证用户的数据安全

小程序拆分逻辑层和渲染层有什么优势和劣势

双线程模型是小程序框架与页面大多数前端Web框架不同之处。基于这个模型,可以更好地管控以及提供更安全的环境。缺点是带来了无处不在的异步问题(任何数据传递都是线程间的通信,也就是都会有一定的延时),不过小程序在框架层面已经封装好了异步带来的时序问题。

小程序框架和普通H5框架的区别

1.H5存在的问题就是性能,依赖于手机浏览器的GPU计算和渲染 2.小程序是依托于APP的所以性能比H5要好,具有加载快,开发简单的特点

小程序在安卓和iOS上的区别

基本没区别,框架都做好了.

为什么要用Taro?

之所以选用 Taro,解决微信小程序原生开发的痛点是一方面,另一方面团队也有多端统一开发的诉求,Taro 无疑是当时支持最好的。另外 React 也符合个人及团队的整体技术栈,可显著降低团队学习成本。

目前Taro已经支持所有的小程序,H5以及RN

Taro是如何实现跨端的?

1.内置环境变量

process.env.TARO_ENV 用于判断当前编译类型,目前有 weapp / swan / alipay / h5 / rn / tt / qq / quickapp 八个取值,可以通过这个变量来书写对应一些不同环境下的代码,在编译时会将不属于当前编译类型的代码去掉,只保留当前编译类型下的代码,例如想在微信小程序和 H5 端分别引用不同资源

这个实现方案,使用过webpack的开发者比较熟悉,实现原理是 使用webpack.DefinePlugin插件 注入到webpack中,在webpack 编译过程中启用 Tree-Shaking 来过滤掉 兼容平台的使用不到的代码。那 Taro 是在哪里处理的呢,我们来看下Taro的源码

  1. 首先我们使用 taro-cli 提供的初始化项目之后,它在package.json 里提供多种平台的编译方式
"scripts": {
    "build:swan": "taro build --type swan",
    "build:weapp": "taro build --type weapp",
    "build:alipay": "taro build --type alipay",
	...
  },
复制代码

可以看到 在 scripts 运行的时候 使用 --type 传入的 TARO_ENV 的值 vscode 打开 Taro 源码中next 分支

// packages/taro-cli/src/cli.ts
customCommand('build', kernel, {
            _: args._,
            platform,
            plugin,
            isWatch: Boolean(args.watch),
            ...
          })
复制代码

taro-cli 将命令行传入的type 使用platform 变量传入给Kernel 处理,Kernel 位于taro-service子包,作为基础服务提供实时的编译工作, Taro 的核心就是 利用 Kernel + 注册插件 + 生命周期钩子函数 的实现方式,灵活的实现了各个不同的命令组合

  1. Kernel 调用 mini-runner 进行build 构建, 将platform 传给 buildAdapter 处理

  2. taro-mini-runner 中执行 build.config.ts中build方法, 在build里利用 export const getDefinePlugin = pipe(mergeOption, listify, partial(getPlugin, webpack.DefinePlugin)) 引入webpack.DefinePlugin

将 buildAdapter 作为env.TARO_ENV 传入到 webpack.DefinePlugin 之后利用 webpack 进行打包,做差异性处理

2.样式的条件编译

以上 webpack.DefinePlugin 可以针对 ts/js 代码进行 Tree-shaking 。

  • 样式处理上,对于RN的样式处理直接替换整个 css代码
当在 JS 文件中引用样式文件: `import './index.scss'`  时,RN 平台会找到并引入  `index.rn.scss` ,其他平台会引入: `index.scss` ,方便大家书写跨端样式,更好地兼容 RN。
复制代码

这里也很好理解,对于RN的替换引入文件的方式,如果我们作为Taro开发者, 在webpack 的 loader 插件中判断方法 对应的scss 文件有无 以 .rn.scss 文件,直接改变引入即可, taro 里是在哪里实现这些操作呢?

  1. 定义css 后缀文件
export const CSS_EXT: string[] = ['.css', '.scss', '.sass', '.less', '.styl', '.stylus', '.wxss', '.acss']
  1. 判断编译平台,优先选择对应平台的 style 文件
  • 对于 单个样式文件里使用 不同平台的兼容性样式 无法处理, Taro 这里引入了 条件编译的方式进行 处理, 处理方式如下:
/*  #ifdef  %PLATFORM%  */
样式代码
/*  #endif  */
/*  #ifndef  %PLATFORM%  */
样式代码
/*  #endif  */

总结

  • 优点 简单易懂,对于前端开发者比较亲切,都是比较传统的概念,易于理解
  • 缺点
  1. 在ts/js代码中会有大量 if/else 充斥其中,后期变得维护困难
  2. 遇到 条件使用 外部npm 包的时候需要是用到 require, 无法使用import, 对于tree-shaking会失效(tree-shaking的消除原理是依赖于ES6的模块特性)

Taro3和Taro2的区别

正如官网所说当前 Taro 已进入 3.x 时代,相较于 Taro 1/2 编译时架构,Taro 3 采用了重运行时的架构,让开发者可以获得完整的 React / Vue 等框架的开发体验

Taro的优化方案

Taro的优化主要针对项目的编译打包,为了优化Taro的编译打包,我们需要了解Taro内置的Webpack的配置,然后使用webpack-chain提供的方法链式修改配置。接下来,我们还需要解决分包过大无法进行二维码预览的问题。

需要注意的是在开发和生产环境下,内置的webpack配置是有差别的,比如在生产环境下,才会调用terser-webpack-plugin进行文件压缩处理。

需要注意的是,Taro打包用到了webpack-chain机制。webpack配置本质是一个对象,创建修改比较麻烦,webpack-chain就是提供链式的 API 来创建和修改webpack 配置

为此,我们引入了speed-measure-webpack-plugin,该插件可以统计出编译打包过程中,plugin和loader的耗时情况,可以帮助我们明确优化方向。(性能定位)

之后可以优化plugin和loader

  • 引入thread-loader开启多线程打包
  • cache-loader 引入缓存loader
  • 使用压缩的plugin

JSBridge

一、什么是 JSBridge

  • JS 无法直接调用 native API
  • 需要通过一些特定的“格式”来调用
  • 这些“格式”就统称为 JS-Bridge,例如 微信 JSSDK

native 提供的一些能力,JSBridge 进行封装,JavaScript 进行调用

JS-Bridge 的核心是构建 Native 和 非Native 之间通信的通道,而且是双向通道

双向通道:

  • JS 向 Native 发送消息:调用 Native 的功能,通知 Native 当前 JS 的相关状态
  • Native 向 JS 发送消息:回溯调用结果、消息推送、通知 Native 当前 JS 的相关状态

Taro设计稿及尺寸单位

Taro 默认会对所有单位进行转换。在 Taro 中书写尺寸按照 1:1 的关系来进行书写,即从设计稿上量的长度 100px,那么尺寸书写就是 100px,当转成微信小程序的时候,尺寸将默认转换为 100rpx,当转成 H5 时将默认转换为以 rem 为单位的值

Taro 默认以 750px 作为换算尺寸标准,如果设计稿不是以 750px 为标准,则需要在项目配置 config/index.js 中进行设置,例如设计稿尺寸是 640px,则需要修改项目配置 config/index.js 中的 designWidth 配置为 640

小程序的生命周期

小程序的生命周期分为应用生命周期和页面生命周期

一:应用生命周期

1:用户首次打开小程序,触发 onLaunch(全局只触发一次)。 :

2:小程序初始化完成后,触发onShow方法,监听小程序显示。

3:小程序从前台进入后台,触发 onHide方法。:

4:小程序从后台进入前台显示,触发 onShow方法。

5:小程序后台运行一定时间,或系统资源占用过高,会被销毁。

前台、后台定义: 当用户点击左上角关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。

二:页面生命周期

1:小程序注册完成后,加载页面,触发onLoad方法。

2:页面载入后触发onShow方法,显示页面。

3:首次显示页面,会触发onReady方法,渲染页面元素和样式,一个页面只会调用一次。

4:当小程序后台运行或跳转到其他页面(使用wx.navigateTo)时,触发onHide方法。

5:当小程序有后台进入到前台运行或重新进入页面时,触发onShow方法。

6:当使用重定向方法wx.redirectTo(OBJECT)或关闭当前页返回上一页wx.navigateBack(),触发onUnload