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

Обновление до 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

Ответ 1

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

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

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

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

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

Эта ошибка была сообщена Apple 21 августа. Исправление вышло в репозиторий WebKit 27 августа и отправлено в Safari Technology Preview 64. Однако Safari 12.0 (последняя стабильная версия на момент написания этой статьи, выпущенная 17 сентября) все еще содержит баг.

Ответ 2

Я написал 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);
  }
})();

Ответ 3

Это ошибка в 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;
    }
  }
})();

Ответ 4

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

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