Я получаю несогласованные результаты при передаче объектов между модулями в приложении Node JS или, возможно, просто не понимаю, что происходит!
В моем приложении, записанном в псевдокоде ниже, сначала требуется файл lib в корневой каталог приложения, за которым следуют другие службы, используемые приложением, которые также требуются в одной и той же библиотеке. Затем библиотека настраивается в корне приложения - это устанавливает переменную класса (this.foo
), которая была инициализирована как пустой объект в библиотеке, и, наконец, вызывается метод в одной из служб.
Свойство foo разрушено в верхней части служебного файла, и если я выхожу из него сразу, я получаю пустой объект (как и ожидалось). Когда я вызываю метод службы, после того, как foo был настроен в другом месте и ссылается на разрушенное свойство, я все равно получаю пустой объект. Если вместо этого я не разрушаю свойство и просто требую в библиотеке (Lib
), то в методе я получаю доступ к Lib.foo, я вижу сконфигурированное значение (то есть, как и ожидалось).
Мое подозрение в том, что деструктурированная переменная - это значение, которое не обновляется, а требуемая библиотека является ссылкой, но все, что я прочитал, предполагает, что ничего не передается по значению. Любые указатели будут оценены!
// main.js
// ========
const lib = require('./lib');
const service = require('./service');
lib.configure({ foo: "bar"});
service.run();
// service.js
// ========
const Lib = require('./lib'); // This is for the example only
const { foo } = require('./lib'); // I'd prefer to just do this
console.log(Lib.foo); // {} as expected
console.log(foo); // {} as expected
class Service {
run() {
console.log(foo); // {} - should be "bar"
console.log(Lib.foo) // "bar" as expected
}
}
module.exports = new Service();
// lib.js
// ======
class Lib {
constructor() {
this.foo = {}
}
configure({ foo }) {
this.foo = foo;
}
}
module.exports = new Lib();
Это правильное поведение.
Что делает оператор =
(оператор присваивания), он обновляет указатель на новую ячейку памяти, содержащую новое значение, это не изменяет значение, на которое мы сейчас указываем.
В случае примитивов, таких как строки, общая реализация заключается в фактическом копировании всего значения во время перераспределения переменной.
Дополнительно: обычно это называется String interning
Вот пример:
var x = "123"; // point the variable named "x" to a memory location containing the string "123"
var y = x; // point the variable named "y" to a new memory location into which we copy the data from the memory location that "x" is pointing to
x = "456"; // point the variable named "x" to a new memory location that that contains the value "456"
console.log(y); // still "123", "y" still points to a memory location containing the copy of "123"
И вот схема того, что происходит.
var x = "123";
var y = x;
x = "456";
Примечание: исходный "123" по-прежнему остается в памяти, но ничего больше не указывает на него, поэтому сборщик мусора в конечном итоге очистит его.
В случае объектов ситуация немного отличается. Вместо копирования значения мы копируем указатель на ячейку памяти, удерживая это значение, но повторное присвоение ведет себя одинаково.
var x = {}; // point the variable named "x" to a memory location containing the empty object
var y = x; // point the variable named "y" to the same memory location that "x" is pointing to
x = { name: "foo" }; // point the variable named "x" to a new memory location that contains the object { name: "foo" }
console.log(y); // still {}, "y" still points to a memory location containing the empty object
Вот схема того, что происходит:
var x = {};
var y = x;
x = { name: "foo" };
Когда вы destructure foo
из экземпляра Lib
, это не то же самое foo
, как один внутренний в этом случае, это новая переменная, указывающая на то же место памяти, как внутренней переменной. Это место содержит {}
.
Когда вы вызываете .configure
, вы обновляете место памяти, в котором указывается внутреннее значение, но разрушенная переменная все еще указывает на старое местоположение, содержащее {}
.
Если вы обновите объект, на который указывает foo
, вместо самой ссылки, все будет работать так, как вы ожидали:
configure({ foo }) {
Object.assign(this.foo, foo);
}
Я бы посоветовал не делать деструктурирования для постоянных геттеров, как и выше, что вы ожидаете сохранить состояние через обновления. Наличие дополнительных переменных увеличивает сложность (потому что увеличивает количество ссылок на поддержку), а также может привести к утечке памяти (устаревшие ссылки указывают на неиспользуемые данные, которые предотвращают сбор мусора).
Кроме того, методы деструктурирования из класса могут приводить к ошибкам, подобным выше, а также к непреднамеренному поведению this
.
Вы спасете себе несколько головных болей, если вы всегда будете называть Lib.foo
.
Поэтому я думаю, что вы смешиваете ссылки и ценности. Причина, по которой значение {foo} не изменяется, а значение Lib.foo - потому, что в случае foo вы назначаете копию значения Lib.foo в это время. Если вы хотите изменить данные Lib и распространить их, вам нужно будет работать с объектом (а не деконструировать аргумент, как вы сейчас делаете).
Надеюсь это поможет!