Didactic Nitpicking (JavaScript Functions)

My question isn’t so much about coding itself but more about effective communication in a developer environment and understanding core concepts.

While studying JavaScript on another platform, I encountered a coding challenge with a step that read:

Create a function functionName that takes the average value of both parameters and then logs it to the console.

I tackled it, but for the sake of thoroughness I reviewed the solution provided. The person offered this code:

const functionName = function (parameter1, parameter2) {
// ... (code for the function) ...
};

Here’s my nitpicky question:

The code above isn’t a function named functionName but rather a const named functionName with a function expression assigned to it, right?

If I tell someone, “Create a function functionName,” it seems to imply that it can’t be a function expression or arrow function — since both are anonymous — but must be a function declaration, like this:

function functionName (parameter1, parameter2) {
// ... (code for the function) ...
}

It’s a bit pedantic, I know, but I’d appreciate any insights on this to double-check if I’m understanding these concepts correctly. My hunch is that precise instructions matter, especially since certain scenarios may require a function to be coded as a declaration rather than an expression or arrow function.

5 Likes

Right. Now consider the data type of the object in the assignment: A function. A function is an expression since it renders a return value. JavaScript will break a leg to evaluate every expression we give it.

Hoisting aside, what separates function declarations from anonymous function expressions? Is it where they are stored? (Not rhetorical, we would love to know.) They certainly do not behave any differently (apart from where they can be called).

Where arrow functions are concerned, the differences are clear:

  • no arguments object
  • no this property
  • no block delimiters
  • no return keyword

We’ll leave that for the reader to enumerate. It would also be interesting to know if arrow functions have an extensible prototype object, a question which only now, after umpteen years, I’ve just come up with.

2 Likes

Aside

Let’s draw out a little of what know (or think we know) about object prototypes.

const f = function (x) {
    return x;
}
f.prototype.g = function (x) {
    return x ** 2;
}
const h = new f()
console.log(h(5))
// 5
console.log(h.g(5))
// 25

Rabbit hole stuff, I know, but it is a fundamental concept in JavaScript. Don’t be a stranger around this kind o’ stuff.

Ready to see if we can pull this off with arrow functions? Just say the word.

1 Like

The one and only mtf, nice! I’m a big fan, sir. Your personal forum page is a treasure trove of information — thank you for your constant contributions. Never change your profile pic; you’re already at a personal brand level. I’m always looking for your contemporary Hemingway look when surfing the forum. It’s a great pleasure to share this voyage with a seasoned sea-dog like yourself.

I won’t insult you by pretending I’m not already way over my head here, but some of the points you raised got me thinking.

I only have a high-level understanding of this, mostly repeating what I’ve read — declarations get added directly to the global scope (becoming properties of the window object in browsers or global in Node.js) or local scope, while anonymous functions get stored in variables.

From this, I can only guess it impacts memory usage, performance, and probably execution context, but this is as far as I can take it for now.

Still, I’m curious: when you were starting out with JavaScript, did you default to function declarations when in doubt? For me, this has been helpful since abstracting functionality and context from large code blocks is still a challenge — especially in JavaScript. I lack proper perspective, but so far, JS seems like a pretty verbose language.

I’m with you, Captain, but it’s going to be a rough journey for me.

From what I’ve gathered so far, it seems arrows don’t have prototypes, meaning they can’t act as constructors. And from what little I know, it seems constructors are primarily used to allow new objects to inherit from empty prototypes. If you can, yet again, double-check me on this: I’d say the new keyword you used on line 7 of the code you posted above is intended to create objects, which doesn’t seem to apply to arrow functions.

1 Like

We can add that item to the earlier list:

  • no prototype object

As such, the construct we created above cannot be replicated with arrow functions. We get an error immediately. Say f is an arrow function. When we attempt to add to the prototype object, we get an error saying that g is undefined and we cannot set it.

 > f = x => x;
<- x => x
 > f.prototype.g = x => x ** 2;
 X > VM136:1 Uncaught TypeError: Cannot set properties of undefined (setting 'g')
    at <anonymous>:1:15
 > 

When we consider that functions are objects, it follows they have a prototype which they inherit from [Function]. It turns out this is also an object, and as you note, an empty one. However, we can insert new properties, as we did above.

 > f = function (x) {
       return x;
   }
