2018.10.31に更新
このバグはiOS 12.1で修正されました。おはようございます〜
私は新しくリリースされたiOS 12 Safariで、Arrayの値の状態に問題があることを発見しました。たとえば、次のようなコードです。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>iOS 12 Safari bugs</title>
<script type="text/javascript">
window.addEventListener("load", function ()
{
let arr = [1, 2, 3, 4, 5];
alert(arr.join());
document.querySelector("button").addEventListener("click", function ()
{
arr.reverse();
});
});
</script>
</head>
<body>
<button>Array.reverse()</button>
<p style="color:red;">test: click button and refresh page, code:</p>
</body>
</html>
ページを更新した後、配列の値はまだ反転されています。これは新しいSafariのバグですか?それとも機能ですか?
これはデモページです。 iOS 12 Safariでそれを使ってみてください: https://abelyao.github.io/others/ios12-safari-bug.html
それは間違いなくバグです!そしてそれは非常に深刻なバグです。
バグはすべての値が原始リテラルである配列初期化子の最適化によるものです。たとえば、次の関数が与えられたとします。
function buildArray() {
return [1, null, 'x'];
}
buildArray()
の呼び出しから返されたすべての配列参照は同じメモリにリンクされ、toString()
のようないくつかのメソッドはそれらの結果がキャッシュされるでしょう。通常、一貫性を保つために、このような最適化された配列に対する変更可能な操作では、データを別のメモリスペースにコピーしてリンクします。このパターンは コピーオンライト 、または略してCoW)と呼ばれます。
reverse()
メソッドは配列を変更するので、コピーオンライトを引き起こすべきです。しかし、最初の実装者(AppleのKeith Miller)は、多くのテストケースを書いたにもかかわらずreverse()
のケースを見逃していたので、そうではありません。
このバグは 8月21日にApple に報告されたものです。修正 8月27日にWebKitリポジトリ に到着し、2018年10月30日にSafari 12.0.1およびiOS 12.1で出荷されました。
私はバグを修正するためにlibを書きました。 https://www.npmjs.com/package/array-reverse-polyfill
これがコードです :
(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);
}
})();
これは webkit のバグです。これは最終的に解決されましたが、まだiOS GMリリースに同梱されていません。この問題に対する解決策の1つ:
(function() {
function getReverseStr() {
return [1, 2].reverse();
}
var n1 = getReverseStr()[0];
var n2 = getReverseStr()[0];
// check if there is an issue
if(n1 != n2) {
var origReverseFunction = Array.prototype.reverse;
Array.prototype.reverse = function() {
var newArr = this.slice();
// use original reverse function so that Edge cases are taken care of
origReverseFunction.apply(newArr, arguments);
var that = this;
// copy reversed array
newArr.forEach(function(value, index) {
that[index] = value;
});
return this;
}
}
})();
要素数が変わってもキャッシュされないようです。
私はこれを避けることができました。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>iOS 12 Safari bugs</title>
<script type="text/javascript">
window.addEventListener("load", function ()
{
let arr = [1, 2, 3, 4, 5];
arr.Push('');
arr.pop();
alert(arr.join());
document.querySelector("button").addEventListener("click", function ()
{
arr.reverse();
});
});
</script>
</head>
<body>
<button>Array.reverse()</button>
<p style="color:red;">test: click button and refresh page, code:</p>
</body>
</html>