Стан масиву буде кешований в iOS 12 Safari. Це помилка чи особливість?


432

Оновлення на 2018.10.31

Ця помилка була виправлена ​​в iOS 12.1, приємний день ~

Я знайшов проблему зі станом значення Array у нещодавно випущеному iOS 12 Safari, наприклад, такий код:

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


41
Помилка підтверджена також у macOS 10.14 Mojave - i.imgur.com/ZJtJJC1.png
a_rahmanshah

43
macOS 10.13.6 (High Sierra) з Safari версії 12.0 (13606.2.11) має той самий випуск. Після оновлення сторінки масив все ще змінюється.
Кевін Гімбель

2
Помилка була виправлена ​​в Safari 12.0.1 (macOS), а також в iOS 12.1.
MrMister

Відповіді:


272

Це напевно БУГ! І це дуже серйозна помилка.

Помилка обумовлена ​​оптимізацією ініціалізаторів масиву, у яких усі значення є примітивними буквами. Наприклад, з урахуванням функції:

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

Усі повернені посилання масиву від дзвінків до buildArray()буде посилатися на одну пам'ять, а деякі методи, такі як toString()результати, будуть кешовані. Зазвичай для збереження послідовності будь-яка змінна операція на таких оптимізованих масивах буде копіювати дані в окремий простір пам'яті та посилатися на нього; ця закономірність називається копіювати при записі або коротко - CoW.

У reverse()методі мутує масив, тому він повинен викликати копію під час запису. Але це не так, тому що оригінальний виконавець (Кіт Міллер з Apple) пропустив цю reverse()справу, хоча він написав багато тестів.

Про цю помилку було повідомлено Apple 21 серпня. Виправлення висадилось у сховищі WebKit 27 серпня та було доставлено в Safari 12.0.1 та iOS 12.1 30 жовтня 2018 року.


11
Примітка: Safari 12.0 на Mac OS X також має таку ж проблему.
hax

17
Так, це вже зафіксовано в джерелах і вже поставляється в Safari Technology Preview. Спробуйте cdn.miss.cat/demo/ios12-safari-bug.html у програмі Safari Technology Preview 65. Ви побачите, що в ньому немає помилки.
sidehowbarker

6
Я не вірю, що основна причина помилки - результат змішування індексу; натомість, здається, це викликано нехтуванням перевірки, чи є предмет незмінним перед його зміною. Питання зрізів може мати подібне пояснення, але це не те саме, але не буде виправлено патчем для зворотного, наскільки я можу сказати. Слід розглянути можливість відкриття звіту про помилку WebKit для проблеми з фрагментами.
Zenexer

5
@Zenexer Ви праві. Цю відповідь я написав ще до того, як знайшов bugs.webkit.org/show_bug.cgi?id=188794 і побачив вихідний код. Я відредагую свою відповідь.
hax

75

Я написав ліб, щоб виправити помилку. 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);
  }
})();


4
Оновлення в будь-який час. Ласкаво просимо зробити свій внесок.
Едіре Фан

14
@zephi, я здогадуюсь, що написання на length ( this.length = this.length) запустить Copy On Write, тому змінить пам'ять масиву і так виправить поведінку reverse.
Cœur

14

Це помилка у веб-програмі . Хоча це було вирішено в їх кінці, але ще не поставляється з випуском GM iOS. Одне з рішень цієї проблеми:

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

6

Здається, не кешується, якщо кількість елементів змінюється.
Мені вдалося уникнути подібного.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.