0

Check it out following code. The counter is declared as const. When you run it, it should not allow it to change in any situation!

function increment(counter) {
 counter += 1; 
}

function test() {
    const counter = 1;
    increment(counter);
}

When transpiled, it produces the following code. Which allows const counter to be incremented!

function increment(counter) {
  counter += 1; // Counter is declared const, still it can be changed!
}

function test() {
  var counter = 1;
  increment(counter);
}

I'm just trying to understand, whether it is a problem with Babel transpilation or with JavaScript specification.

Edit: I know that unlike ES6, the current version of JS does not support const. My concern is, if I use transpiled JavaScript, I may encounter unknown const related bug. Should that be okay?

Aamir Maniar
  • 1
  • 1
  • 2
  • 3
    `counter` isn't incremented. It's passed by *value* -- not *reference* -- to the `increment` function. Examine `const counter`'s value after the `increment` function has been called and you'll see that it hasn't changed. I think we can both agree that, eg, literal `1` is constant. As a thought experiment, what happens when you call `increment(1)`? Do all the `1`s in your source code turn into `2`s? – Kelvin Sherlock Feb 05 '18 at 03:08
  • Your code example won't even increment the original `counter` variable in actual ES6 because of the pass by value, not reference (which means a non-const copy of `counter` is what is passed to the function. You can't make function parameters `const` either. So, this has nothing to do with Babel. This is how Javascript works. – jfriend00 Feb 05 '18 at 04:33
  • The title of your question is a wrong assertion about how passing arguments work in Javascript. You appear to be thinking that passing a `const` variable as a function argument will "retain" the `const` for that function parameter. Javascript does not do that. Function parameters are copies or pointers. They aren't the original `const` variable. – jfriend00 Feb 05 '18 at 05:11
  • All parameters are always passed *by value* in Javascript. For primitives (`Number`, `String`, `Boolean`) this means the value is copied. So whatever you do with the (copied) value inside the function will have no effect on the value that the caller passed in. When you pass an object or an array to a function, the parameter is still passed *by value*, but those values are actually references (like, but not quitte the same as, pointers), meaning they just contain a reference to the actual object, which lives on the heap. – Stijn de Witt Jan 14 '19 at 08:07

2 Answers2

8

Counter is declared const, still it can be changed!

There are two identifiers with name counter in the transpiled code:

  • The parameter counter inside increment.
  • The variable counter inside test.

They are completely independent!

Parameters are never "constant", they can always be assigned a new value. It has no impact on what you passed to the function, since all arguments are passed by value, i.e. increment is passed the value of the variable counter which is then assigned to the parameter counter.

We can easily verify this by logging the value of the variable before and after the function call:

function increment(counter) {
 counter += 1; 
}

function test() {
    var counter = 1;
    console.log('value before', counter);
    increment(counter);
    console.log('value after', counter);
}

test();

I'm just trying to understand, whether it is a problem with Babel transpilation or with JavaScript specification

With neither. It works exactly has specified, no matter whether you are using var or const to declare counter.

My concern is, if I use transpiled JavaScript, I may encounter unknown const related bug.

No, that won't be the case. What makes const special? That you cannot assign a new value to it and that it is blocked scoped. So lets have a look what Babel does when you are violating these two constraints:

Input:

const foo = 21;
foo = 42;

Babel output:

Babel refuses to transpile the code with the error

SyntaxError: intput.js: "foo" is read-only
  1 | const foo = 42;
> 2 | foo = 21;
    | ^
  3 |

Input:

{
  const foo = 21;
}
console.log(foo); // trying to access const outside of block

Babel output:

"use strict";

{
  var _foo = 21;
}
console.log(foo); // trying to access const outside of block

