Состояние массива будет кэшироваться в iOS 12 Safari. Это ошибка или особенность?

Обновление от 31 октября 2018 г.

Эта ошибка исправлена ​​в iOS 12.1, хорошего дня ~

Я обнаружил проблему с состоянием значения массива в недавно выпущенном 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


person abelyao    schedule 18.09.2018    source источник
comment
Ошибка подтверждена также в macOS 10.14 Mojave - i.imgur.com/ZJtJJC1.png   -  person a_rahmanshah    schedule 19.09.2018
comment
macOS 10.13.6 (High Sierra) с Safari версии 12.0 (13606.2.11) имеет ту же проблему. После обновления страницы массив все еще переворачивается.   -  person Kevin G.    schedule 19.09.2018
comment
Ошибка исправлена ​​в Safari 12.0.1 (macOS), а также в iOS 12.1.   -  person MrMister    schedule 01.11.2018


Ответы (4)


Это определенно ОШИБКА! И это очень серьезная ошибка.

Ошибка связана с оптимизацией инициализаторов массивов, в которых все значения являются примитивными литералами. Например, учитывая функцию:

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 года.

person hax    schedule 18.09.2018
comment
Примечание. Safari 12.0 в Mac OS X также имеет ту же проблему. - person hax; 19.09.2018
comment
Да, это уже исправлено в исходных кодах и уже отправлено в Safari Technology Preview. Попробуйте cdn.miss.cat/demo/ios12-safari-bug.html в Safari Technology Preview 65. Вы обнаружите, что в нем нет ошибки. - person sideshowbarker; 19.09.2018
comment
Я не верю, что основная причина ошибки - результат неправильного индекса; вместо этого, похоже, это вызвано пренебрежением проверкой неизменяемости объекта перед его изменением. Проблема с разрезом может иметь аналогичное объяснение, но это не то же самое, но, насколько я могу судить, патч для реверса не исправит. Вам следует подумать об открытии отчета об ошибке WebKit для проблемы среза. - person Zenexer; 19.09.2018
comment
@Zenexer Вы правы. Я написал этот ответ до того, как нашел bugs.webkit.org/show_bug.cgi?id= 188794 и посмотрите исходный код. Отредактирую свой ответ. - person hax; 19.09.2018

Я написал библиотеку, чтобы исправить ошибку. 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);
  }
})();

person Edire Fan    schedule 18.09.2018
comment
Обновить в любое время. Добро пожаловать, чтобы внести свой вклад. - person Edire Fan; 19.09.2018
comment
@zephi, я предполагаю, что запись по длине (this.length = this.length) вызовет копирование при записи, поэтому изменит адрес памяти массива и, таким образом, исправит поведение reverse. - person Cœur; 20.09.2018

Это ошибка в webkit. Хотя это было решено в их конце, но еще не отправлено с выпуском iOS GM. Одно из решений этой проблемы:

(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;
    }
  }
})();
person jsist    schedule 20.09.2018

Кажется, он не кэшируется, если количество элементов изменяется.
Мне удалось этого избежать.

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

person Atsushi Sasaki    schedule 20.09.2018