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

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

微信小程序兼容性问题

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

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

微信本身的 Bug 引起的

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

1
2
3
4
5
6
7
8
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 代码:

1
2
3
4
5
6
7
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 源文件头部引入如下代码即可:

1
import 'path/to/polyfill.js'

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

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

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

Component.apiNameiOS 10.2Android 6.0.1Android 7.0devtool
Array.toStringYESYESYESYES
Array.valuesYESN/AYESN/A
Array.toLocaleStringYESYESYESYES
Array.concatYESYESYESYES
Array.fillYESN/AYESYES
Array.joinYESYESYESYES
Array.popYESYESYESYES
Array.pushYESYESYESYES
Array.reverseYESYESYESYES
Array.shiftYESYESYESYES
Array.sliceYESYESYESYES
Array.sortYESYESYESYES
Array.spliceYESYESYESYES
Array.unshiftYESYESYESYES
Array.everyYESYESYESYES
Array.forEachYESYESYESYES
Array.someYESYESYESYES
Array.indexOfYESYESYESYES
Array.lastIndexOfYESYESYESYES
Array.filterYESYESYESYES
Array.reduceYESYESYESYES
Array.reduceRightYESYESYESYES
Array.mapYESYESYESYES
Array.entriesYESN/AYESYES
Array.keysYESN/AYESYES
Array.findYESN/AYESYES
Array.findIndexYESN/AYESYES
Array.includesYESN/AN/AYES
Array.copyWithinYESN/AYESYES
Array.constructorYESYESYESYES
BufferN/AN/AN/AN/A
DataView.getInt8YESYESYESYES
DataView.getUint8YESYESYESYES
DataView.getInt16YESYESYESYES
DataView.getUint16YESYESYESYES
DataView.getInt32YESYESYESYES
DataView.getUint32YESYESYESYES
DataView.getFloat32YESYESYESYES
DataView.getFloat64YESYESYESYES
DataView.setInt8YESYESYESYES
DataView.setUint8YESYESYESYES
DataView.setInt16YESYESYESYES
DataView.setUint16YESYESYESYES
DataView.setInt32YESYESYESYES
DataView.setUint32YESYESYESYES
DataView.setFloat32YESYESYESYES
DataView.setFloat64YESYESYESYES
DataView.constructorYESYESYESYES
Date.toStringYESYESYESYES
Date.toISOStringYESYESYESYES
Date.toDateStringYESYESYESYES
Date.toTimeStringYESYESYESYES
Date.toLocaleStringYESYESYESYES
Date.toLocaleDateStringYESYESYESYES
Date.toLocaleTimeStringYESYESYESYES
Date.valueOfYESYESYESYES
Date.getTimeYESYESYESYES
Date.getFullYearYESYESYESYES
Date.getUTCFullYearYESYESYESYES
Date.getMonthYESYESYESYES
Date.getUTCMonthYESYESYESYES
Date.getDateYESYESYESYES
Date.getUTCDateYESYESYESYES
Date.getDayYESYESYESYES
Date.getUTCDayYESYESYESYES
Date.getHoursYESYESYESYES
Date.getUTCHoursYESYESYESYES
Date.getMinutesYESYESYESYES
Date.getUTCMinutesYESYESYESYES
Date.getSecondsYESYESYESYES
Date.getUTCSecondsYESYESYESYES
Date.getMillisecondsYESYESYESYES
Date.getUTCMillisecondsYESYESYESYES
Date.getTimezoneOffsetYESYESYESYES
Date.setTimeYESYESYESYES
Date.setMillisecondsYESYESYESYES
Date.setUTCMillisecondsYESYESYESYES
Date.setSecondsYESYESYESYES
Date.setUTCSecondsYESYESYESYES
Date.setMinutesYESYESYESYES
Date.setUTCMinutesYESYESYESYES
Date.setHoursYESYESYESYES
Date.setUTCHoursYESYESYESYES
Date.setDateYESYESYESYES
Date.setUTCDateYESYESYESYES
Date.setMonthYESYESYESYES
Date.setUTCMonthYESYESYESYES
Date.setFullYearYESYESYESYES
Date.setUTCFullYearYESYESYESYES
Date.setYearYESYESYESYES
Date.getYearYESYESYESYES
Date.toJSONYESYESYESYES
Date.toUTCStringYESYESYESYES
Date.toGMTStringYESYESYESYES
Date.constructorYESYESYESYES
Error.toStringYESYESYESYES
Error.constructorYESYESYESYES
Float32Array.constructorYESYESYESYES
Float64Array.constructorYESYESYESYES
Function.constructorYESYESYESYES
Int16Array.constructorYESYESYESYES
Int32Array.constructorYESYESYESYES
Int8Array.constructorYESYESYESYES
Map.forEachYESN/AYESYES
Map.clearYESN/AYESYES
Map.deleteYESN/AYESYES
Map.getYESN/AYESYES
Map.hasYESN/AYESYES
Map.setYESN/AYESYES
Map.keysYESN/AYESYES
Map.valuesYESN/AYESYES
Map.entriesYESN/AYESYES
Map.constructorYESN/AYESYES
Math.absYESYESYESYES
Math.acosYESYESYESYES
Math.asinYESYESYESYES
Math.atanYESYESYESYES
Math.acoshYESN/AYESYES
Math.asinhYESN/AYESYES
Math.atanhYESN/AYESYES
Math.atan2YESYESYESYES
Math.cbrtYESN/AYESYES
Math.ceilYESYESYESYES
Math.clz32YESN/AYESYES
Math.cosYESYESYESYES
Math.coshYESN/AYESYES
Math.expYESYESYESYES
Math.expm1YESN/AYESYES
Math.floorYESYESYESYES
Math.froundYESN/AYESYES
Math.hypotYESN/AYESYES
Math.logYESYESYESYES
Math.log10YESN/AYESYES
Math.log1pYESN/AYESYES
Math.log2YESN/AYESYES
Math.maxYESYESYESYES
Math.minYESYESYESYES
Math.powYESYESYESYES
Math.randomYESYESYESYES
Math.roundYESYESYESYES
Math.signYESN/AYESYES
Math.sinYESYESYESYES
Math.sinhYESN/AYESYES
Math.sqrtYESYESYESYES
Math.tanYESYESYESYES
Math.tanhYESN/AYESYES
Math.truncYESN/AYESYES
Math.imulYESYESYESYES
Object.toStringYESYESYESYES
Object.toLocaleStringYESYESYESYES
Object.valueOfYESYESYESYES
Object.hasOwnPropertyYESYESYESYES
Object.propertyIsEnumerableYESYESYESYES
Object.isPrototypeOfYESYESYESYES
Object._defineGetter_YESYESYESYES
Object._defineSetter_YESYESYESYES
Object._lookupGetter_YESYESYESYES
Object._lookupSetter_YESYESYESYES
Object.constructorYESYESYESYES
Promise.thenYESYESYESYES
Promise.catchYESYESYESYES
Promise.constructorYESYESYESYES
RegExp.compileYESYESYESYES
RegExp.execYESYESYESYES
RegExp.toStringYESYESYESYES
RegExp.testYESYESYESYES
RegExp.constructorYESYESYESYES
Set.forEachYESN/AYESYES
Set.addYESN/AYESYES
Set.clearYESN/AYESYES
Set.deleteYESN/AYESYES
Set.hasYESN/AYESYES
Set.entriesYESN/AYESYES
Set.valuesYESN/AYESYES
Set.keysYESN/AYESYES
Set.constructorYESN/AYESYES
String.matchYESYESYESYES
String.padStartYESN/AN/AN/A
String.padEndYESN/AN/AN/A
String.repeatYESN/AYESYES
String.replaceYESYESYESYES
String.searchYESYESYESYES
String.splitYESYESYESYES
String.toStringYESYESYESYES
String.valueOfYESYESYESYES
String.charAtYESYESYESYES
String.charCodeAtYESYESYESYES
String.codePointAtYESN/AYESYES
String.concatYESYESYESYES
String.indexOfYESYESYESYES
String.lastIndexOfYESYESYESYES
String.sliceYESYESYESYES
String.substrYESYESYESYES
String.substringYESYESYESYES
String.toLowerCaseYESYESYESYES
String.toUpperCaseYESYESYESYES
String.localeCompareYESYESYESYES
String.toLocaleLowerCaseYESYESYESYES
String.toLocaleUpperCaseYESYESYESYES
String.bigYESYESYESYES
String.smallYESYESYESYES
String.blinkYESYESYESYES
String.boldYESYESYESYES
String.fixedYESYESYESYES
String.italicsYESYESYESYES
String.strikeYESYESYESYES
String.subYESYESYESYES
String.supYESYESYESYES
String.fontcolorYESYESYESYES
String.fontsizeYESYESYESYES
String.anchorYESYESYESYES
String.linkYESYESYESYES
String.trimYESYESYESYES
String.trimLeftYESYESYESYES
String.trimRightYESYESYESYES
String.startsWithYESN/AYESYES
String.endsWithYESN/AYESYES
String.includesYESN/AYESYES
String.normalizeYESYESYESYES
String.constructorYESYESYESYES
Symbol.toStringYESN/AYESYES
Symbol.valueOfYESN/AN/AYES
Symbol.constructorYESN/AYESYES
TypeError.toStringYESN/AN/AYES
TypeError.constructorYESYESYESYES
Uint16Array.constructorYESYESYESYES
Uint32Array.constructorYESYESYESYES
Uint8Array.constructorYESYESYESYES
Uint8ClampedArray.constructorYESYESYESYES
WeakMap.deleteYESYESYESYES
WeakMap.getYESYESYESYES
WeakMap.hasYESYESYESYES
WeakMap.setYESYESYESYES
WeakMap.constructorYESYESYESYES

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 上也无法成功运行 (这一点未做深入验证,感兴趣的同学可以验证一下)。

总结

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


仅有一条评论

  1. 写得不错啊

添加新评论