0

I know that for primitive datatype passing by reference doesn't work in javascript, so a workaround is to wrap them in object. But consider a scenario where the initial state of the variable is null and then the variable is reassigned as an Object. Now if that varible is passed as an argument to an external function will it be passed as reference or will it end up being undefined inside the function.

For reference consider this usecase:

in a mocha test for Login endpoint

Method 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);
})

in externalTest.spec.js

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

If i wrap the response in an Object it works. Why is it so?

Method 2

If i wrap the response in an Object it works. Why is it so?

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);
})

in externalTest.spec.js

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

NOTE: Pls let me know if you find there is a lack of clarity. Thanks in advance.

  • 2
    [Everything is passed by value in JavaScript.](https://stackoverflow.com/questions/42045586/whats-the-difference-between-a-boolean-as-primitive-and-a-boolean-as-property-o/42045636#42045636) Passing by reference doesn't exist. – Scott Marcus Jun 09 '21 at 14:03
  • @ScottMarcus can u pls explain why the first method doesn't worked, while the later one suceeded ? – DevDesignerSid Jun 09 '21 at 14:04
  • Objects are always passed by reference. Scalars are never passed by reference. When you replace the null with an object wrapping null, you trigger the change. You spell all this out in your own question. It seems to me you are not actually confused, just unhappy. Pro-tip: in javascript, you should try hard to avoid patterns that rely on mutation of shared objects. – Tom Jun 09 '21 at 14:07
  • Sorry, I don't have sufficient experience with chia or mocha to be able to do that, but I can tell you unequivocally that whatever the issue, it's not because of passing by value vs. by reference because JavaScript doesn't pass by reference. – Scott Marcus Jun 09 '21 at 14:07
  • 2
    Javascript *never* passes by reference, always value. The thing about objects is that the value of the variable/constant **is itself a reference**. Therefore, objects can be used to "simulate" a pass-by-reference feature but only as long as the different parts of the code maintain the same object reference. In your "non-working" example you're overwriting the value, i.e. dropping the reference. In your working example, you're only changing a property of the referenced object and the reference itself is maintained. – Lennholm Jun 09 '21 at 14:07
  • @Tom. No, that's incorrect. [JavaScript passes everything by value](https://stackoverflow.com/questions/42045586/whats-the-difference-between-a-boolean-as-primitive-and-a-boolean-as-property-o/42045636#42045636). Object variables ***hold*** a reference, but when passed, a copy is make, thus it's ***passed*** by value. – Scott Marcus Jun 09 '21 at 14:08
  • @Lennholm thank you. that makes sense but will it work the same way if i replace null with an **empty object**. i.e initialy having an empty object and reassigning it to another object. It doesnt seems to be so. – DevDesignerSid Jun 09 '21 at 14:12
  • I think the confusion here is that, with objects in javascript, it's kind of a hybrid approach. @Lennholm got it right -- when you pass an object to a function, you're passing the reference by value. – TKoL Jun 09 '21 at 14:12
  • @DevDesignerSid can you make some function and argument examples that don't require us to install a test suite? A minimum reproducible example? – TKoL Jun 09 '21 at 14:13
  • 1
    [Additional post that further explains](https://stackoverflow.com/questions/50840293/setting-a-variable-equal-to-another-variable/50840423#50840423) why it seems that JavaScript has pass-by-reference although it doesn't. – Scott Marcus Jun 09 '21 at 14:16
  • @TKoL i will try and update the question. Thank you for responding. – DevDesignerSid Jun 09 '21 at 14:17
  • 1
    @DevDesignerSid An empty object should work. Think of it this way: your approach breaks as soon as you do `response = ...` in the callback. `response.property = ...` works. Declare `response` as a `const` instead of a `let` to help you remember ;) – Lennholm Jun 09 '21 at 14:21
  • weired it seems. with all the above the above mentioned comments, why does this work in the first place ```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 – DevDesignerSid Jun 09 '21 at 14:31
  • @DevDesignerSid why are you expecting that not to work? – TKoL Jun 09 '21 at 14:37
  • @DevDesignerSid That last code snippet is not the same concept, you're actually passing the data to the function instead of the function reading the data from a variable in the common scope. – Lennholm Jun 09 '21 at 14:40
  • @Lennholm So are you suggesting that inside the exported function the response variable would have worked correctly, just like in the above snippet, Where as the issue exist when the function `it` tries to read the variable `response` from the common scope. – DevDesignerSid Jun 09 '21 at 14:49
  • @TKoL wouldnt it drop the reference if i simply reassign a variable from null to an object as illustrated in the above code snippet. if not what are the necessary conditions? it seems the issue depends on the execution context. – DevDesignerSid Jun 09 '21 at 14:54
  • Sorry @DevDesignerSid you gotta get more specific. Where are you expecting the reference to get dropped? You write `data = await delay(...);`, so at that point, once `delay` resolves, `data` has whatever value was resolved from `delay()`. Then you pass that directly to `someExtFunc` -- I don't see any opportunity for anything to get "dropped" – TKoL Jun 09 '21 at 14:56
  • @TKoL Okay. would pls explain what are the primary conditions for the reference to be dropped. Does it have anything to do with execution context? – DevDesignerSid Jun 09 '21 at 14:59
  • I'm not 100% confident I know what you mean by the reference being dropped. This is why a minimum reproducible example is so valuable. – TKoL Jun 09 '21 at 15:00
  • @TKoL could u pls checkout the fourth comment where Lennholm explain why the former method in the question never worked? – DevDesignerSid Jun 09 '21 at 15:02
  • I don't understand that. The example in your original post doesn't contain any runnable code. That's why it's not a minimum reproducible example. – TKoL Jun 09 '21 at 15:05
  • @TKoL pls refer to my answer and add suggest if any correction is required. Thank you! – DevDesignerSid Jun 09 '21 at 15:51
  • @DevDesignerSid I don't even know specifically what `method #1` and `method #2` mean, because there are no clear code examples. I think this entire question and your answer would be immensely improved with concrete examples of code. – TKoL Jun 09 '21 at 15:53
  • I also would say `heap` and `stack` are concepts that are not really relevant or required to understand what's going on with these variables. I don't see what they have to do with the question I think you're asking. – TKoL Jun 09 '21 at 15:57
  • @TKoL i have edited the question, hope this brings some clarity. – DevDesignerSid Jun 09 '21 at 16:04

3 Answers3

0

The most immediate comparison I can make is PHP, which has some pretty huge differences from Javascript in this regard.

First of all, in PHP if you pass an array to a function, and change the array in the function, the original array doesn't actually change. The array is fully copied by value if you pass it in:

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

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

When you var_dump($arr) in PHP, you might expect it to have 'key' => 'value', but in PHP this isn't the case.

The same example in javascript looks like this:

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

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

And you can see, unlike PHP's array example, directly changing the object in Javascript changes it in both places.

So that brings us to the next big difference - PHP has a full pass-by-reference functionality, like so:

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

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

The & symbol in PHP means pass by reference, so in that example in PHP you would see key => value when you var_dump($arr).

However, this & symbol doesn't actually make PHP's behaviour with this variable identical to Javascripts behaviour. Because in PHP, it is a TRUE pass by reference, you can do this:

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

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

So when you var_dump($arr), it would output 2, because the func is actually changing what $arr means at a fundamental level.

In comparison in Javascript, the func function cannot change what obj actually MEANS, all it can do is mutate what you sent it. So if you try to do the same in javascript:

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

function func(obj) {
   obj = 2;
}

... then when you console.log(obj) you'll still get the empty object you started with. func cannot change what the outside world thinks obj refers to by using the = symbol.

TKoL
  • 13,158
  • 3
  • 39
  • 73
0

Actually i think i have some idea on how this works.

Why method #1 failed ?

When the external function is required and invoked we pass the response variable to it. This response variable currently has the value null. And before function reassigns response variable to a new response Object it is actually not updated in the execution context of the exported function, since null is one of the scalar primitive values (Number, String, Boolean, undefined, null, Symbol). For scalar primitive values javascript use a stack memmory structure and on every reassignment we will add a new value on top of the stack and the varible is pointed to it. So the exported function doesnt know about the new value pushed to the stack but only a refernce to the old value.

Why method #2 succeeded

Now when we wrap the response as an object we effectievly uses two memory structure one is the stack and the other is a heap. The original value of response is stored on heap and a refernce to this is maintained in the stack. So when the before function updates the key of the data Object (response),it wont actualy push a new value on top of the stack rather it updates in the heap, therby keeping the same refernce. And thus when the it inside the exported function executes it can find the original data.

Hope this helps.

0

I've ran your code and the problem is pretty straight forward. I'll annotate it with which lines run first for you to see the order of events

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);
   })
}

The important bits here are (2) and (5). You're saying data is null, and then require expternalTest.spec.js and pass it whatever data is (which is null).

So eternalTest.spec.js just gets data as null immediately, and it will never and can never have anything else. You've passed it null, so that's it, that's what it is.

In your second method, the execution order is exactly the same, but you haven't passed it null, you've passed it the object. So of course as the object changes (and the response property gets a value), what expternalTest.spec.js sees as data changes to, because data is that object!

TKoL
  • 13,158
  • 3
  • 39
  • 73