mPaaS 小程序运行原理及优化

小程序的由来

在小程序没有出来之前,最初一些巨型App的webview成为移动web重要入口,这些厂商发布一整套网页开发工具包,称之为JS-SDK,给所有的web开发者打开一扇全新的窗户,让所有开发者都可以使用到这些大型App的原生能力,去完成一些之前做不到或者难以做到的事情。但JS-SDK的模式并没有解决使用移动网页遇到的体验不良的问题,比如受限于设备性能和网络速度,会出现白屏的可能。因此又设计了一个增强版的JS-SDK,主要是提供了资源离线存储,但是在复杂的页面上依然会出现白屏的问题,表现在页面切换的生硬和点击的迟滞感。这时候就需要一个JS-SDK所处理不了的,使用户体验更好的一个系统,小程序就应运而生了。

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

小程序的开发同普通的网页开发相比有很大的相似性,小程序的主要开发语言也是 JavaScript,但是二者还是有些差别的。

  • 普通网页开发可以使用各种浏览器提供的 DOM API,进行 DOM 操作,小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOMAPI。
  • 普通网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。
  • 网页开发者在开发网页的时候,只需要使用到浏览器,并且搭配上一些辅助工具或者编辑器即可。小程序的开发则有所不同,需要经过申请小程序帐号、安装小程序开发者工具、配置项目等等过程方可完成。

小程序运行机制

启动流程

小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台状态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被宿主APP主动销毁后再次打开的情况,此时小程序需要重新加载启动。

启动完整流程如下图所示:

小程序没有重启的概念,当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后,会被宿主App主动销毁。

双线程架构

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

类似于JSSDK 这样的 Hybrid 技术,小程序的界面主要由成熟的 Web 技术渲染,辅之以大量的接口提供丰富的客户端原生能力。同时,每个小程序页面都是用不同的 WebView 去渲染,这样可以提供更好的交互体验,更贴近原生体验,也避免了单个 WebView 的任务过于繁重。此外,界面渲染这一块我们定义了一套内置组件以统一体验,并且提供一些基础和通用的能力,进一步降低开发者的学习门槛。值得一提的是,内置组件有一部分较复杂组件是用客户端原生实现的同层渲染,以提供更好的性能。

为什么要设计双线程架构

为了管控和安全,小程序阻止开发者使用一些浏览器提供的,诸如跳转页面、操作 DOM、动态执行脚本的开放性接口。将逻辑层与视图层进行分离,视图层和逻辑层之间只有数据的通信,可以防止开发者随意操作界面,更好的保证了用户数据安全。

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

运行环境 逻辑层 渲染层
Android V8 Chromium定制内核
iOS JavaScriptCore WKWebView
小程序开发者工具 NWJS Chrome WebView

我们看一下单 WebView 实例与小程序双线程多实例下代码执行的差异点。

单 WebView 模式下,Page 视图与 App 逻辑共享同一个 JSContext,这样所有的页面可以共享全局的数据和方法,能够实现全局的状态管理。多 WebView 模式下,每一个 WebView 都有一个独立的 JSContext,虽然可以通过窗口通信实现数据传递,但是无法共享数据和方法,对于全局的状态管理也相对比较复杂,抽离一个通用的 WebView 或者 JS Engine 作为应用的 JSContext 就可以解决这些问题,但是同时引入了其他问题:视图和逻辑如何通信,在小程序里面数据更新后视图是异步更新的。

渲染一个hello world页面

mPaaS 小程序开发在语法方面与传统的前端网页开发非常类似,开发者主要编写 .axml、.acss、.js三部分文件,分别对标前端开发中的HTML、CSS、JS。

page 由四个文件组成,分别是:

文件类型 必填 作用
js 页面逻辑
axml 页面结构
acss 页面样式表
json 页面配置

其中 .axml 的内容如下所示,AXML 是小程序框架设计的一套标签语言,用于描述小程序页面的结构。.js 的内容用于实现小程序的业务逻辑。.acss 的内容如下所示,ACSS 是一套样式语言,用于描述 AXML 的组件样式,决定 AXML 的组件的显示效果。

javascript 复制代码
// index.axml
<view>{{ msg }}</view>

// index.js
Page({
  onLoad: function () {
    this.setData({ msg: 'Hello World' })
  }
})
// index.acss
view {
  padding-left: 10px;
}

