微信小程序兼容性问题

3,672 阅读8分钟

本文我们来谈谈微信小程序系统兼容性的那些坑。

微信小程序兼容性问题

微信小程序发布一周多了,兼容性问题,特别是 Android 平台兼容性问题特别严重。据我观察,好多小程序掉到兼容性的坑里。掉坑里不要紧,更让人捉急的是,从坑里爬上来的时候,手刚抓到坑沿,又被微信官方踩到(紧急修复兼容性的版本没审核通过,被微信打回重审),再次跌落坑底,然后眼睁睁地看着后台用户在破口大骂“什么东西都没有啊~,什么破小程序”。

微信小程序的兼容性问题除了微信本身的 Bug 外,大部分是目标平台对 JavaScript 标准库支持程度不同造成的。

微信本身的 Bug 引起的

微信本身的 Bug 引发的兼容性问题有个现成的例子,就是 wx.request() 返回的状态码 res.statusCode 的值在 iOS 下是 int 型数据,而在 Android 6.0.1 上却是 String 型数据。如果你判断服务器的返回状态码方法不当,可能就踩到坑里了。

wx.request({
    url: 'http://api.example.com',
    success: function (res) {
        if (res.statusCode === 200) {
            // success
        } else {
            // server failure
        }
    }
})

上述代码就踩坑了,正确的做法是使用 == 而不是使用 === 来判断。另外一个更规范的方法是使用 parseInt(res.statusCode) === 200 来实现。

Javascript 标准库兼容性问题

比如 Array.find() 方法在 iOS 10.2/Android 7.0 上完美支持,但在 Android 6.0.1 上却不支持。如果代码里用到了这个接口,就会导致在 Android 6.0.1 上无法正常工作。通过对比发现,这类接口不支持的个数还是比较多的。特别是 Android 平台版本众多,兼容性问题就更严重,可能一不小小心就掉到坑里。

解决方法

微信本身 Bug 只能绕过去,但对 JavaScript 引擎的兼容性,可以有更优雅的解决方法。比如,我们可以打补丁,使用 polyfill 来实现这些不支持的标准库方法。比如,修复 Android 6.0.1 平台不支持 String.startsWith() 的问题,可以使用下面的 polyfill 代码:

if (!String.prototype.startsWith) {
    console.warn('define polyfill for Array.prototype.startsWith');
    String.prototype.startsWith = function (searchString, position) {
      position = position || 0;
      return this.substr(position, searchString.length) === searchString;
  };
}

推而广之,我们可以把平台不支持的标准库方法,使用 polyfill 实现。这就是 minapp-polyfill 这个项目的目的。

使用方法很简单,把 minapp-polyfill 项目里的 polyfill.js 拷贝到小程序源码目录下,在需要打补丁的 JavaScript 源文件头部引入如下代码即可:

import 'path/to/polyfill.js'

目前这个项目只是搭了个骨架,还有很多方法需要实现。PRs is welcome。

各个平台对 JavaScript 标准库支持情况

条件限制,这里统计了四个平台对 JavaScript 标准库的支持情况,分别是 iOS 10.2, Android 6.0.1, Android 7.0, 微信开发者工具,具体数据如下:

