Looking at MDN Web Docs:
Non-strict functions that are passed only simple parameters (that is, not rest, default, or restructured parameters) will sync the value of variables new values in the body of the function with the arguments object, and vice versa:
function func(a) {
arguments[0] = 99; // updating arguments[0] also updates a
console.log(a);
}
func(10); // 99
and also
function func(a) {
a = 99; // updating a also updates arguments[0]
console.log(arguments[0]);
}
func(10); // 99
Conversely, non-strict functions that are passed rest, default, or destructured parameters will not sync new values assigned to argument variables in the function body with the arguments object. Instead, the arguments object in non-strict functions with complex parameters will always reflect the values passed to the function when the function was called (this is the same behavior as exhibited by all strict-mode functions, regardless of the type of variables they are passed):
function func(a = 55) {
arguments[0] = 99; // updating arguments[0] does not also update a
console.log(a);
}
func(10); // 10
and
function func(a = 55) {
a = 99; // updating a does not also update arguments[0]
console.log(arguments[0]);
}
func(10); // 10
and also
// An untracked default parameter
function func(a = 55) {
console.log(arguments[0]);
}
func(); // undefined
This is probably due to some issue with the sync process syntactic sugar in the javascript compiler.
UPDATE:
It turns out that deno
is doing something different under the hood and printing in both cases 10
which is kinda strange so I looked into the assembly generated here:
deno eval 'function f(a) { a = 99; console.log(arguments[0]); } f(10);' --v8-flags='--print-bytecode,--print-bytecode-filter=f'
deno eval 'function f(a = 55) { arguments[0] = 99; console.log(a); } f(10);' --v8-flags='--print-bytecode,--print-bytecode-filter=f'
The first command prints this code, which I tried to understand, putting comments where I think it is doing something relevant.
[generated bytecode for function: f (0x38db0826b679 <SharedFunctionInfo f>)]
Parameter count 2
Register count 4
Frame size 32
0x38db0826b846 @ 0 : 88 CreateUnmappedArguments
0x38db0826b847 @ 1 : 26 fb Star r0: Store accumulator in register 0 (presumibly function frame pointer)
0x38db0826b849 @ 3 : 0c 63 LdaSmi [99]: Load (SMallInteger) 99 in accumulator
0x38db0826b84b @ 5 : 26 02 Star a0: Store accumulator in function argument 0
0x38db0826b84d @ 7 : 13 00 00 LdaGlobal [0], [0]: I don't know
0x38db0826b850 @ 10 : 26 f9 Star r2: Store accumulator in register 2
0x38db0826b852 @ 12 : 28 f9 01 02 LdaNamedProperty r2, [1], [2]: Load a in the accumulator
0x38db0826b856 @ 16 : 26 fa Star r1: Store accumulator in register 1
0x38db0826b858 @ 18 : 0b LdaZero: Load 0 in accumulator
0x38db0826b859 @ 19 : 2a fb 04 LdaKeyedProperty r0, [4]: (presumibly) loading the property with key 4
from the function frame with the accumulator
(syncing) the argument here.
0x38db0826b85c @ 22 : 26 f8 Star r3: Store accumulator in register 3
0x38db0826b85e @ 24 : 59 fa f9 f8 06 CallProperty1 r1, r2, r3, [6]: console.log(r1)
0x38db0826b863 @ 29 : 0d LdaUndefined: Load undefined in accumulator
0x38db0826b864 @ 30 : aa Return: Return the accumulator value
Constant pool (size = 2)
Handler Table (size = 0)
Source Position Table (size = 0)
and this is the output of the second program that I processed:
[generated bytecode for function: f (0x3bd80826b679 <SharedFunctionInfo f>)]
Parameter count 2
Register count 4
Frame size 32
0x3bd80826b846 @ 0 : 88 CreateUnmappedArguments
0x3bd80826b847 @ 1 : 26 fa Star r1: Store accumulator in register 1 (presumibly function frame pointer)
0x3bd80826b849 @ 3 : 25 02 Ldar a0: Load in the accumulator the argument 0
0x3bd80826b84b @ 5 : 9e 06 JumpIfNotUndefined [6] (0x3bd80826b851 @ 11): If accumulator is undefined:
0x3bd80826b84d @ 7 : 0c 37 LdaSmi [55]: Load (SMallInteger) 55 in accumulator
0x3bd80826b84f @ 9 : 8b 04 Jump [4] (0x3bd80826b853 @ 13): else:
0x3bd80826b851 @ 11 : 25 02 Ldar a0: Load argument 0 in accumulator
0x3bd80826b853 @ 13 : 26 fb Star r0: Store accumulator in register 0
0x3bd80826b855 @ 15 : 0b LdaZero: Load 0 in accumulator
0x3bd80826b856 @ 16 : 26 f8 Star r3: Store accumulator in register 3
0x3bd80826b858 @ 18 : 0c 63 LdaSmi [99]: Load (SMallInteger) 99 in accumulator
0x3bd80826b85a @ 20 : 30 fa f8 00 StaKeyedProperty r1, r3, [0]: (presumibly) loading in the function frame
(r1) the value of the accumulator (99) in the
property with key r3 (0) (syncing)
0x3bd80826b85e @ 24 : 13 00 02 LdaGlobal [0], [2]: (presumibly) loading the global value containing the
pointer to a in the accumulator
0x3bd80826b861 @ 27 : 26 f8 Star r3: Store accumulator in register 3
0x3bd80826b863 @ 29 : 28 f8 01 04 LdaNamedProperty r3, [1], [4]: Load a in the accumulator
0x3bd80826b867 @ 33 : 26 f9 Star r2: Store accumulator in register 2
0x3bd80826b869 @ 35 : 59 f9 f8 fb 06 CallProperty1 r2, r3, r0, [6]: console.log(r2)
0x3bd80826b86e @ 40 : 0d LdaUndefined: Load undefined in accumulator
0x3bd80826b86f @ 41 : aa Return: Return the accumulator value
Constant pool (size = 2)
Handler Table (size = 0)
Source Position Table (size = 0)
As you can see, the second code looks like the first one, with the addition of an if statement to check for the optional argument.
In the end, the v8 google engine
within deno
prints 10
twice.
That is weird but I assume there is a reason why this is happening.
It might be that someone made this decision in their implementation and not everyone reflected the change. However, please don't roast me, I tried my best to understand the bytecode without a manual, so if it's wrong, just tell me and I'll fix it.