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

432

Обновление на 2018.10.31

Эта ошибка была исправлена ​​в 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

abelyao
источник
41
Ошибка также подтверждена в macOS 10.14 Мохаве - 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.
Мистер Мистер

Ответы:

272

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

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

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

Все возвращаемые ссылки на массивы из вызовов to buildArray()будут ссылаться на одну и ту же память, а некоторые методы, такие как, toString()будут кэшировать свои результаты. Обычно для сохранения согласованности любая изменяемая операция с такими оптимизированными массивами копирует данные в отдельное пространство памяти и ссылается на него; этот шаблон называется копирование при записи , или сокращенно CoW.

В reverse()методе мутирует массив, поэтому он должен вызвать копию при записи. Но это не так, потому что оригинальный разработчик (Кит Миллер из Apple) пропустил reverse()дело, хотя он написал много тестов.

Об этой ошибке Apple сообщила 21 августа. Исправление появилось в репозитории WebKit 27 августа и было выпущено в Safari 12.0.1 и iOS 12.1 30 октября 2018 года.

Hax
источник
11
Примечание. Safari 12.0 в Mac OS X также имеет ту же проблему.
hax
17
Да, это уже исправлено в источниках и уже отправлено в Safari Technology Preview. Попробуйте cdn.miss.cat/demo/ios12-safari-bug.html в Safari Technology Preview 65. Вы обнаружите, что в нем нет ошибки.
Sideshowbarker
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
Обновлять в любое время. Добро пожаловать, чтобы внести свой вклад.
Edire Fan
14
@zephi, я думаю, что запись в length ( this.length = this.length) вызовет Copy On Write, поэтому изменит адрес памяти массива и, таким образом, исправит поведение reverse.
Cœur
14

Это ошибка в вебките . Хотя это было решено в их конце, но еще не выпущено с выпуском 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;
    }
  }
})();
jsist
источник
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>

Ацуши Сасаки
источник