Если вы бежите console.log (['this'] === ['this'])
В JavaScript, что бы вы ожидали увидеть? Ну, было бы совершенно рационально ожидать, что Верно
был бы результатом, но вместо этого мы видим ложный
Анкет Давайте посмотрим на несколько тестов:
// Control Test console.log('this' === 'this'); // => true // Test 1 console.log(['this'] === ['this']); // => false // Test 2 const arr1 = ['this']; const arr2 = ['this']; console.log(arr1 === arr2); // => false // Test 3 const arr3 = ['this']; const arr4 = arr3; console.log(arr3 === arr4); // => true
Наш контрольный тест напрямую сравнить две идентичные возвраты строки Верно
как и ожидалось. Первые два теста, сравнивающие, казалось бы, идентичные массивы, журнал ЛОЖЬ
, но третьи журналы истинный
. Так что же на самом деле здесь? Давайте посмотрим, как JavaScript назначает разные типы данных переменным.
Типы данных
Примитивный
Это потенциально неожиданное поведение будет происходить только для определенных типов данных. В JavaScript данные могут быть классифицированы как примитивные значения или объекты. Примитив Типы включают в себя строку, число, бигинт, логический, неопределенный, символ и нулевый. Когда вы назначаете примитивный тип переменной, переменная содержит само значение. Это позволяет нам сравнивать два примитивных значения и интуитивно ожидать правильного ответа.
console.log('this' === 'this'); // => true console.log(1 === 1); // => true console.log(true === true); // => true const myString1 = 'this'; const myString2 = 'this'; console.log(myString1 === myString2); // => true
Объекты
Неприемные типы данных ведут себя по-разному. Эти типы данных классифицируются как Объекты и включайте такие вещи, как объекты, массивы и функции: типы данных, которые хранят сбор значений. За MDN, относительно того, почему функции и массивы включены в категорию Объекты :
Функции регулярные объекты с дополнительной способностью быть Callible Анкет
Массивы регулярные объекты, для которых существует определенная связь между целочисленными свойствами и длина
имущество.
Когда вы назначаете эти типы данных переменной, сама коллекция не хранится в переменной. Вместо этого хранится ссылка на коллекцию. Давайте внимательнее рассмотрим один из испытаний ранее:
const arr1 = ['this']; const arr2 = ['this']; console.log(arr1 === arr2); // => false
В этом примере, когда arr1
назначается, массив ['this']
хранится где -то в памяти, а сама переменная теперь является адресом местоположения памяти. Когда arr2
Инициализируется, массив хранится в другом месте в памяти (отдельно от первого массива), и этот второй адрес хранится в переменной. С тех пор arr1
и arr2
Иметь два отдельных адреса с двумя отдельными массивами, сравнение двух переменных приведет к ложный
Анкет
Давайте посмотрим на другой пример:
const arr3 = ['this']; const arr4 = arr3; console.log(arr3 === arr4); // => true
Здесь мы назначаем arr3
к arr4
Анкет Делая это, обе переменные указывают на один и тот же массив в памяти. Обе переменные имеют адрес с одним и тем же массивом в памяти, поэтому сравнение двух переменных приведет к Верно
Анкет
Примеры здесь охватывают массивы, но этот принцип также применяется к другим непримитивым типам данных:
const obj1 = {this: 'that'}; const obj2 = {this: 'that'}; console.log(obj1 === obj2); // => false const obj3 = {this: 'that'}; const obj4 = obj3; console.log(obj3 === obj4); // => true const func1 = () => {}; const func2 = () => {}; console.log(func1 === func2); // => false const func3 = () => {}; const func4 = func3; console.log(func3 === func4); // => true
Разрушительные модификации
Есть еще одна важная концепция, чтобы понять, что создает тот факт, что переменные, которые хранят ссылки на объекты в памяти. Поскольку несколько переменных могут указывать на одни и те же данные в памяти, важно проявлять осторожность при создании разрушительные модификации Анкет Взгляните на этот пример:
const arr3 = ['this']; const arr4 = arr3; arr4[0] = 'that'; console.log(arr3); // => ['that'] console.log(arr4); // => ['that']
В примере оба arr3
и arr4
указывают на тот же массив в памяти. Когда элемент в arr4
Изменен, он меняет массив в памяти. Поскольку обе переменные указывают на один и тот же массив в памяти, это изменение можно увидеть путем регистрации arr3
хотя arr3
не был напрямую изменен. Этот пример напрямую изменил элемент в массиве, но важно отметить, что Многие методы массива и объектов разрушительны и изменяют исходный объект . Я рекомендую просмотреть документацию для массивы и объекты Если вам нужно знать, какие методы разрушительны.
Если вам нужно присвоить массив или объект новой переменной и внести изменения, не влияя на оригинал, вам нужно сделать копию. Если есть только один уровень данных, неглубокую копию будет достаточно и будет легко выполнить. С ES6 неглубокая копия может быть быстро создана с помощью оператора распространения ( ...
):
const arr5 = [1, 2, 3]; const arr6 = [...arr5]; console.log(arr5 === arr6); // => false arr6[1] = 'b'; console.log(arr5); // => [1, 2, 3] console.log(arr6); // => [1, 'b', 3]
Поскольку мы сделали копию, Арр5
и arr6
Теперь укажите на два разных массива в памяти. Мы можем подтвердить это, сравнив два массива и регистрируя результат ( false
). Изменения могут быть внесены в массив, связанный с одной переменной, не влияя на другую.
Глубокие объекты
Объекты с вложенными уровнями немного сложнее. Мы все еще можем создать мелкую копию для разделения элементов верхнего уровня, но все, что вложенное внутри, будет сохранено в качестве ссылки на какой -то объект в памяти. Вот демонстрация:
const arr7 = [1, 2, [3, 4]]; const arr8 = [...arr7]; console.log(arr7 === arr8); // => false console.log(arr7[2] === arr8[2]); // => true arr8[1] = 'b'; arr8[2][1] = 'd'; console.log(arr7); // => [1, 2, [3, 'd']] console.log(arr8); // => [1, 'b', [3, 'd']]
Итак, мы можем продемонстрировать, что arr7
и arr8
указывают на два разных массива с первым Консоль.log
Анкет Однако, когда мы сравниваем подсолитель в индексе 2 в каждом массиве, мы обнаруживаем, что они оба указывают на один и тот же массив в памяти. Мутирующие элементы в верхнем уровне одного массива не повлияют на другое, но мутирующие элементы в суб-араме будут влиять оба . Это может быть немного запутанным, так что вот простая диаграмма:
Обе переменные указывают на другой массив на верхнем уровне, но эти два массива по -прежнему указывают на один и тот же массив для одного из элементов. Чтобы полностью разделить две наши переменные, нам нужно будет сделать глубокую копию.
Глубокое копирование
С JSON
Есть несколько способов сделать глубокую копию объекта или массива. Один из способов – использовать JSON stringify
и Parse
Методы:
const arr9 = [1, 2, [3, 4]]; const arr10 = JSON.parse(JSON.stringify(arr9)); console.log(arr9 === arr10); // => false console.log(arr9[2] === arr10[2]); // => false arr10[1] = 'b'; arr10[2][1] = 'd'; console.log(arr9); // => [1, 2, [3, 4]] console.log(arr10); // => [1, 'b', [3, 'd']]
Это работает достаточно хорошо во многих ситуациях, но не совсем копирует все типы данных. Любой неопределенное
Значения в объекте будут заменены NULL
Анкет Кроме того, любой Дата
Объекты будут преобразованы в строковое представление. Таким образом, скопированный массив будет полностью независимы от оригинала, но это не может быть точное копия
// undefined values are replaced with null console.log(JSON.parse(JSON.stringify([undefined]))); // => [null]
// Date objects are replaced with the string representation const myDate = new Date(); console.log(typeof myDate); // => object const myDateCopy = JSON.parse(JSON.stringify(myDate)); console.log(typeof myDateCopy); // => string
С библиотеками
Некоторые библиотеки JavaScript предоставляют методы создания глубоких копий. Одним из примеров этого будет Лодаш Клонип
метод . Если вы используете библиотеку, в которой есть такой метод, проверьте документацию, чтобы убедиться, что она работает так, как вам нужно.
С рекурсией
Вы также можете создать свою собственную функцию, чтобы сделать глубокую копию! Вот рекурсивная функция, которую я написал, чтобы сделать это:
function deepCloner(target) { if (Array.isArray(target)) { return target.map(deepCloner); } else if (target instanceof Date) { return new Date(target); } else if (typeof target === 'object' && target !== null) { const newObj = {}; for (const key in target) { newObj[key] = deepCloner(target[key]) } return newObj; } return target; }
Чтобы объяснить, что он делает:
- Если вход является массивом, переверните через массив с
карта
Метод, передайте каждый элемент вDeepCloner
функционируйте рекурсивно и верните новый массив. - Если ввод является объектом Date, создайте копию объекта Date с
Новая дата ()
Анкет - Если вход является объектом (но не значением
null
), перечислите через пары ключа/значение и перенесите значения рекурсивно вDeepCloner
функция - Если ввод не соответствует ни одному из вышеуказанных критериев, верните сам вход без модификации.
Я полагаю, что эта функция должна подходить для большинства ситуаций, но могут быть и другие краевые случаи, которые я еще не объяснил. Одна из таких ситуаций, о которой я могу придумать, это если ссылка на функцию хранится в исходном объекте. Глубокая копия все равно будет ссылаться на ту же функцию в памяти, хотя я не предвижу, что это проблема. Оставьте комментарий, если вы можете подумать о любых типах данных, которые это может не охватить! Я также включил в нижнюю часть этого поста, который показывает эту функцию в действии.
Вывод
То, как объекты упоминаются в переменных, может не быть интуитивно понятным для новичков в JavaScript. В первый раз, когда я заметил, что изменение элемента в массиве, связанном с одной переменной, может повлиять на другие переменные, которые я был полностью ошеломлен. Не зная, что JavaScript делает за кулисами с объектами, трудно понять, почему некоторые из этих поведений происходят. Теперь, когда я лучше понимаю, почему это происходит, мне гораздо легче писать код, чтобы избежать этого. Надеюсь, это тоже вам поможет! Спасибо за чтение!
Оригинал: “https://dev.to/fitzgeraldkd/object-references-why-thisthis-5l6”