Если вы бежите 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”