Sunday, 2 February 2025

JavaScript Arguments and Arrow Functions

After writing last week about how to sort of emulate JavaScript's arguments-object in Python, it seems a good idea to mention the special behaviour of the arguments-object in arrow functions. When arrow functions were added to JavaScript most articles promptly informed about the particular behaviour of the this-object in arrow functions (that is indeed one of its main features). The main idea that got stamped in my brain was "arrow functions do not have dynamic this, but lexical this". This means that they do not receive as "this" value the "receiver", the object on which they get invoked, but the "this" of their lexical environment. I used to think that such "this" value would get bound to the arrow function in a similar way to when we use function.bind(), but it's not like that. Arrow functions are not bound-functions with some particular property pointing to the "this", but they just look it up in its scope chain. From [1] and [2]:

[1] In essence, lexical this means that an arrow function will keep climbing up the scope chain until it locates a function with a defined this keyword. The this keyword in the arrow function is determined by the function containing it.

[2] Arrow functions lack their own this binding. Therefore, if you use the this keyword inside an arrow function, it behaves just like any other variable. It will lexically resolve to an enclosing scope that defines a this keyword.

What has prompted this post is another thing that I had not realized until recently (and it's not so hard as it comes in the MDN documentation) is that something similar happens with the arguments-object.

Arrow functions don't have their own bindings to this, arguments, or super, and should not be used as methods.

"don't have their own bindings" means that they'll be looked up in the scope chain (as any other varible). Arrow functions do not have its own arguments-object containing the arguments received by the arrow, but look up that arguments-object in the scope chain, so it will contain the arguments received by the first enclosing function (at the time of the arrow creation, not of the arrow invokation) that is not an arrow. Notice how in the example below, it prints "aaa" (from its scope chain) rather than "bbb" (that is what it receives as parameter).


// arrow functions do not have an "arguments" or "this" binding, they take them from their scope chain
function f2(a) {
    console.log("inside f");
    let fn1 = (b) => console.log(arguments);
    return fn1;
}

let arrow = f2("aaa")
arrow("bbb")
//[Arguments] { '0': 'aaa' }

I'll leverage this post to mention that changing where an entry in the arguments-object points to changes also where the variable itself points to. So this behaviour corresponds to what happens in Python with the FrameType.f_locals write-through proxy, not to what we have in Python via locals() (a snapshot through which we can not change where the original variables point to).


function test(user) {
    console.log(`user: ${user}`)
    console.log(`arguments[0]: ${arguments[0]}`);
    arguments[0] = "Iyán";
    console.log(`arguments[0]: ${arguments[0]}`);
    console.log(`user: ${user}`)
}

test("Xuan");

// user: Xuan
// arguments[0]: Xuan
// arguments[0]: Iyán
// user: Iyán


No comments:

Post a Comment