<- ƒ (x) {
       return x;
   }
 > f.prototype
<- > {}
 > f.prototype.g = function (x) {
       return x ** 2;
   }
<- ƒ (x) {
       return x ** 2;
   }
 > f.prototype
<- > {g: ƒ}
 > 

Now if we attempt to access the prototype of f, as in, f.g(5) we get an error, g is not a function. This is where new comes in. We create an instance, then we can invoke the inherited method, g.

h = new f()

The rest we already have seen above. It is impractical, yes, but demonstrates how easy it is to create new objects that inherit from their parent object’s prototype.

Arrow functions have no bindings to the window object, so have no link in the prototype chain, or so it would seem. I’m not an expert and can only encourage you to keep reading up on this topic.

Oh, and yes, when I started out in JavaScript I had only HTML and CSS (and some BASIC and QBASIC) in my background. It was a long time before I graduated to function expressions as opposed to bog standard function declarations. There was a whole lengthy period where the only practical use I could find for them (expressions) was in function factories. In my naivete it hadn’t occurred to me that we could actually redeclare a function.

The literature demonstrated that function expressions had the same behavior as declared functions, including acting as a constructor. Any function can be a constructor (except ES2015+ arrow functions, as we’ve seen).

It was only when we got the const keyword that I switched to function expressions, and now favor them. Mind, I don’t do any development work anymore (like I ever really did) so am falling quickly out of practice. The beginners trenches is where I planted my feet, for that very reason. I cannot give professional advice.

1 Like

Which, in colloquial English translates to “Lucky us.”

Wrapping my head around prototypes still feels like an idea first born on an undigested apple-dumpling, with the caveat that this is heaven for me.

Probably the only time I’ll vehemently disagree with you. My gut tells me we’ll be shooting the breeze on that front many times before the curtain drops.

1 Like

One thing we could always appreciate in the days of constructor functions was the ability to extend a constructor’s prototype, even on the fly, after instances have been created. Any addition to the prototype object will be immediately accessible in all instances, not just new ones.

This is very practical since rather than every instance having copies of the methods (and shared properties) only one exists in the prototype and does not need to be replicated. The memory savings can be substantial.

const numberOps = function (a, b) {
    this.a = a;
    this.b = b;
}

const u = new numberOps(6, 7)
const v = new numberOps(3, 14)

We’ve got two instances in effect for the entire session. Now we can add a shared property:

numberOps.prototype.c = 42
console.log(u.c)    //  42
console.log(v.c)    //  42

Cool, eh?

But what about the methods?

numberOps.prototype.add = function () {
    return this.a + this.b;
}
numberOps.prototype.mul = function () {
    return this.a * this.b;
}

console.log(u.add())    //  13
console.log(v.add())    //  17

console.log(u.mul())    //  42
console.log(v.mul())    //  42

Truly fun stuff! We can apply the same general thinking to ES6 class constructors, as well, but it takes a little working around. Seems some months ago I wrote a post demonstrating this. If it hasn’t been archived it might come up on search.

1 Like

It wouldn’t be fair to not at least explore the class path…

 > class Foo {
     constructor (a, b) {
       this._a = a;
       this._b = b;
     }
     get a () {
       return this._a;
     }
     get b () {
       return this._b;
     }
   }
<- undefined
 > Foo.prototype.add = function () {
       return this.a + this.b;    //  call the getters
   }
<- ƒ () {
       return this.a + this.b;
   }
 > f = new Foo(6, 7)
<- Foo {_a: 6, _b: 7}
 > f.add()
<- 13
 > 

If you’re wondering about the weird stuff in the left margin, it’s to emulate the Chrome JavaScript Console. When testing ideas, I write the code directly into the console and watch for error messages, and such in real time. If the code works, then I can put it into production, or at least go forward with it.

1 Like

Being an ‘igniter’ has long been my principle goal. On that basis I may consider myself successful. Without praise, maybe, but not without countless appreciative posts and DMs. Whatever makes them become my teacher is what makes me the happiest, and always will.

1 Like

Then you’re up to par with your principle goal, no question about it. Even if online interactions have their limitations, it’s still a joy sharing the learning process with veterans like you, mtf.

1 Like