AXML先转成JS对象,然后再渲染出真正的Dom树,回到"Hello World"那个例子,我们可以看到转换的过程。

通过setData把msg数据从"Hello World"变成"Goodbye",产生的JS对象对应的节点就会发生变化,此时可以对比前后两个JS对象得到变化的部分,然后把这个差异应用到原来的Dom树上,从而达到更新UI的目的,这就是"数据驱动"。

这一点和vue其实是一致的。

既然小程序是基于双线程模型,那就意味着任何数据传递都是线程间的通信,也就是都会有一定的延时。

一切都是异步。小程序采用多个webview渲染,更加接近原生App的用户体验。如果为单页面应用,单独打开一个页面,需要先卸载当前页面结构,并重新渲染。多页面应用,新页面直接滑动出来并且覆盖在旧页面上即可。这样用户体验非常好。

页面的载入是通过创建并插入webview 来实现的。mPaaS小程序做了限制,在mPaaS小程序中打开的页面不能超过10个,达到10个页面后,就不能再打开新的页面。所以我们在开发中,要避免路由嵌套太深。

代码打包

小程序启动时,客户端会从CDN下载小程序离线包,这个离线包是将原项目打包后的一个 .tar 文件,存放在 /data/data/com.eg.android.AlipayGphone/files/nebulaInstallApps 目录下。从真机上拖出来解压后得到目录。其中 index.html、index.js、index.worker.js就是之前我们编写的代码所编译出的js代码。其中index.worker.js是小程序所有页面的业务逻辑代码,对应着开发者编写的pageName.js中的内容;index.html、index.js中的内容对应着acss与axml,其中axml的组件信息、层次结构同样被会被编译成js代码,在运行时由这些js进行渲染。

通信原理

