ECMAScript specification: Variable Assignment - javascript

I've heard multiple explanations what the idea behind assignment in ECMAScript specification is. But which one does the EMCAScript spec actually apply? Let's take a look at a simple practical example:
Assignment by copying values, aka "the classic C++/Java approach".
let a = 4 // assign value 4 to variable a
let b = a // copy value of a into b
a = 5 // reassign a with value 5
Assignment by sharing a reference. Primitive values are immutable, Objects are mutable.
let a = 4 // Assigns a with a reference to the immutable value 4
let b = a // copies the reference of a to b
a = 5 // reassigns a with a reference to 5 (in-place mutation would cause b to change as well)
Arguments for Variant 1:
Most importantly, ECMAScript itself seems to specify the assignment as copying values.
https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s
AssignmentExpression : LeftHandSideExpression = AssignmentExpression
1. If LeftHandSideExpression is neither an ObjectLiteral nor an ArrayLiteral, then
a. Let lref be ? Evaluation of LeftHandSideExpression.
b. If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then
i. Let rval be ? NamedEvaluation of AssignmentExpression with argument lref.[[ReferencedName]].
c. Else,
i. Let rref be ? Evaluation of AssignmentExpression.
ii. Let rval be ? GetValue(rref).
d. Perform ? PutValue(lref, rval).
e. Return rval.
2. Let assignmentPattern be the AssignmentPattern that is covered by LeftHandSideExpression.
3. Let rref be ? Evaluation of AssignmentExpression.
4. Let rval be ? GetValue(rref).
5. Perform ? DestructuringAssignmentEvaluation of assignmentPattern with argument rval.
6. Return rval.
Branch 1d is particularly interesting for primitives, which ultimately leads to the SetMutableBinding method of the EnvironmentRecord, which states:
https://tc39.es/ecma262/#sec-declarative-environment-records-setmutablebinding-n-v-s
It attempts to change the bound value of the current binding of the
identifier whose name is N to the value V.
Arguments for Variant 2:
On the other hand a lot of documentation suggests that example 1 might be the case. Like the explicit mentioning of immutability of primitive values, which only really makes sense if there would be a possibility to affect other variables by doing so (by having multiple references point to that value).
https://developer.mozilla.org/en-US/docs/Glossary/Primitive?retiredLocale=de
All primitives are immutable; that is, they cannot be altered. It is important not to confuse a primitive itself with a variable assigned a primitive value. The variable may be reassigned to a new value, but the existing value can not be changed in the ways that objects, arrays, and functions can be altered.
This explanation in Eloquent JavaScript I assume also talks about a reference (binding) pointing to a different value.
https://eloquentjavascript.net/04_data.html
Even though number values don’t change, you can use a let binding to keep track of a changing number by changing the value the binding points at.
My guess is, the specification is rather general. Which applies better to variant 1, but it is free to the implementor to implement it the way you want. Is that correct?

Assignment always copies by value, you can easily check that option 2 is not what happens.
What may be sometimes confusing is that if you copy (by value) a pointer and then modify the value that it points to, then the value the original variable points to will always be modified (because they both point to the same memory address). And things like objects and arrays are secretly pointers.
let a = {x: 3};
let b = a;
b.x = 5;
// Now a.x is 5 as well because a and b point to the same memory address
b = {x: 7};
// a.x is still 5 because we changed the value of b

Related

Searching toFixed with "in" operator - How does the "in" operator work on primitives?

