本文我們來談?wù)勎⑿判〕绦蛳到y(tǒng)兼容性的那些坑。
微信小程序兼容性問題
微信小程序發(fā)布一周多了,兼容性問題塘装,特別是 Android 平臺(tái)兼容性問題特別嚴(yán)重急迂。據(jù)我觀察,好多小程序掉到兼容性的坑里蹦肴。掉坑里不要緊僚碎,更讓人捉急的是,從坑里爬上來的時(shí)候阴幌,手剛抓到坑沿勺阐,又被微信官方踩到(緊急修復(fù)兼容性的版本沒審核通過,被微信打回重審)矛双,再次跌落坑底渊抽,然后眼睜睜地看著后臺(tái)用戶在破口大罵“什么東西都沒有啊~,什么破小程序”议忽。
微信小程序的兼容性問題除了微信本身的 Bug 外懒闷,大部分是目標(biāo)平臺(tái)對(duì) JavaScript 標(biāo)準(zhǔn)庫支持程度不同造成的。
微信本身的 Bug 引起的
微信本身的 Bug 引發(fā)的兼容性問題有個(gè)現(xiàn)成的例子徙瓶,就是 wx.request()
返回的狀態(tài)碼 res.statusCode
的值在 iOS 下是 int 型數(shù)據(jù)毛雇,而在 Android 6.0.1 上卻是 String 型數(shù)據(jù)。如果你判斷服務(wù)器的返回狀態(tài)碼方法不當(dāng)侦镇,可能就踩到坑里了灵疮。
wx.request({
url: 'http://api.example.com',
success: function (res) {
if (res.statusCode === 200) {
// success
} else {
// server failure
}
}
})
上述代碼就踩坑了,正確的做法是使用 ==
而不是使用 ===
來判斷壳繁。另外一個(gè)更規(guī)范的方法是使用 parseInt(res.statusCode) === 200
來實(shí)現(xiàn)震捣。
Javascript 標(biāo)準(zhǔn)庫兼容性問題
比如 Array.find()
方法在 iOS 10.2/Android 7.0 上完美支持,但在 Android 6.0.1 上卻不支持闹炉。如果代碼里用到了這個(gè)接口蒿赢,就會(huì)導(dǎo)致在 Android 6.0.1 上無法正常工作。通過對(duì)比發(fā)現(xiàn)渣触,這類接口不支持的個(gè)數(shù)還是比較多的羡棵。特別是 Android 平臺(tái)版本眾多,兼容性問題就更嚴(yán)重嗅钻,可能一不小小心就掉到坑里皂冰。
解決方法
微信本身 Bug 只能繞過去,但對(duì) JavaScript 引擎的兼容性养篓,可以有更優(yōu)雅的解決方法秃流。比如,我們可以打補(bǔ)丁柳弄,使用 polyfill 來實(shí)現(xiàn)這些不支持的標(biāo)準(zhǔn)庫方法舶胀。比如,修復(fù) Android 6.0.1 平臺(tái)不支持 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;
};
}
推而廣之嚣伐,我們可以把平臺(tái)不支持的標(biāo)準(zhǔn)庫方法糖赔,使用 polyfill 實(shí)現(xiàn)。這就是 minapp-polyfill 這個(gè)項(xiàng)目的目的纤控。
使用方法很簡(jiǎn)單挂捻,把 minapp-polyfill 項(xiàng)目里的 polyfill.js
拷貝到小程序源碼目錄下,在需要打補(bǔ)丁的 JavaScript 源文件頭部引入如下代碼即可:
import 'path/to/polyfill.js'
目前這個(gè)項(xiàng)目只是搭了個(gè)骨架船万,還有很多方法需要實(shí)現(xiàn)刻撒。PRs is welcome。
各個(gè)平臺(tái)對(duì) JavaScript 標(biāo)準(zhǔn)庫支持情況
條件限制耿导,這里統(tǒng)計(jì)了四個(gè)平臺(tái)對(duì) JavaScript 標(biāo)準(zhǔn)庫的支持情況声怔,分別是 iOS 10.2, Android 6.0.1, Android 7.0, 微信開發(fā)者工具,具體數(shù)據(jù)如下:
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 表示這個(gè)標(biāo)準(zhǔn)庫方法在平臺(tái)上不支持
Q: 這些數(shù)據(jù)是怎么來的舱呻,靠譜嗎醋火?
A: 這些數(shù)據(jù)是在真實(shí)小程序運(yùn)行環(huán)境下運(yùn)行,然后把 API 支持情況發(fā)送到服務(wù)器后臺(tái)箱吕,再寫個(gè)腳本把數(shù)據(jù)整理匯總后得來的芥驳。
Q: 其他平臺(tái),比如 Android 5.0 的支持情況怎么樣茬高?
A: 由于條件限制兆旬,手上沒有 Android 5.0 的手機(jī),有愿意配合收集數(shù)據(jù)的怎栽,私信留言丽猬。配合的方法很簡(jiǎn)單,用指定型號(hào)的手機(jī)打開一個(gè)微信小程序熏瞄,按一個(gè)按鈕即可脚祟。
Q: 為什么不使用 lodash 之類效率更高的庫,而使用的標(biāo)準(zhǔn)庫强饮?
A: 使用 lodash 之類的確實(shí)效率更高由桌,兼容性也更好∮史幔基于兩個(gè)原因沒有使用行您,一是 lodash 太大,而微信小程序限制在 1MB 以內(nèi)柠座。當(dāng)然邑雅,可以用 lodash 模塊化的版本來解決片橡,但還有第二個(gè)原因妈经,即 lodash 的一些 API 也有兼容性問題,比如我試過 lodash.findIndex 這個(gè)包,結(jié)果在 Android 6.0.1 上也無法成功運(yùn)行 (這一點(diǎn)未做深入驗(yàn)證吹泡,感興趣的同學(xué)可以驗(yàn)證一下)骤星。
總結(jié)
從后臺(tái)數(shù)據(jù)來看,小程序剛發(fā)布的前三天爆哑,確實(shí)帶來了非扯茨眩可觀的流量紅利,但這部分償鮮的用戶揭朝,很快就消失了队贱。三天過后,基本上保持了平衡的訪問量潭袱。流量紅利和廣告一樣柱嫌,是催化劑,真正有價(jià)值的還是要做用戶需要的產(chǎn)品屯换。
在此順手安利一下開發(fā)的兩個(gè)小程序 360好書推薦 和 51經(jīng)典電影编丘,偶爾想用的時(shí)候打開,可能會(huì)偶遇一些小驚喜彤悔。但坦白講嘉抓,這兩個(gè)小程序都和微信倡導(dǎo)的小程序價(jià)值觀不符。微信還是希望通過小程序把線下低頻的晕窑,服務(wù)成本高(這里應(yīng)該主要是時(shí)間成本抑片,即便利性)的場(chǎng)景,轉(zhuǎn)化為線上快捷的使用方式幕屹。