小程序逻辑层和渲染层的通信会由 Native (微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发。

视图层组件:

内置组件中有部分组件是利用到客户端原生提供的能力,既然需要客户端原生提供的能力,那就会涉及到视图层与客户端的交互通信。这层通信机制在 iOS 和安卓系统的实现方式并不一样,iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓则是往 WebView 的 window 对象注入一个原生方法,最终会封装成 XXXJSBridge 这样一个兼容层,主要提供了调用(invoke)和监听(on)这两种方法。

我们知道小程序逻辑层没有浏览器的 DOM/BOM,视图层的更新借助于 Virtual DOM。用 JS 对象模拟 DOM 树 -> 比较两棵虚拟 DOM 树的差异 -> 把差异应用到真正的 DOM 树上,状态更新的时候,通过对比前后 JS 对象变化,进而改变视图层的 Dom 树。实际上,在视图层与客户端的交互通信中,开发者只是间接调用的,真正调用是在组件的内部实现中。开发者插入一个原生组件,一般而言,组件运行的时候被插入到 DOM 树中,会调用客户端接口,通知客户端在哪个位置渲染一块原生界面。在后续开发者更新组件属性时,同样地,也会调用客户端提供的更新接口来更新原生界面的某些部分。

逻辑层接口:

逻辑层与客户端原生通信机制与渲染层类似,不同在于,iOS 平台可以往 JavaScripCore 框架注入一个全局的原生方法,而安卓方面则是跟渲染层一致的。

同样地,开发者也是间接地调用到与客户端原生通信的底层接口。一般我们会对逻辑层接口做层封装后才暴露给开发者,封装的细节可能是统一入参、做些参数校验、兼容各平台或版本问题等等。

小程序更新机制

小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。如果需要马上应用最新版本,可以使用 getUpdateManager API 进行处理。

mPaaS这边没有实现getUpdateManager 这个API,我们这边自己提供了桥接方法支持了更新功能。

同层渲染

什么是同层渲染

众所周知,小程序当中有一类特殊的内置组件------原生组件,这类组件有别于 WebView 渲染的内置组件,他们是交由原生客户端渲染的。原生组件作为 Webview 的补充,为小程序带来了更丰富的特性和更高的性能,但同时由于脱离 Webview 渲染也给开发者带来了不小的困扰。在小程序引入「同层渲染」之前,原生组件的层级总是最高,不受 z-index 属性的控制,无法与 view、image 等内置组件相互覆盖,cover-view 和 cover-image 组件的出现一定程度上缓解了覆盖的问题,同时为了让原生组件能被嵌套在 swiper、scroll-view 等容器内,小程序在过去也推出了一些临时的解决方案。但随着小程序生态的发展,开发者对原生组件的使用场景不断扩大,原生组件的这些问题也日趋显现,为了彻底解决原生组件带来的种种限制,引入了「同层渲染」。

首先我们先来了解一下小程序原生组件的渲染原理。我们知道,小程序的内容大多是渲染在 WebView 上的,如果把 WebView 看成单独的一层,那么由系统自带的这些原生组件则位于另一个更高的层级。两个层级是完全独立的,因此无法简单地通过使用 z-index 控制原生组件和非原生组件之间的相对层级。正如下图所示,非原生组件位于 WebView 层,而原生组件及 cover-view 与 cover-image 则位于另一个较高的层级:

那么「同层渲染」顾名思义则是指通过一定的技术手段把原生组件直接渲染到 WebView 层级上,此时「原生组件层」已经不存在,原生组件此时已被直接挂载到 WebView 节点上。你几乎可以像使用非原生组件一样去使用「同层渲染」的原生组件,比如使用 view、image 覆盖原生组件、使用 z-index 指定原生组件的层级、把原生组件放置在 scroll-view、swiper、movable-view 等容器内,通过 AXSS 设置原生组件的样式等等。启用「同层渲染」之后的界面层级如下图所示:

同层渲染原理

iOS端

小程序在 iOS 端使用 WKWebView 进行渲染的,WKWebView 在内部采用的是分层的方式进行渲染,它会将 WebKit 内核生成的 Compositing Layer(合成层)渲染成 iOS 上的一个 WKCompositingView,这是一个客户端原生的 View,不过可惜的是,内核一般会将多个 DOM 节点渲染到一个 Compositing Layer 上,因此合成层与 DOM 节点之间不存在一对一的映射关系。不过我们发现,当把一个 DOM 节点的 CSS 属性设置为 overflow: scroll (低版本需同时设置 -webkit-overflow-scrolling: touch)之后,WKWebView 会为其生成一个 WKChildScrollView,与 DOM 节点存在映射关系,这是一个原生的 UIScrollView 的子类,也就是说 WebView 里的滚动实际上是由真正的原生滚动组件来承载的。WKWebView 这么做是为了可以让 iOS 上的 WebView 滚动有更流畅的体验。虽说 WKChildScrollView 也是原生组件,但 WebKit 内核已经处理了它与其他 DOM 节点之间的层级关系,因此你可以直接使用 AXSS 控制层级而不必担心遮挡的问题。

小程序 iOS 端的「同层渲染」也正是基于 WKChildScrollView 实现的,原生组件在 attached 之后会直接挂载到预先创建好的 WKChildScrollView 容器下,大致的流程如下:

  • 创建一个 DOM 节点并设置其 CSS 属性为 overflow: scroll 且 -webkit-overflow-scrolling: touch;
  • 通知客户端查找到该 DOM 节点对应的原生 WKChildScrollView 组件;
  • 将原生组件挂载到该 WKChildScrollView 节点上作为其子 View。

通过上述流程,小程序的原生组件就被插入到 WKChildScrollView 了,也即是在 步骤1 创建的那个 DOM 节点对应的原生 ScrollView 的子节点。此时,修改这个 DOM 节点的样式属性同样也会应用到原生组件上。因此,「同层渲染」的原生组件与普通的内置组件表现并无二致。

Android 端

小程序在 Android 端采用 chromium 作为 WebView 渲染层,与 iOS 不同的是,Android 端的 WebView 是单独进行渲染而不会在客户端生成类似 iOS 那样的 Compositing View (合成层),经渲染后的 WebView 是一个完整的视图,因此需要采用其他的方案来实现「同层渲染」。经过我们的调研发现,chromium 支持 WebPlugin 机制,WebPlugin 是浏览器内核的一个插件机制,主要用来解析和描述embed 标签。Android 端的同层渲染就是基于 embed 标签结合 chromium 内核扩展来实现的。

Android 端「同层渲染」的大致流程如下:

  • WebView 侧创建一个 embed DOM 节点并指定组件类型;
  • chromium 内核会创建一个 WebPlugin 实例,并生成一个 RenderLayer;
  • Android 客户端初始化一个对应的原生组件;
  • Android 客户端将原生组件的画面绘制到步骤2创建的 RenderLayer 所绑定的 SurfaceTexture 上;
  • 通知 chromium 内核渲染该 RenderLayer;
  • chromium 渲染该 embed 节点并上屏。

这样就实现了把一个原生组件渲染到 WebView 上,这个流程相当于给 WebView 添加了一个外置的插件,如果你有留意 Chrome 浏览器上的 pdf 预览,会发现实际上它也是基于 标签实现的。

这种方式可以用于 map、video、canvas、camera 等原生组件的渲染,对于 input 和 textarea,采用的方案是直接对 chromium 的组件进行扩展,来支持一些 WebView 本身不具备的能力。

对比 iOS 端的实现,Android 端的「同层渲染」真正将原生组件视图加到了 WebView 的渲染流程中且 embed 节点是真正的 DOM 节点,理论上可以将任意 WXSS 属性作用在该节点上。Android 端相对来说是更加彻底的「同层渲染」,但相应的重构成本也会更高一些。

性能优化

网络图片

网络请求图片过大

说明

网络请求图片过大会影响请求耗时,从而影响页面启动耗时,且消耗过多的网络流量,影响资源流耗。

最佳实践

  • 建议单张图片大小不超过 100KB。将图片格式转换 WebP 或者 SVG。WebP 和 SVG 格式的图片能够在不降低图片质量的前提下减小图片的体积。
  • 合理压缩、剪裁图片大小。根据图片实际需要对图片进行合理压缩和剪裁。
  • 大图建议从 CDN 渠道上传,且需要控制并发加载数量,并开启 HTTP 缓存,下次加载同样图片,直接从缓存读取。
  • 较小的图片建议放到源码包中,避免不必要的小图片的请求耗时影响整体启动耗时。在以往的性能治理案例中,出现过将几 KB 的图片放到线上且没有使用 CDN,导致对启动耗时影响了 10%,这样的情况应该避免。

图片并发请求过大

说明

短时间内发起太多图片请求会加重网络带宽压力,且并发请求太多也会导致图片加载慢。

最佳实践

  • 同域名耗时超过 300ms 的图片请求并发数不超过 6 个。
  • 首屏页面应降低业务的复杂度,减少图片请求的频率。
  • 图片开启 HTTP 缓存控制,下次加载同样图片,直接从缓存读取。
  • 未使用到的图片或者屏幕外的图片采用懒加载处理。

启动阶段图片请求数过多

说明

小程序启动阶段网络请求的图片过多,一方面会耗费网络资源,导致网络通道拥挤,网络处理时长增加,另一方面不断加载的图片会导致页面不断重排、重绘引起页面不断抖动,推迟首次可交互的时间。尤其是线上很多小程序实际用户使用场景是非 WiFi,容易出现网络不稳定的情况,比如健康码相关的小程序场景,因此此类场景下,如果图片请求过多会导致线上用户的启动耗时比较长。

最佳实践

  • 小程序启动阶段网络请求的图片小于 10 个。
  • 减少启动阶段的非必要图片请求。屏幕外的图片可以使用 懒加载,如果不确定,可以给所有图片都加上该属性,具体使用方法参考 加载屏幕外图片 里的最佳优化方案。
  • 对于较小的图片,建议放到代码包里,或者使用雪碧图来减少图片请求数,雪碧图的使用可查看 图片并发请求过大。
  • 图片开启 HTTP 缓存控制,下次加载同样图片,直接从缓存读取,从而减少非首次访问的启动耗时。

JSAPI接口

同步 JSAPI 调用次数过多

说明

同步的 JSAPI 虽然开发比较方便,但是会有很大的性能损耗。具体表现在同步 JSAPI 的调用过多将造成进程的阻塞,影响后续业务逻辑的执行,造成响应变慢,因此原则即是 能不用就不用,非要用要慎重 。优化经验中发现,getSystemInfoSync、getStorageSync、setStorageSync、getLocation、getCities 是同步调用的高发区。

最佳实践

  • 应避免过度使用 Sync 结尾的同步 API,同种 JSAPI 同步执行次数应不超过 5 次。
  • 在小程序初始化代码和启动相关的几个生命周期 app.onLaunch 、app.onShow、page.onLoad、page.onShow、page.onReady 中,应避免过度使用 Sync 结尾的同步 API,同步执行转化成异步执行。
  • 对于getSystemInfo、getSystemInfoSync 、getExtConfigSync、getExtConfig这类的 JSAPI,调用的结果应进行缓存,避免重复使用。比如通过 getSystemInfo 获取信息后可以用变量来做缓存,后续使用时直接使用变量即可。
  • 对于 getStorageSync、setStorageSync 同步 JSAPI 调用次数过多的情况:my.setStorageSyncmy.getStorageSync 设计目的是为给小程序提供类似于 前端 localStorage 即本地缓存的功能,并非是提供类似 Redux/Vuex 的全局状态管理的最佳实践。在开发过程中,建议合理使用这两个同步 JSAPI,简单的状态管理可以使用 App.globalData,复杂的使用 herculex 或跨端开发框架提供的状态管理。
  • 合并多个字段到一个对象,减少需要的次数。

JSAPI 重复调用次数检测

说明

JSAPI 重复调用指的是 JSAPI 名称和参数都相同的情况下,多次重复调用请求的情况,最常见的是多次网络请求 request 而其中 url 相同;首屏页面加载中,JSAPI 多次重复调用会增加无用耗时,影响小程序整体的启动耗时。

最佳实践

  • 同种 JSAPI 可重复调用次数应不超过 5 次。
  • 采用数据缓存方式处理前要执行 JSAPI 后的结果数据,避免重复调用,对于 getSystemInfo、getSystemInfoSync 获取信息后可以用变量来做缓存,后续使用时直接使用变量即可。
  • 不要把 storage 相关 API 作为状态管理工具,可以把信息存在 APP 实例上:
less 复制代码
App({
  someDataInAppLifecycle: {}, // 可以把应用运行时需要的信息放在App实例上
});
// 后续通过getApp() 读取实例存储值
  • 同步 JSAPI 的优化可查看 同步 JSAPI 调用次数过多。

JSAPI 并发检测

说明

JSAPI 不是免费的,当调用 JSAPI 时,底层的链路为:小程序调用 JSAPI > 通知客户端(Android、iOS 支付宝客户端)执行对应的逻辑 > x ms 之后执行完毕 > 把结果返回给小程序 > 小程序回调;因此 JSAPI 并发过高会导致客户端响应变慢、阻塞其他 JSAPI 的响应(可能会阻塞其他业务逻辑)、用户耗电增加、请求超时等问题(例如网络通信吞吐量有限的情况下,部分请求会阻塞、超时,甚至直接以失败返回,影响业务)。

最佳实践

  • JSAPI 并发次数应不超过 20 次/s。
  • 采用数据缓存方式处理前要执行 JSAPI 后的结果数据,减少调用次数。
  • 减少并发执行次数。比如使用排队等策略,保障高优先级的业务请求。
  • 同步 JSAPI 的优化可查看同步 JSAPI 调用次数过多。

setData

setData 数据量检测

说明

由 setData 底层原理可知,由于逻辑层和视图层通信时,数据需要序列化,并通过 evaluateJavascript 执行脚本,因此,setData 传递的数据量过大时,会影响首屏渲染性能。

最佳实践

  • 首屏页面单次 setData 调用,传递的字符串数据量应不超过 256KB。
  • 减少数据量,避免一次性 setData 传递过长的列表。
  • 分批处理。首屏请勿一次性构造太多节点,服务端可能一次请求传递大量数据,请勿一次性 setData,可先 setData 一部分数据,然后等待一段时间(比如 400ms,具体需要业务调节)再调用 $spliceData 将其他数据传输过去。

setData 调用次数检测

说明

页面 setData 调用次数不宜过多,否则会导致 JS 线程一直在执行编译和渲染,视图层和逻辑层数据交互阻塞,可能导致用户的事件操作传递到逻辑层不及时,同时逻辑层的处理结果也不能很快的传递到视图层,从而造成用户滑动时的卡顿和操作反馈延时。

最佳实践

  • 页面 setData 调用次数应不超过 20 次/s。
  • 页面级别或组件级别避免频繁触发 setData 或者 $spliceData。在分析的案例中,有些页面存在倒计时逻辑且过于频繁触发(ms 级别的触发)。
  • 需要频繁触发重新渲染时,避免使用页面级别的 setData 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> s p l i c e D a t a ,将这一块封装成自定义组件,然后使用组件级别的 s e t D a t a 或 spliceData, 将这一块封装成自定义组件,然后使用组件级别的 setData 或 </math>spliceData,将这一块封装成自定义组件,然后使用组件级别的setData或spliceData 触发组件重新渲染。
  • 使用 $batchedUpdates 合并多次 setData 请求为一次 setData,进而减少逻辑层到视图层的更新次数。注意:只会合并方法中同步的 setData 调用。

网络请求

请求耗时检测

说明

单次网络请求(如 http),从发起请求到数据返回的耗时,网络请求耗时过长将导致数据等待,延迟首屏初次渲染开始的时间,会让用户一直等待甚至离开。

最佳实践

  • 单次网络请求耗时时长不超过 400ms。
  • 数据缓存存储网络请求结果。支付宝提供 my.setStorage / my.getStorage 等异步读写本地缓存的能力,将数据存储在本地,返回结果的速度会比网络请求快。对于部分网络请求数据,可优先从缓存中获取数据来渲染视图,等待网络请求返回后进行更新。
  • 避免重复请求。在过去案例中常见同一 url 发起多次网络请求 request。
  • 优化好服务器处理时间。服务端可做 CDN 缓存,做网络请求的预加载(预热),让前端请求能快速响应。
  • 页面资源的域名尽量采用相同域名。底层网络可以复用相同的 TCP 连接做资源请求,节省了重复的 DNS > TCP 建连 > HTTPS 握手等繁杂的网络交互工作,这样可以大幅提升资源加载效率。
  • 页面资源依赖切勿依赖国外站点,跨国访问普遍比较慢,应该将国外站点资源发布到自己的服务器,可以大幅提升网页打开的性能。
  • 非关键资源应该做好隔离。避免非关键资源加载过慢,导致整个页面渲染不出来。
  • HTTP 请求长耗时服务器设计。如果服务器处理时间较长,需要 10s+才能返回结果的情况,必须设计为可轮询查状态的模式,切勿一个 http 请求上去等 10s+ 的 Response,很容易触发 Socket 读超时导致业务失败。而且移动网络也不够稳定,中间如果出现网络抖动导致断链业务就会失败, 所以客户端可以采取间断性轮询请求服务器的方式去查服务器处理结果。

请求 url 长度过长

说明

小程序发送给服务端的数据包过大,会延长数据传输时长和处理时长,最终影响小程序启动耗时。mPaaS网络库处理异常(mPaaS客户端限制 URL 长度)、服务端处理异常(服务端对 URL 长度限制),进而导致请求失败。

最佳实践

  • 网络请求 url 长度应不超过 2000 个字符。
  • 针对以上的第一种情况,建议将GET 请求转换成 POST 请求;但需要注意的是,这样的方式并不能解决第二种情况里数据包过大的问题。另外,在采用解决方案时,开发者需要验证服务端是否支持这种请求类型;
  • 针对数据包过大的第二种情况,建议对数据进行合理拆分,分页、分级请求和展示,从而减少 URL 的长度。

(本文作者:郭宏伟)

相关推荐
想你的风吹到了瑞士5 分钟前
变量提升&函数提升
前端·javascript·vue.js
生椰拿铁You17 分钟前
12 —— Webpack中向前端注入环境变量
前端
Huazzi.1 小时前
免费好用的静态网页托管平台全面对比介绍
前端·网络·github·web
吃土少女古拉拉1 小时前
前端和后端
前端·学习笔记
寒雒2 小时前
【Python】实战:实现GUI登录界面
开发语言·前端·python
独上归州2 小时前
Vue与React的Suspense组件对比
前端·vue.js·react.js·suspense
Komorebi⁼2 小时前
Vue核心特性解析(内含实践项目:设置购物车)
前端·javascript·vue.js·html·html5
明月清风徐徐2 小时前
Vue实训---0-完成Vue开发环境的搭建
前端·javascript·vue.js
SameX2 小时前
HarmonyOS Next 企业数据备份与恢复策略
前端·harmonyos
SameX2 小时前
HarmonyOS Next 企业数据传输安全策略
前端·harmonyos