Передача переменных по ссылке в javascript - передача переменных во внешний тест в мокко

Я знаю, что для примитивного типа данных передача по ссылке не работает в javascript, поэтому обходным путем является обернуть их в объект. Но рассмотрим сценарий, в котором начальное состояние переменной равно null, а затем переменной присваивается значение Object. Теперь, если эта переменная передается в качестве аргумента внешней функции, она будет передана как ссылка или в конечном итоге окажется undefined внутри функции.

Для справки рассмотрим этот вариант использования:

в тесте mocha для конечной точки Вход

Способ 1

describe('Login Endpoint Test', function(){
   let response = null;
   
   before('test pre-requisites', async function(){
      this.timeout(15000);
      response = await endpointCall(); //returns response Object
   });   

   it('simple endpoint test', function(){
      //response is availble here.
      response.should.have.status(200);
   });
  
   /*Importing an external testfile.*/
   require('./externalTest.spec.js')(response);
})

в externalTest.spec.js

module.exports = (response) => {
   it('external test', function(){
     console.log(response);  // null;
   })
}
   

Если я оберну ответ в Object, он сработает. Почему это так?

Способ 2

Если я оберну ответ в Object, он сработает. Почему это так?

describe('Login Endpoint Test', function(){
   let data = {response: null};
   
   before('test pre-requisites', async function(){
      this.timeout(15000);
      data.response = await endpointCall(); //returns response Object
   });   

   it('simple endpoint test', function(){
      //response is availble here.
      data.response.should.have.status(200);
   });
  
   /*Importing an external testfile.*/
   require('./externalTest.spec.js')(data);
})

в externalTest.spec.js

module.exports = (data) => {
   it('external test', function(){
     console.log(data.response);  // response object;
   })
}
   

ПРИМЕЧАНИЕ. Пожалуйста, дайте мне знать, если вы обнаружите неясность. Заранее спасибо.