Component.apiName iOS 10.2 Android 6.0.1 Android 7.0 devtool
Array.toString YES YES YES YES
Array.values YES N/A YES N/A
Array.toLocaleString YES YES YES YES
Array.concat YES YES YES YES
Array.fill YES N/A YES YES
Array.join YES YES YES YES
Array.pop YES YES YES YES
Array.push YES YES YES YES
Array.reverse YES YES YES YES
Array.shift YES YES YES YES
Array.slice YES YES YES YES
Array.sort YES YES YES YES
Array.splice YES YES YES YES
Array.unshift YES YES YES YES
Array.every YES YES YES YES
Array.forEach YES YES YES YES
Array.some YES YES YES YES
Array.indexOf YES YES YES YES
Array.lastIndexOf YES YES YES YES
Array.filter YES YES YES YES
Array.reduce YES YES YES YES
Array.reduceRight YES YES YES YES
Array.map YES YES YES YES
Array.entries YES N/A YES YES
Array.keys YES N/A YES YES
Array.find YES N/A YES YES
Array.findIndex YES N/A YES YES
Array.includes YES N/A N/A YES
Array.copyWithin YES N/A YES YES
Array.constructor YES YES YES YES
Buffer N/A N/A N/A N/A
DataView.getInt8 YES YES YES YES
DataView.getUint8 YES YES YES YES
DataView.getInt16 YES YES YES YES
DataView.getUint16 YES YES YES YES
DataView.getInt32 YES YES YES YES
DataView.getUint32 YES YES YES YES
DataView.getFloat32 YES YES YES YES
DataView.getFloat64 YES YES YES YES
DataView.setInt8 YES YES YES YES
DataView.setUint8 YES YES YES YES
DataView.setInt16 YES YES YES YES
DataView.setUint16 YES YES YES YES
DataView.setInt32 YES YES YES YES
DataView.setUint32 YES YES YES YES
DataView.setFloat32 YES YES YES YES
DataView.setFloat64 YES YES YES YES
DataView.constructor YES YES YES YES
Date.toString YES YES YES YES
Date.toISOString YES YES YES YES
Date.toDateString YES YES YES YES
Date.toTimeString YES YES YES YES
Date.toLocaleString YES YES YES YES
Date.toLocaleDateString YES YES YES YES
Date.toLocaleTimeString YES YES YES YES
Date.valueOf YES YES YES YES
Date.getTime YES YES YES YES
Date.getFullYear YES YES YES YES
Date.getUTCFullYear YES YES YES YES
Date.getMonth YES YES YES YES
Date.getUTCMonth YES YES YES YES
Date.getDate YES YES YES YES
Date.getUTCDate YES YES YES YES
Date.getDay YES YES YES YES
Date.getUTCDay YES YES YES YES
Date.getHours YES YES YES YES
Date.getUTCHours YES YES YES YES
Date.getMinutes YES YES YES YES
Date.getUTCMinutes YES YES YES YES
Date.getSeconds YES YES YES YES
Date.getUTCSeconds YES YES YES YES
Date.getMilliseconds YES YES YES YES
Date.getUTCMilliseconds YES YES YES YES
Date.getTimezoneOffset YES YES YES YES
Date.setTime YES YES YES YES
Date.setMilliseconds YES YES YES YES
Date.setUTCMilliseconds YES YES YES YES
Date.setSeconds YES YES YES YES
Date.setUTCSeconds YES YES YES YES
Date.setMinutes YES YES YES YES
Date.setUTCMinutes YES YES YES YES
Date.setHours YES YES YES YES
Date.setUTCHours YES YES YES YES
Date.setDate YES YES YES YES
Date.setUTCDate YES YES YES YES
Date.setMonth YES YES YES YES
Date.setUTCMonth YES YES YES YES
Date.setFullYear YES YES YES YES
Date.setUTCFullYear YES YES YES YES
Date.setYear YES YES YES YES
Date.getYear YES YES YES YES
Date.toJSON YES YES YES YES
Date.toUTCString YES YES YES YES
Date.toGMTString YES YES YES YES
Date.constructor YES YES YES YES
Error.toString YES YES YES YES
Error.constructor YES YES YES YES
Float32Array.constructor YES YES YES YES
Float64Array.constructor YES YES YES YES
Function.constructor YES YES YES YES
Int16Array.constructor YES YES YES YES
Int32Array.constructor YES YES YES YES
Int8Array.constructor YES YES YES YES
Map.forEach YES N/A YES YES
Map.clear YES N/A YES YES
Map.delete YES N/A YES YES
Map.get YES N/A YES YES
Map.has YES N/A YES YES
Map.set YES N/A YES YES
Map.keys YES N/A YES YES
Map.values YES N/A YES YES
Map.entries YES N/A YES YES
Map.constructor YES N/A YES YES
Math.abs YES YES YES YES
Math.acos YES YES YES YES
Math.asin YES YES YES YES
Math.atan YES YES YES YES
Math.acosh YES N/A YES YES
Math.asinh YES N/A YES YES
Math.atanh YES N/A YES YES
Math.atan2 YES YES YES YES
Math.cbrt YES N/A YES YES
Math.ceil YES YES YES YES
Math.clz32 YES N/A YES YES
Math.cos YES YES YES YES
Math.cosh YES N/A YES YES
Math.exp YES YES YES YES
Math.expm1 YES N/A YES YES
Math.floor YES YES YES YES
Math.fround YES N/A YES YES
Math.hypot YES N/A YES YES
Math.log YES YES YES YES
Math.log10 YES N/A YES YES
Math.log1p YES N/A YES YES
Math.log2 YES N/A YES YES
Math.max YES YES YES YES
Math.min YES YES YES YES
Math.pow YES YES YES YES
Math.random YES YES YES YES
Math.round YES YES YES YES
Math.sign YES N/A YES YES
Math.sin YES YES YES YES
Math.sinh YES N/A YES YES
Math.sqrt YES YES YES YES
Math.tan YES YES YES YES
Math.tanh YES N/A YES YES
Math.trunc YES N/A YES YES
Math.imul YES YES YES YES
Object.toString YES YES YES YES
Object.toLocaleString YES YES YES YES
Object.valueOf YES YES YES YES
Object.hasOwnProperty YES YES YES YES
Object.propertyIsEnumerable YES YES YES YES
Object.isPrototypeOf YES YES YES YES
Object._defineGetter_ YES YES YES YES
Object._defineSetter_ YES YES YES YES
Object._lookupGetter_ YES YES YES YES
Object._lookupSetter_ YES YES YES YES
Object.constructor YES YES YES YES
Promise.then YES YES YES YES
Promise.catch YES YES YES YES
Promise.constructor YES YES YES YES
RegExp.compile YES YES YES YES
RegExp.exec YES YES YES YES
RegExp.toString YES YES YES YES
RegExp.test YES YES YES YES
RegExp.constructor YES YES YES YES
Set.forEach YES N/A YES YES
Set.add YES N/A YES YES
Set.clear YES N/A YES YES
Set.delete YES N/A YES YES
Set.has YES N/A YES YES
Set.entries YES N/A YES YES
Set.values YES N/A YES YES
Set.keys YES N/A YES YES
Set.constructor YES N/A YES YES
String.match YES YES YES YES
String.padStart YES N/A N/A N/A
String.padEnd YES N/A N/A N/A
String.repeat YES N/A YES YES
String.replace YES YES YES YES
String.search YES YES YES YES
String.split YES YES YES YES
String.toString YES YES YES YES
String.valueOf YES YES YES YES
String.charAt YES YES YES YES
String.charCodeAt YES YES YES YES
String.codePointAt YES N/A YES YES
String.concat YES YES YES YES
String.indexOf YES YES YES YES
String.lastIndexOf YES YES YES YES
String.slice YES YES YES YES
String.substr YES YES YES YES
String.substring YES YES YES YES
String.toLowerCase YES YES YES YES
String.toUpperCase YES YES YES YES
String.localeCompare YES YES YES YES
String.toLocaleLowerCase YES YES YES YES
String.toLocaleUpperCase YES YES YES YES
String.big YES YES YES YES
String.small YES YES YES YES
String.blink YES YES YES YES
String.bold YES YES YES YES
String.fixed YES YES YES YES
String.italics YES YES YES YES
String.strike YES YES YES YES
String.sub YES YES YES YES
String.sup YES YES YES YES
String.fontcolor YES YES YES YES
String.fontsize YES YES YES YES
String.anchor YES YES YES YES
String.link YES YES YES YES
String.trim YES YES YES YES
String.trimLeft YES YES YES YES
String.trimRight YES YES YES YES
String.startsWith YES N/A YES YES
String.endsWith YES N/A YES YES
String.includes YES N/A YES YES
String.normalize YES YES YES YES
String.constructor YES YES YES YES
Symbol.toString YES N/A YES YES
Symbol.valueOf YES N/A N/A YES
Symbol.constructor YES N/A YES YES
TypeError.toString YES N/A N/A YES
TypeError.constructor YES YES YES YES
Uint16Array.constructor YES YES YES YES
Uint32Array.constructor YES YES YES YES
Uint8Array.constructor YES YES YES YES
Uint8ClampedArray.constructor YES YES YES YES
WeakMap.delete YES YES YES YES
WeakMap.get YES YES YES YES
WeakMap.has YES YES YES YES
WeakMap.set YES YES YES YES
WeakMap.constructor YES YES YES YES

