Console.log() показывает измененное значение переменной до фактического изменения значения

Этот бит кода я понимаю. Мы делаем копию A и называем ее C. Когда A изменен, C остается тем же самым

var A = 1;
var C = A;
console.log(C); // 1
A++;
console.log(C); // 1

Но когда A - массив, мы имеем различную sitiuation. Не только C изменится, но и изменится, прежде чем мы даже коснемся A

var A = [2, 1];
var C = A;
console.log(C); // [1, 2]
A.sort();
console.log(C); // [1, 2]

Может кто-нибудь объяснить, что произошло во втором примере?

Ответ 1

Ответ заостренный имеет хорошую информацию, но это не правильный ответ на этот вопрос.

Поведение, описанное OP, является частью ошибки, о которой впервые было сообщено в марте 2010 года, исправленной для Webkit в августе 2012 года, но на момент написания этой статьи еще не интегрированной в Google Chrome. Поведение зависит от того, открыто или закрыто окно отладки консоли во время передачи литерала объекта в console.log().

Выдержки из исходного сообщения об ошибке (https://bugs.webkit.org/show_bug.cgi?id=35801):

Описание от Митч Крамер 2010-03-05 11:37:45 PST

1) создать литерал объекта с одним или несколькими свойствами

2) console.log этого объекта, но оставьте его закрытым (не раскрывайте его в консоли)

3) изменить одно из свойств на новое значение

Теперь откройте этот console.log, и вы увидите, что у него есть новое значение по какой-то причине, даже несмотря на то, что оно было другим на момент его создания.

Я должен указать, что если вы откроете его, он сохранит правильное значение, если это не было ясно.

Ответ от разработчика Chromium:

Комментарий # 2 От Павла Фельдмана 2010-03-09 06:33:36 PST

Я не думаю, что мы когда-нибудь исправим это. Мы не можем клонировать объект при выгрузке его в консоль, и мы также не можем прослушивать изменения свойств объекта, чтобы сделать его всегда актуальным.

Мы должны убедиться, что существующее поведение ожидается.

Последовало много жалоб, и в конечном итоге это привело к исправлению ошибки.

Заметки об изменениях в патче, выпущенном в августе 2012 года (http://trac.webkit.org/changeset/125174):

На сегодняшний день сброс объекта (массива) в консоль приведет к тому, что свойства объектов будут считываться при расширении объекта консоли (т.е. лениво). Это означает, что выгрузка одного и того же объекта с изменением будет затруднена с помощью консоли.

Это изменение начинает генерировать сокращенные предварительные просмотры для объектов/массивов в момент их регистрации и передает эту информацию во внешний интерфейс. Это происходит только тогда, когда интерфейс уже открыт, он работает только для console.log(), а не для взаимодействия с живой консолью.

Ответ 2

Console.log() передается ссылка на объект, поэтому значение в консоли изменяется по мере изменения объекта. Чтобы избежать этого, вы можете:

console.log(JSON.parse(JSON.stringify(c)))

MDN предупреждает:

Пожалуйста, имейте в виду, что если вы регистрируете объекты в последних версиях Chrome и Firefox, то, что вы регистрируете в консоли, является ссылкой на объект, который не обязательно является "значением" объекта в момент времени, когда вы вызываете console.log(), но это значение объекта на момент открытия консоли.

Ответ 3

Массивы являются объектами. Переменные относятся к объектам. Таким образом, присваивание во втором случае копировало ссылку (адрес) в массив из "A" в "C". После этого обе переменные ссылаются на один и тот же объект (массив).

Примитивные значения, такие как числа, полностью копируются из одной переменной в другую в простых назначениях, подобных вашему. "A++;" оператор присваивает новое значение "А".

Другими словами, значение переменной может быть либо примитивным значением (число, логическое значение, null или строка), либо ссылкой на объект. Случай строковых примитивов немного странный, потому что они больше похожи на объекты, чем на примитивные (скалярные) значения, но они неизменны, поэтому можно утверждать, что они просто как числа.

Ответ 4

РЕДАКТИРОВАТЬ: Сохранение этого ответа только для сохранения полезных комментариев ниже.

@Esailija на самом деле прав - console.log() не обязательно будет регистрировать значение, которое имела переменная, когда вы пытались ее зарегистрировать. В вашем случае оба вызова console.log() будут регистрировать значение C после.

Если вы попытаетесь выполнить этот код как 5 отдельных операторов в консоли, вы увидите ожидаемый результат (сначала [2, 1], затем [1, 2]).

Ответ 5

Хотя это не будет работать в каждой ситуации, я решил использовать "точку прерывания" для решения этой проблемы:

mysterious = {property:'started'}

// prints the value set below later ?
console.log(mysterious)

// break,  console above prints the first value, as god intended
throw new Error()

// later
mysterious = {property:'changed', extended:'prop'}

Ответ 6

Эта проблема присутствует и в Safari. Как уже указывали другие в этом и аналогичных вопросах, консоли передается ссылка на объект, она печатает значение объекта на момент открытия консоли. Если вы, например, выполняете код в консоли напрямую, значения печатаются, как ожидается. Вместо JSON я предпочитаю распределять массивы (например, в вашем случае console.log([... C]);) и объекты: результат почти одинаковый, но код выглядит немного чище. У меня есть два фрагмента кода VS, которыми можно поделиться.

    "Print object value to console": {
      "prefix": "clo",
      "body": [
         "console.log(\"Spread object: \", {...$0});"
      ],
      "description": "Prints object value instead of reference to console, to avoid console.log async update"
   },
   "Print array value to console": {
      "prefix": "cla",
      "body": [
         "console.log(\"Spread array: \", [...$0]);"
      ],
      "description": "Prints array value instead of reference to console, to avoid console.log async update"
   }

Чтобы получить тот же вывод, что и с console.log(JSON.parse(JSON.stringify(c))), вы можете опустить строку, если хотите. Кстати, распространенный синтаксис часто экономит время и код.