html页面中输入如下内容:
Use iOS12 test. <br/>
<button onclick="test()" style="border: 1px solid #ccc;">Click Refresh</button>
<script>
function test() {
var arr = [1, 2, 3, 4]
document.body.innerHTML += '<br/>'+arr.join(',')
arr.reverse()
}
</script>
测试地址如下:
For Not fixed: https://fanmingfei.github.io/array-reverse-ios12/origin.html
For Fixed: https://fanmingfei.github.io/array-reverse-ios12/fixed.html
正常预期为
在IOS12 Safari下显示为:
随后前端元老贺师俊(@johnhax)给出了一个可能的原因:
iOS 12 safari 的 JS 引擎 reverse()惊天大 bug 之原因分析:Safari 对所有值是 primitive
literal 的 array initializer 做了优化,同一个 initializer 产生的数组在内存里永远指向一份,
reverse()之后,所有从这个 initializer 得到的数组也都倒序了。另一方面,其 toString()的结果是预先计算缓存的,所以 toString()结果并不会修改。按正常优化来说,如果某个这样的
array 执行了任何修改操作,应该复制到一份独立内存去。这是所谓 copy-on-write 的策略。但不幸的,
reverse()方法没有触发 CoW。另一方面,所有不修改 array 的方法应该不触发 CoW。我实测下来,甚至 copyWithin和 fill这样的方法,如果
start/end 相同使得实际上并没有修改效果,也不会触发 CoW。但是神奇的是 slice()会触发 CoW。所以我猜有可能某个苹果的临时工把 reverse/ slice的方法索引搞颠倒了。
进行reverse polyfill:
先根据ua判断是否为IOS12:
/Version\/12\.0.*Safari\//.test(navigator.userAgent)
然后进行reverse方法重写:
(function() {
function buggy() {
var a = [1, 2];
return String(a) === String(a.reverse());
}
if(!buggy()) return;
var r = Array.prototype.reverse;
Array.prototype.reverse = function reverse() {
if (Array.isArray(this)) this.length = this.length;
return r.call(this);
}
})();
参考资料:
https://stackoverflow.com/questions/52390368/array-state-will-be-cached-in-ios-12-safari-is-it-a-bug-or-feature
https://github.com/fanmingfei/array-reverse-ios12/
https://www.npmjs.com/package/array-reverse-polyfill