I started reading YDKJS for fun - and found that he's written:
we can do stuff like:
var num = (1.2).toFixed(1)
so - this means that toFixed is being invoked as a member method from an integer value.
So why doesn't this work??
"toFixed" in 1.222
But this works:
"toFixed" in new Number(1.222)
Page 268 of the Es262 spec states:
RelationalExpression : RelationalExpression in ShiftExpression
[...]
Let rref be the result of evaluating ShiftExpression.
Let rval be ? GetValue(rref).
If Type(rval) is not Object, throw a TypeError exception.
So in other words: You can't use in on numbers. Thats just the way it is defined.
new Number however does not create a number, but a number object (an object that inherits from the Number.prototype). That's why you can use in on it, cause its an actual object.
You can still do 12..toFixed(), thats because of a very interesting construct in the spec: The abstract GetValue operation, which will be called when you access a property¹, does call toObject if the target (12 in this case) is not an object, and that will then do the following:
Return a new Number object whose [[NumberData]] internal slot is set to argument.
So in other words: 12..toFixed() is exactly the same as new Number(12).toFixed().
¹ interestingly accessing the property itself does not actually do that according to the spec, if you do a.b that will only look up the value of a and create a reference (Reference(a, "b")). The actual property lookup happens when GetValue gets called on it (however I don't know of any case were a Reference gets lost without calling GetValue on it).

How the Number function interacts with the operator new

In this case, the built-in function Number was found. I would like to consider it, because the questions I ask below are found in other functions in the specification.
If we open the specification, we see the following:
20.1.1.1 Number ( value )
When Number is called with argument value, the following steps are
taken:
If no arguments were passed to this function invocation, let n be
+0.
Else, let n be ? ToNumber(value).
If NewTarget is undefined, return n.
Let O be ? OrdinaryCreateFromConstructor(NewTarget,
"%NumberPrototype%", « [[NumberData]] »).
Set O.[[NumberData]] to n.
Return O.
Based on this, I want to ask a few questions:
NewTarget where does it come from?
If this algorithm is used both for the constructor and for a simple conversion to a number, then if NewTarget = undefined this conversion to a number, if NewTarget != undefined, then this calls this function as a constructor.
Let's go further to clarify all the points.
12.3.3.1 Runtime Semantics: Evaluation
NewExpression:new NewExpression
Return ? EvaluateNew(NewExpression, empty).
MemberExpression:new MemberExpressionArguments
Return ? EvaluateNew(MemberExpression, Arguments).
12.3.3.1.1 Runtime Semantics: EvaluateNew ( constructExpr, arguments )
The abstract operation EvaluateNew with arguments constructExpr, and
arguments performs the following steps:
Assert: constructExpr is either a NewExpression or a
MemberExpression.
Assert: arguments is either empty or an Arguments.
Let ref be the result of evaluating constructExpr.
Let constructor be ? GetValue(ref).
If arguments is empty, let argList be a new empty List.
Else,
a. Let argList be ArgumentListEvaluation of arguments.
b. ReturnIfAbrupt(argList).
If IsConstructor(constructor) is false, throw a TypeError exception.
Return ? Construct(constructor, argList).
When you call a function via the new operator (we will act without arguments so easily), we'll see that we have the Return 'line. EvaluateNew (NewExpression, empty) In this line, NewExpression is the Number function, right? If everything is correct go further. Next, we see that in the EvaluateNew algorithm there is a line that looks like this: Let's get the result of evaluating constructExpr. - what does this mean? This means that we need to calculate what is ref before continuing to execute the algorithm? If I'm not mistaken, then ref is a Number function. So we are asked to execute the lines of the algorithm from the Number function? If this is the case, then we are faced with a problem in the Number algorithm because we do not know what value NewTarget has.
That you understood a question consists in that that I do not understand as the Number interacts with the operator new. I ask to help you figure it out.
P.S For voters down: explain what you do not like so that I can change the question for the better, thanks
NewTarget - where does it come from?
From either [[call]]ing a builtin function object or from [[construct]]ing a builtin function object.
If this algorithm is used both for the constructor and for a simple conversion to a number, then if NewTarget = undefined this conversion to a number, if NewTarget != undefined, then this calls this function as a constructor.
That's not a question, that's a (true) statement.
In this line, NewExpression is the Number function, right?
No. The NewExpression constructExpr is an expression, i.e. the abstract syntax tree of whatever operand the new operator has. This could be any expression, for example a PrimaryExpression with an IdentifierReference, that evaluates to the global Number function (or a Reference to it) - when it is being evaluated, and that's exactly what step 3 does.

What does the sentence "The element expressions in an array initializer are evaluated each time the array initializer is evaluated" mean?

I was reading David Flanagan's JavaScript: The Definitive Guide, probably the fattest book for JavaScript in the world. When briefly describing array initializers, Flanagan said "The element expressions in an array initializer are evaluated each time the array initializer
is evaluated". What does this means. My practice results made me more confused:
var a = 50;
var b = 70;
var array = [a+b, 50];
console.log (array [0]); //120
a = 60;
console.log (array [0]); //120
var other = array;
console.log (other [0]); //120
I thought the result would be 130 after I change a's value to 60, because the expression is going to be re-evaluated. But it's not that. I know I am completely getting it wrong. Can someone explain what Flanagan is trying to explain in that sentence?
He means that when the array literal expression is ("re"-)evaluated, so will be its contents.
function makeArray() {
return [a];
}
var a = 0;
console.log(makeArray()); // [0]
a = 1;
console.log(makeArray()); // [1]
So, nothing special actually, just default expression behaviour.
You've misquoted the author; what he actually said is
The element expressions in an array initializer are evaluated each time the array initializer is evaluated.
Rather than the term "array initializer", it would be clearer, and more in line with common usage, if he said "array literal". If we adopt the common-sense interpretation of "array initializers" as meaning "initializers for an array-valued variable", then such initializers could be array literals, but could also be any array expression. (Having said that, it does appear that the spec uses the term "array initializer" for "array literal" in Section 12.2.5, so the author's usage is not wrong in that technical sense.) Array literals (basically, anything of the form [...]) can, on the other hand, be used as initializers, but can also be used elsewhere.
What he appears to be trying to say is that an array literal such as
[a]
re-evaluates a each time it itself is re-evaluated, such as when a function is re-executed. If you think about it, that's pretty obvious. It's hard to see how it could work any other way. So the function
function foo(x) { return [x]; }
will return the array [1] when called as foo(1), and the array [2] when called as foo(2), because the array literal [x] is re-evaluated each time the function is called.
This is an entirely different issue from the one that is tripping you up, which is that
var a = 22;
var b = [a];
a = 42;
console.log(b);
does not change b to have the value [42]. This behavior has nothing to do with how array initializers are (re-)evaluated or their component elements are (re-)evaluated. Is has to do with how JavaScript variables and references work. The above would change the value of b if b were storing a dynamic reference to a as its element, but JavaScript has no such notion of dynamically-updatable references. To put it a different way, the console.log(b) line is indeed evaluating b, but that just returns whatever b already is. It does not (re-)evaluate the expression [a] originally used to set b. The fact that b happens to have taken on he value resulting from evaluating [a] is lost immediately after the statement setting the value of b, at which point b now has the value [22].
Consider also the following example, which is completely analogous:
var a = 1, b = 2, c = a + b;
a = 42;
console.log(c);
Here, no-one would expect the expression given as the initial value for c to change when the value of a changes later. c is 3, and it will always be until explicitly re-assigned. In the console(c) line, yes, we are "evaluating" c, but that is merely a matter of retrieving the value of c, not of re-evaluating some expression which happens to have used to assign a value to c in the past. There are some other declarative-style language paradigms where things might behave in that way, but not JavaScript.
Here you assigning the array to array variable and it will store that value until that variable(unless you modify that variable) goes out of scope. In the second statement you're reusing the the same array pointed to by "array" variable. Third statement is just assigning the reference to "array" variable to another variable and access the same element.
Only the first time a+b statement is evaluated and that resulting array is saved as value reference. Last statement is an example of pass by value(Javascript is always pass by value) where copy of "array" variable is created as "other", and "other" also points to same memory reference pointed by "array" variable.

Re-initialization array in called function

Arrays are passed by reference in javascript. That means arrays are mutable and can be changed in called function. Why am I not able to Re-initialization an array in called function?
This is also true for python
function calling(){
var arr = [1,2]
console.log(arr); //prints [1,2]
called(arr);
console.log(arr); //prints [1,2,3,4],but I was expecting [5,6]
}
function called(arr){
arr.push(3)
arr.push(4)
arr = [5,6]
}
calling()
UPDATE
I am seeking justification of (mutability, pass by reference, pass by assignment and re-initialization of an array)
Please don't post workaround and solution of the problem. I know how to get arr to print [5,6]
Arrays are passed by reference in javascript.
No, they aren't. ECMAScript/JavaScript is strictly pass-by-value. (More precisely, call-by-sharing, which is a special case of pass-by-value.)
That means arrays are mutable and can be changed in called function.
No, that's not what it means. It means exactly what it says: that the reference within the caller's scope to the array is passed as an argument into the function, and not the value.
Whether or not the array is mutable or not has nothing to with pass-by-reference vs. pass-by-value. ECMAScript is not a purely functional language, most objects can be mutated. Numbers, Symbols, and Strings are an exception.
Why am I not able to Re-initialization an array in called function?
You are trying to modify the reference within the caller's scope. That only works with pass-by-reference. ECMAScript is not pass-by-reference, whoever told you that is simply wrong.
This is also true for python
Python behaves identically to ECMAScript in this regard, yes, it is also pass-by-value.
Your confusion stems from the fact that you erroneously believe ECMAScript/JavaScript is pass-by-reference, when in fact it is not.
ECMAScript uses pass-by-value, or more precisely, a special case of pass-by-value where the value being passed is always a pointer. This special case is also sometimes known as call-by-sharing, call-by-object-sharing or call-by-object.
It's the same convention that is used by Java (for objects), C# (by default for reference types), Smalltalk, Python, Ruby and more or less every object-oriented language ever created.
Note: some types (e.g. Numbers) are actually passed directly by value and not with an intermediary pointer. However, since those are immutable, there is no observable behavioral difference between pass-by-value and call-by-object-sharing in this case, so you can greatly simplify your mental model by simply treating everything as call-by-object-sharing. Just interpret these special cases as internal compiler optimizations that you don't need to worry about.
Here's a simple example you can run to determine the argument passing convention of ECMAScript (or any other language, after you translate it):
function isEcmascriptPassByValue(foo) {
foo.push('More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!');
foo = 'No, ECMAScript is pass-by-reference.';
return;
}
var bar = ['Yes, of course, ECMAScript *is* pass-by-value!'];
isEcmascriptPassByValue(bar);
console.log(bar);
// Yes, of course, ECMAScript *is* pass-by-value!,
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!
def is_python_pass_by_value(foo):
foo[0] = 'More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!'
foo = ['Python is not pass-by-reference.']
quux = ['Yes, of course, Python *is* pass-by-value!']
is_python_pass_by_value(quux)
print(quux[0])
# More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value!
If you are familiar with C#, it is a very good way to understand the differences between pass-by-value and pass-by-reference for value types and reference types, because C# supports all 4 combinations: pass-by-value for value types ("traditional pass-by-value"), pass-by-value for reference types (call-by-sharing, call-by-object, call-by-object-sharing as in ECMAScript), pass-by-reference for reference types, and pass-by-reference for value types.
(Actually, even if you don't know C#, this isn't too hard to follow.)
// In C#, struct defines a value type, class defines a reference type
struct MutableCell
{
public string value;
}
class Program
{
// the ref keyword means pass-by-reference, otherwise it's pass-by-value
// You must explicitly request pass-by-reference both at the definition and the call
static void IsCSharpPassByValue(string[] foo, MutableCell bar, ref string baz, ref MutableCell qux)
{
foo[0] = "More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.";
foo = new string[] { "C# is not pass-by-reference." };
bar.value = "For value types, it is *not* call-by-sharing.";
bar = new MutableCell { value = "And also not pass-by-reference." };
baz = "It also supports pass-by-reference if explicitly requested.";
qux = new MutableCell { value = "Pass-by-reference is supported for value types as well." };
}
static void Main(string[] args)
{
var quux = new string[] { "Yes, of course, C# *is* pass-by-value!" };
var corge = new MutableCell { value = "For value types it is pure pass-by-value." };
var grault = "This string will vanish because of pass-by-reference.";
var garply = new MutableCell { value = "This string will vanish because of pass-by-reference." };
// the first two are passed by value, the other two by reference
IsCSharpPassByValue(quux, corge, ref grault, ref garply);
Console.WriteLine(quux[0]);
// More precisely, for reference types it is call-by-object-sharing, which is a special case of pass-by-value.
Console.WriteLine(corge.value);
// For value types it is pure pass-by-value.
Console.WriteLine(grault);
// It also supports pass-by-reference if explicitly requested.
Console.WriteLine(garply.value);
// Pass-by-reference is supported for value types as well.
}
}
If you want to get technical, Javascript is really only "pass by value" - it is not "pass by reference". When you pass an object (like an Array), you are passing by value and the value is a reference (a copy of a reference to the array, not a copy of the actual array). This means you can use the value reference to mutate the original object, but it's not an actual reference to the original variable so you can't affect the contents of the original variable itself (e.g. you can't cause that other variable to point at a new object).
Since this language jargon doesn't actually explain what is going on for many folks, what follows here is more of a description of how it works:
When you do:
arr = [5,6]
inside your function, you are just assigning a new array to the arr function argument. You've just instructed Javascript to take the arr argument variable and point it at a new array object. That has no effect at all on the original array object that was passed into the function. It still happily exists in its other life.
You could change that array like this:
function called(arr){
arr.push(3)
arr.push(4)
// now set arr back to [5,6]
arr[0] = 5;
arr[1] = 6;
arr.length = 2;
}
It's a bit of a misnomer to say arrays are passed by reference because it isn't really a full reference like you would find in C++, for example. Some people say it's "pass by sharing" or "pass by pointer". Some people even call it "pass by value, but the value is a reference" which is probably technically correct, but doesn't usually help newbies understand what is going on. In my brain (partly because I know C/C++), I think of it like "pass by pointer".
Though Javascript doesn't actually have it's own data type that is a pointer, passing an array works more like passing a pointer works in C++. If you index off the pointer, you are accessing the original object:
arr[0] = 5; // this changes the original object
But, if you reassign the pointer a new value:
arr = [5,6]; // this just makes arr point at a new and different array
Then, you are just making the pointer variable itself point at a new object and the original object still remains untouched.
So, in summary. If you reference arr as in arr.push(x), then you are mutating the original array object.
But, if you assign arr a new array as in arr = [5,6], then you are just telling Javascript that you want the arr variable to point at a new array. Your arr variable in that function now points at a different array than it did before. The original array can no longer be reached by this code. But any other code that was pointing at that original array is still pointing at that original array. If you know C/C++, it actually works fairly similarly to a pointer in C++.
Here's a simpler example that is outside of the context of a function argument:
var x = [1,2];
var y = x;
console.log(x); // [1,2]
console.log(y); // [1,2]
y = [3,4];
console.log(x); // [1,2]
console.log(y); // [3,4]
You've merely asked y to now point to a new array [3,4]. Assigning y to point to a new array does not affect the original array at all.
In this example, the variable y is like the arr function argument in your called() function. Assigning a new value to it doesn't change the original in any way.
In Python:
In [170]: def called(arr):
.....: arr.append(3)
.....: arr.append(4)
.....: arr=[5,6]
.....:
In [171]: arr=[1,2]
In [172]: called(arr)
In [173]: arr
Out[173]: [1, 2, 3, 4]
Initially in called, arr is a reference to the mutable list [1,2]. The 2 append calls modify that list. But the arr=[5,6] step reassigns the variable. The link to outside arr is broken, so there's no further change.
If I'd added a return arr statement to the call,
In [175]: called(arr)
Out[175]: [5, 6]
In [176]: arr
Out[176]: [1, 2, 3, 4, 3, 4]
I modify the global arr again, but return the [5,6].
called would be clearer if written:
def called(arr):
arr.append(3)
arr.append(4)
res=[5,6]
return res
The last 2 lines have nothing to do with initial arr argument, so there is not point in reusing the variable name.
For further illustration of what is going on, look at the object id at various points:
In [178]: def called(arr):
arr.append(3)
arr.append(4)
print(id(arr))
arr = [5,6]
.....: return arr
.....:
In [179]: id(arr)
Out[179]: 2999818796
In [180]: id(called(arr))
2999818796 # id of global arr
Out[180]: 2999999564 # id of returned list
In [181]: id(arr)
Out[181]: 2999818796

The evaluation process of a compound expression containing two assignment operators in JavaScript

Here is a compound expression containing two assignment operators:
var a = {n: 1};
var b = a;
a.x = a = {m: 2};
a; // => {m: 2}
b; // => {n: 1, x: {m: 2}}
The tricky part is the third line:
a.x = a = {m: 2};
IMHO, The assignment operator = is right-associative, so the nesting structure of the expression is:
a.x = (a = {m: 2});
But the evaluation order in ES5 is always from left to right, according to ES5 Annex D.
According to ES5 Section 11.13.1,
The production AssignmentExpression : LeftHandSideExpression =
AssignmentExpression is evaluated as follows:
Let lref be the result of evaluating LeftHandSideExpression.
Let rref be the result of evaluating AssignmentExpression.
Let rval be GetValue(rref).
Throw a SyntaxError exception if the following conditions are all true:
....omitted intentionally to save space
PutValue(lref, rval).
Return rval.
So my understanding of the left-to-right evaluation order is :
evaluate a.x first and return a reference lref1 for it
evaluate a = {m: 2} to obtain rref1, because it is also an assignment expression, we'll start the procedure again (like recursion)
2.1. evaluate a first and return a reference lref2 for it
2.2. evaluate {m: 2} and return the very object {m: 2} as rref2
2.3. Let rval2 = GetValue(rref2), so rval2 is also the object {m: 2}
2.4. PutValue(lref2, rval2), so a will rebinds the object {m: 2} instead of {n: 1}
2.5. return rval2, i.e. the object {m: 2} as rref1 (not Reference type, but object)
Let rval1 = GetValue(rref1), which is also the very object {m: 2}
PutValue(lref1, rval1), so the memory address which lref1 refers to will be {m: 2}. And b.x still refers to this address and b will be updated.
This procedure complies to the ES5 Spec and explains the result well.
My questions are:
Is this evaluation order abovementioned true or false? If false, any alternative explanations?
How to understand the Reference Specification Type in ES5 appropriately? Is it just an intermediate pointer which refers to certain memory address?
Yes, your understanding about the operator order appears to be correct.
ECMAScript 5 section 8.7 says:
A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).
The process of creating a reference from property access is defined in 11.2.1:
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
Thus the reference lref1 holds the object value of a (along with the referenced name string "x"), which you originally created with {n: 1}. A reference does not care about the variable that it came from; it cares only about what base value and reference name it is supplied when created.
Altering what value is held by a has no effect whatsoever on the base value held by the reference lref1. lref1 continues to the hold the original value of a (i.e., that {n: 1} object) regardless of what a does after the creation of lref1.
In short, the reference created from the expression a.x ceases to have anything to do with the variable a as soon as the reference is created. Instead, the reference knows only about the value that was held by a at the time the reference was created.

Categories

Resources