person DevDesignerSid    schedule 09.06.2021    source источник
comment
В JavaScript все передается по значению. Передачи по ссылке не существует.   -  person Scott Marcus    schedule 09.06.2021
comment
@ScottMarcus, не могли бы вы объяснить, почему первый метод не сработал, а второй сработал?   -  person DevDesignerSid    schedule 09.06.2021
comment
Объекты всегда передаются по ссылке. Скаляры никогда не передаются по ссылке. Когда вы заменяете нуль объектом, обертывающим нуль, вы инициируете изменение. Вы все это излагаете в своем вопросе. Мне кажется, вы на самом деле не запутались, а просто недовольны. Совет: в javascript вы должны изо всех сил стараться избегать шаблонов, основанных на мутации общих объектов.   -  person Tom    schedule 09.06.2021
comment
Извините, у меня нет достаточного опыта работы с чиа или мокко, чтобы сделать это, но я могу однозначно сказать вам, что какой бы ни была проблема, это не из-за передачи по значению, а не по ссылке, потому что JavaScript не проходит по ссылке .   -  person Scott Marcus    schedule 09.06.2021
comment
Javascript никогда не передает по ссылке, всегда значение. Суть объектов в том, что значение переменной/константы само по себе является ссылкой. Следовательно, объекты можно использовать для имитации функции передачи по ссылке, но только до тех пор, пока разные части кода поддерживают одну и ту же ссылку на объект. В вашем нерабочем примере вы перезаписываете значение, т.е. удаляете ссылку. В вашем рабочем примере вы только изменяете свойство объекта, на который указывает ссылка, а сама ссылка сохраняется.   -  person Lennholm    schedule 09.06.2021
comment
@Том. Нет, это неправильно. JavaScript передает все по значению. Объектные переменные держат ссылку, но при передаче создается копия, поэтому она передается по значению.   -  person Scott Marcus    schedule 09.06.2021
comment
@Леннхольм, спасибо. это имеет смысл, но будет ли это работать так же, если я заменю null на пустой объект. т.е. изначально иметь пустой объект и переназначить его другому объекту. Вроде не так.   -  person DevDesignerSid    schedule 09.06.2021
comment
Я думаю, что путаница здесь в том, что с объектами в javascript это своего рода гибридный подход. @Lennholm понял правильно - когда вы передаете объект функции, вы передаете ссылку по значению.   -  person TKoL    schedule 09.06.2021
comment
@DevDesignerSid, можете ли вы сделать несколько примеров функций и аргументов, которые не требуют от нас установки набора тестов? Минимальный воспроизводимый пример?   -  person TKoL    schedule 09.06.2021
comment
Дополнительный пост, который далее объясняет, почему кажется, что JavaScript имеет передачу по ссылке, хотя это не так.   -  person Scott Marcus    schedule 09.06.2021
comment
@TKoL, я постараюсь обновить вопрос. Спасибо за ответ.   -  person DevDesignerSid    schedule 09.06.2021
comment
@DevDesignerSid Пустой объект должен работать. Подумайте об этом так: ваш подход ломается, как только вы делаете response = ... в обратном вызове. response.property = ... работает. Объявите response как const вместо let, чтобы лучше запомнить ;)   -  person Lennholm    schedule 09.06.2021
comment
утомил кажется. со всеми вышеупомянутыми вышеупомянутыми комментариями, почему это работает в первую очередь function delay(t, val) { return new Promise(function(resolve) { setTimeout(function() { resolve(val); }, t); }); } let someFunc = async function(){ let data = null; data = await(delay(3000, {'key':'value'})); console.log('inside someFunc : ', data); someExtFunc(data); } let someExtFunc = (data) => { console.log('inside someExtFunc :',data); } someFunc(); @Lennholm   -  person DevDesignerSid    schedule 09.06.2021
comment
@DevDesignerSid, почему вы ожидаете, что это не сработает?   -  person TKoL    schedule 09.06.2021
comment
@DevDesignerSid Этот последний фрагмент кода не является той же концепцией, вы фактически передаете данные функции вместо того, чтобы функция считывала данные из переменной в общей области.   -  person Lennholm    schedule 09.06.2021
comment
@Lennholm Итак, вы предполагаете, что внутри экспортированной функции переменная ответа работала бы правильно, как и в приведенном выше фрагменте, где проблема возникает, когда функция it пытается прочитать переменную response из общей области.   -  person DevDesignerSid    schedule 09.06.2021
comment
@TKoL не удалит ссылку, если я просто переназначу переменную с нуля на объект, как показано в приведенном выше фрагменте кода. если нет, то каковы необходимые условия? кажется, проблема зависит от контекста выполнения.   -  person DevDesignerSid    schedule 09.06.2021
comment
Извините, @DevDesignerSid, вам нужно уточнить. Где вы ожидаете, что ссылка будет удалена? Вы пишете data = await delay(...);, поэтому в этот момент, как только delay разрешается, data имеет любое значение, полученное из delay(). Затем вы передаете это непосредственно someExtFunc -- я не вижу никакой возможности, чтобы что-то было отброшено.   -  person TKoL    schedule 09.06.2021
comment
@TKoL Хорошо. Пожалуйста, объясните, каковы основные условия для удаления ссылки. Имеет ли это какое-либо отношение к контексту выполнения?   -  person DevDesignerSid    schedule 09.06.2021
comment
Я не уверен на 100%, что знаю, что вы имеете в виду, говоря об удалении ссылки. Вот почему минимальный воспроизводимый пример так ценен.   -  person TKoL    schedule 09.06.2021
comment
@TKoL, не могли бы вы проверить четвертый комментарий, где Леннхольм объясняет, почему предыдущий метод в вопросе никогда не работал?   -  person DevDesignerSid    schedule 09.06.2021
comment
Я этого не понимаю. Пример в исходном сообщении не содержит исполняемого кода. Вот почему это не минимально воспроизводимый пример.   -  person TKoL    schedule 09.06.2021
comment
@TKoL, пожалуйста, обратитесь к моему ответу и добавьте предложение, если требуется какое-либо исправление. Благодарю вас!   -  person DevDesignerSid    schedule 09.06.2021
comment
@DevDesignerSid Я даже не знаю конкретно, что означают method #1 и method #2, потому что нет четких примеров кода. Я думаю, что весь этот вопрос и ваш ответ будут значительно улучшены с помощью конкретных примеров кода.   -  person TKoL    schedule 09.06.2021
comment
Я бы также сказал, что heap и stack — это понятия, которые на самом деле не важны и не требуются для понимания того, что происходит с этими переменными. Я не понимаю, какое отношение они имеют к вопросу, который, я думаю, вы задаете.   -  person TKoL    schedule 09.06.2021
comment
@TKoL я отредактировал вопрос, надеюсь, это внесет некоторую ясность.   -  person DevDesignerSid    schedule 09.06.2021


Ответы (3)


Самое непосредственное сравнение, которое я могу провести, это PHP, который в этом отношении имеет довольно большие отличия от Javascript.

Прежде всего, в PHP, если вы передаете массив функции и изменяете массив в функции, исходный массив фактически не изменяется. Массив полностью копируется по значению, если вы передаете его:

