javascript - Starea array-ului va fi memorată în cache în Safari din iOS 12. Este un bug sau o funcționalitate?


Actualizare la 31.10.2018 :

Acest bug a fost rezolvat în iOS 12.1, să ai o zi bună~

Am găsit o problemă cu starea valorii unui Array în Safari-ul din iOS 12 proaspăt lansat, de exemplu, cod precum acesta:

<!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>

După reîmprospătarea paginii, valoarea array-ului este încă inversată. Este aceasta o eroare sau o funcție a noului Safari?


Iată o pagină demo. Încercați să o folosiți cu Safari în iOS 12: https://abelyao.github.io/others/ios12-safari-bug.html

Răspuns #1

Este cu siguranță un BUG! Și este un bug foarte grav.

Eroarea se datorează optimizării inițializatorilor de array-uri în care toate valorile sunt litere primitive. De exemplu, dată fiind funcția:

function buildArray() {
    return [1, null, 'x'];
}

Toate referințele la array-urile returnate de apelurile către buildArray() vor fi legate la aceeași memorie, iar unele metode, cum ar fi toString(), vor avea rezultatele memorate în cache. De obicei, pentru a menține consistența, orice operațiune mutabilă pe astfel de array-uri optimizate va copia datele într-un spațiu de memorie separat și se va lega la acesta; acest tipar se numește copy-on-write, sau pe scurt CoW.

Metoda reverse() modifică array-ul, așa că ar trebui să declanșeze o copiere la scriere. Dar nu o face, deoarece implementatorul original (Keith Miller de la Apple) a omis cazul reverse(), chiar dacă el scrisese multe teste.

Această eroare a fost raportată către Apple pe 21 august. Remedierea a fost adăugată în depozitul WebKit pe 27 august și a fost livrată în Safari 12.0.1 și iOS 12.1 pe 30 octombrie 2018.

Răspunsul #2

Am scris o bibliotecă pentru a repara bug-ul. https://www.npmjs.com/package/array-reverse-polyfill

Acesta este codul:

(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);
  }
})();

Răspunsul #3

Aceasta este o eroare în webkit. Deși aceasta a fost rezolvată de partea lor, nu a fost încă inclusă în versiunea GM de iOS. Una dintre soluțiile acestei probleme:

(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;
    }
  }
})();