Babel renamed the block-scoped variable. foo doesn't exist (as expected).

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • only primitives are passed by value. Objects including functions are passed by reference and can be mutated. Probably worth mentioning in your answer – coagmano Feb 05 '18 at 03:49
  • 2
    @FredStark not really the same thing. The reference itself is still passed by value, even for an object. If you pass an object to a function and re-assign to the parameter, that doesn't propagate either: `function test (foo) { foo = { bar: 'edit?' } } const in = { foo: 'bar' }; test(in); // in is still { foo: 'bar' }` – Patrick Roberts Feb 05 '18 at 03:52
  • sure, but that value IS a reference to the original object, and changes to the object done inside the function affects the original – coagmano Feb 05 '18 at 03:56
  • 2
    @FredStark: Yes, the value *is* a reference. But that's not the same as [pass *by* reference](https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_reference) (also https://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value) – Felix Kling Feb 05 '18 at 04:18
  • I used the counter just a typical constant variable, and not as primitive or reference type. It could have been any variable or an object of some class! – Aamir Maniar Feb 05 '18 at 05:51
  • So you mean to say if I declare any const (primitive data type or complex object) it will be converted to a variable. Right? If yes, is this the same behavior in ES5 and ES6? Any reference? – Aamir Maniar Feb 05 '18 at 06:02
  • @AamirManiar: It doesn't matter whether you use `var`, `let` or `const` to declare the variable, nor does it matter which value it has. JavaScript is and always has been a pass *by* value language. Is that what you wanted to know? – Felix Kling Feb 05 '18 at 07:06
  • Logically when you declare `const` it should be assigned or changed! But when you pass it through function, you can change it. That's weird. – Aamir Maniar Feb 05 '18 at 07:41
  • @AamirManiar: *"But when you pass it through function, you can change it"* I have demonstrated that `counter` inside `test` does not change. So I am not sure what you mean here. Maybe you think that the *value* changes? First of all, that's not the case either, because primitive values are not mutable. Secondly, all that `const` does it disallowing the *assignment of a new value* to the "variable". I.e. you cannot do `const foo = 42; foo = 21;`. It does not have any impact on the concrete value itself though. – Felix Kling Feb 05 '18 at 07:49
  • 1
    @AamirManiar: And as I have said in my answer, the `counter` **paramater** inside `increment` has **nothing** to do with the `counter` constant in `test`. They just happen to have identical names. A coincidence, but maybe that's confusing you. – Felix Kling Feb 05 '18 at 07:52
  • To explain pass-by-value, think of the `const counter` inside `test()` as a piece of paper with the number `1` written on it. Now imagine we want to call the function `increment(counter)`. This function has a parameter `counter`, which you may imagine as another piece of paper. When the parameter is passed *by value*, we simply copy the number onto the new piece of paper. Inside `increment` we can change that value, but it has no effect on the original number. – Stijn de Witt Jan 14 '19 at 08:18
  • You can expand on this to explain how object references work. Imagine the same pieces of paper as in the above example, but this time we are not writing simple numbers on them but *object references*, which you may imagine as telephone numbers. When you want to change the object you call the telephone number. I think it is easy to see now how passing the reference by value means that the called function will be able to change the object. This also makes it clear that `const myObject = new SomeObject()` still allows you to change fields of the object. – Stijn de Witt Jan 14 '19 at 08:21
1

There are two things going on here:

  1. The function is passing the value of your variable and not the variable itself (as other answers have explained)
  2. The way in which babel transpiles const to var and still keeps the contract of not being re-assignable. I'll explain this part in my answer:

Why does Babel does not properly handle const?

The thing about babel and const, is that it only works via static analysis

The transpilation of working code converts const into var as that's all that exists in ES5.

const a = 0
const b = a + 1;
// becomes
var a = 0;
var b = a + 1;

The magic happens when babel finds you assigning a different value to your variable like so:

const a = 1;
a = a + 1;
// becomes
function _readOnlyError(name) { throw new Error("\"" + name + "\" is read-only"); }

var a = 1;
a = (_readOnlyError("a"), a + 1);

Because babel can tell it will throw an error by reading your code, it replaces the point where the error happens with an error.

So in effect for 99% of cases, const prevents re-assignment when converted to ES5.

Ergo Babel does properly handle const

The reason why your code wasn't transpiled to throw an error is because of part 1.

Community
  • 1
  • 1
coagmano
  • 5,542
  • 1
  • 28
  • 41
  • This isn't the issue here. The issue is that `counter` is passed by value, thus what is passed is a copy so you're freely allowed to modify the copy which is not `const`. FYI, Javascript doesn't have `const` function parameters. – jfriend00 Feb 05 '18 at 04:34
  • 1
    He was asking why const was being turned into var, I answered that. The reason he wasn't getting the error generating code is because of the function parameters which is already in other people's answers, which I mention – coagmano Feb 05 '18 at 04:37
  • His error doesn't change the content of the question. Yes the error needs correcting, and by the time I wrote my answer that had already been done by the previous two answers – coagmano Feb 05 '18 at 04:37
  • I've edited my answer to make that clearer and put it at the start – coagmano Feb 05 '18 at 04:42
  • Why does your point #2 refer to const and Babel here? That has absolutely nothing to do with what's going on for the OP. There is no `const` for a function parameter in Javascript. That has nothing to do with Babel and what it does or doesn't do for implementing `const`. – jfriend00 Feb 05 '18 at 05:02
  • The question is titled "Why does Babel does not properly handle const?", Explaining his mistake in the example provided does not answer the question of how Babel handles const. So I added information not provided by the other answer as to how Babel handles const – coagmano Feb 05 '18 at 05:05
  • Well, the question is based on a wrong assumption about what passing a `const` variable to a function should do in Javascript. So, the whole title of the question contains a wrong assertion. What Babel does or doesn't do with `const` has nothing to do with the OP's code. The OP's code allows you to change the function parameter even in a fully implemented ES6 environment and no Babel because there are no `const` function parameters in Javascript. Function parameters are non-const copies (for primitives) or non-const pointers (for objects). – jfriend00 Feb 05 '18 at 05:08
  • There is more to the question than the code sample provided, especially when it is clear that it is a contrived example that only exists to explore the question, not to the the entire question itself. Otherwise the question would be titled "Why does this function not throw an assignment error" – coagmano Feb 05 '18 at 05:08
  • You are correct to say that the error in his assumption is important. That's why (in my edit) it's part 1 of why this doesn't work the way he expects – coagmano Feb 05 '18 at 05:10