$arr = [];
func($arr);
var_dump($arr);

function func($arr) {
   $arr['key'] = 'value';
}

Когда вы используете var_dump($arr) в PHP, вы можете ожидать, что он будет иметь 'key' => 'value', но в PHP это не так.

Тот же пример на javascript выглядит так:

let obj = {};
func(obj);
console.log(obj);

function func(obj) {
   obj['key'] = 'value';
}

И вы можете видеть, что в отличие от примера с массивом PHP, прямое изменение объекта в Javascript меняет его в обоих местах.

Итак, это подводит нас к следующему большому отличию — PHP имеет полную функциональность передачи по ссылке, например:

$arr = [];
func($arr);
var_dump($arr);

function func(&$arr) {
   $arr['key'] = 'value';
}

Символ & в PHP означает передачу по ссылке, поэтому в этом примере в PHP вы увидите key => value, когда вы var_dump($arr).

Однако этот символ & на самом деле не делает поведение PHP с этой переменной идентичным поведению Javascripts. Поскольку в PHP это ИСТИННЫЙ проход по ссылке, вы можете сделать это:

$arr = [];
func($arr);
var_dump($arr);

function func(&$arr) {
   $arr = 2;
}

Так что, когда вы var_dump($arr), будет выведено 2, потому что func на самом деле меняет то, что $arr означает на фундаментальном уровне.

Для сравнения в Javascript функция func не может изменить то, что на самом деле ЗНАЧИТ obj, все, что она может сделать, это изменить то, что вы ей отправили. Итак, если вы попытаетесь сделать то же самое в javascript:

let obj = {};
func(obj);
console.log(obj);

function func(obj) {
   obj = 2;
}

... затем, когда вы console.log(obj), вы все равно получите пустой объект, с которого вы начали. func не может изменить то, что думает внешний мир, на что ссылается obj, используя символ =.

person TKoL    schedule 09.06.2021

На самом деле я думаю, что у меня есть некоторое представление о том, как это работает.

Почему метод №1 не удался?

Когда требуется и вызывается внешняя функция, мы передаем ей переменную ответа. Эта переменная ответа в настоящее время имеет значение null. А функция before переназначает переменную ответа новому объекту ответа, который на самом деле не обновляется в контексте выполнения экспортируемой функции, поскольку null является одним из скалярных примитивов значения (число, строка, логическое значение, неопределенное значение, нуль, символ). Для скалярных примитивных значений javascript использует структуру памяти stack, и при каждом переназначении мы будем добавлять новое значение поверх стека, и переменная указывает на него. Таким образом, экспортируемая функция не знает о новом значении, помещенном в стек, а только ссылается на старое значение.

Почему метод № 2 оказался успешным

Теперь, когда мы оборачиваем ответ как объект, мы эффективно используем две структуры памяти: одна — стек, а другая — куча. Исходное значение ответа хранится в куче, а ссылка на него сохраняется в стеке. Таким образом, когда функция before обновляет ключ объекта данных (response), она фактически не помещает новое значение в верхнюю часть стека, а обновляет его в куче, т. е. сохраняя ту же ссылку. Таким образом, когда it внутри экспортируемой функции выполняется, она может найти исходные данные.

Надеюсь это поможет.

person DevDesignerSid    schedule 09.06.2021

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

1 describe('Login Endpoint Test', function(){ 
   2 let data = null;
   
   3 before('test pre-requisites', async function(){
      8 this.timeout(15000);
      9 data.response = await endpointCall(); //returns response Object
   });   

   4 it('simple endpoint test', function(){
      //response is availble here.
      10 data.response.should.have.status(200);
   });
  
   /*Importing an external testfile.*/
   5 require('./externalTest.spec.js')(data);
})

// different file
6 module.exports = (data) => {
   7 it('external test', function(){
     11 console.log(data.response);
   })
}

Важными битами здесь являются (2) и (5). Вы говорите data is null, а потом require expternalTest.spec.js and pass it whatever data is (which is null).

Таким образом, everTest.spec.js просто немедленно получает данные как null, и у него никогда не будет и не может быть ничего другого. Вы прошли его null, так что все, вот что это такое.

Во втором методе порядок выполнения точно такой же, но вы не передали ему значение null, вы передали ему объект. Так что, конечно, когда объект изменяется (и свойство response получает значение), то, что expternalTest.spec.js видит как data, изменяется на, потому что data и есть этот объект!

person TKoL    schedule 09.06.2021
comment
Да???? Я только что уточнил это в своем ответе. В любом случае спасибо, что заглянули. - person DevDesignerSid; 09.06.2021