N/A 表示这个标准库方法在平台上不支持

Q: 这些数据是怎么来的,靠谱吗?
A: 这些数据是在真实小程序运行环境下运行,然后把 API 支持情况发送到服务器后台,再写个脚本把数据整理汇总后得来的。

Q: 其他平台,比如 Android 5.0 的支持情况怎么样?
A: 由于条件限制,手上没有 Android 5.0 的手机,有愿意配合收集数据的,私信留言。配合的方法很简单,用指定型号的手机打开一个微信小程序,按一个按钮即可。

Q: 为什么不使用 lodash 之类效率更高的库,而使用的标准库?
A: 使用 lodash 之类的确实效率更高,兼容性也更好。基于两个原因没有使用,一是 lodash 太大,而微信小程序限制在 1MB 以内。当然,可以用 lodash 模块化的版本来解决,但还有第二个原因,即 lodash 的一些 API 也有兼容性问题,比如我试过 lodash.findIndex 这个包,结果在 Android 6.0.1 上也无法成功运行 (这一点未做深入验证,感兴趣的同学可以验证一下)。

总结

从后台数据来看,小程序刚发布的前三天,确实带来了非常可观的流量红利,但这部分偿鲜的用户,很快就消失了。三天过后,基本上保持了平衡的访问量。流量红利和广告一样,是催化剂,真正有价值的还是要做用户需要的产品。

在此顺手安利一下开发的两个小程序 360好书推荐51经典电影,偶尔想用的时候打开,可能会偶遇一些小惊喜。但坦白讲,这两个小程序都和微信倡导的小程序价值观不符。微信还是希望通过小程序把线下低频的,服务成本高(这里应该主要是时间成本,即便利性)的场景,转化为线上快捷的使用方式。