Can we fix "this"?

I should've been more specific. That's a really bad idea. Strict-equal values must be interchangeable. If instance1.f() does something different than instance2.f(), they cannot be equal.

More specifically to the callbacks example: if you have a callback registry, you may need to register both instance1.f and instance2.f as callbacks (at different points in time). Later you may need to remove instance1.f callback, keeping instance2.f still active. There'd be no way to achieve this if they were equal.

Haha, durp on my part. I understood your point, but for some reason thought that freezing the functions would fix it.

So yeah, I think the way forward would be to require instance1.f !== instance2.f, and have them use a shared space for storing members.

1 Like

Technically strict equality does not imply sameValue equality for everything. Only Object.is gives that. -0 === +0 but 1/-0 !== 1/+0.

1 Like

Maybe there's a simpler formulation to this idea.

Instead of trying to correctly auto-bind "this", what if a class-level decorator was provided that simply enforced the "this" value to be an instance of a class. The benefits would be:

  • This is generally how many people expect you to use their classes. Now there's a convenient way to enforce this expectation.
  • The generated error message could be much nicer than "Could not read property 'whatever' from undefined", or whatever other cryptic error you may or may not get for providing the wrong receiver.
  • This doesn't add any complexity to how this-binding works in general.

As an example:

@enforceInstanceRecievers
class User {
  age = 2
  getAge() {
    return this.age
  }
}

const user = new User()
user.getAge() // 2
setTimeout(user.getAge, 1000) // Error!
user.getAge.call({}) // Error!
User.prototype.getAge() // Error!
User.prototype.getAge.call(user) // 2

Nit: most modern stateful React components nowadays use hooks-based function components, not class Foo extends React.Component { ... }. And not only is that more intuitive, it also doesn't have the pitfalls this proposal seeks to fix.

Yeah, that was this paragraph was meant to address :)

You're right that this issue is close to zero in React now if you use hooks. Part of the reason they moved to hooks was because they thought "this" was too confusing for new comers, and they wanted to avoid them. At least, I remember that being one of their selling points.

But, for all of the other non-react users who use classes and callbacks, the problem will still be prevalent.

In my experience, arrow functions are pretty common for such stuff. I'm still not sure how this is sufficiently compelling, especially when not also existing in some fashion for objects in general.

enforced the "this" value to be an instance of a class

You can already do this with:

class C {
#brand;
method() {
this.#brand;
/* or */
if (!(#brand in this)) {
throw new TypeError('the receiver is not a C instance');
}
}
}

A decorator could do it also, but it'd be doing exactly the same conceptual check.

1 Like

Maybe I'm too old of a programmer, but I don't even see how the issue in the OP constitutes a real problem. The behavior of a callback function is precisely what it should be. It's the expectation that the instance will somehow be magically carried along with the function that's strange.

class User {
  constructor(name) {
    this.name = name
  }
 
  printName() {
    console.log(this.name)
  }
}
 
const sarah = new User('Sarah')
setTimeout(()=>sarah.printName(), 1000)

By adding 6 characters to the original example, the problem is fixed without appending .bind(this) (which is only 11 characters itself).

Honestly, it seems weird to me that so much effort is being put into trying to hide the need to bind a function to a context. Then again, that may be because the first 5 languages I learned in order were LOGO, Apple Pascal, Applesoft Basic, 6502 Assembly, C++. By the time JavaScript came along, I already had a strong understanding of things like function pointers.

So maybe this should just be dismissed as a rant from me, but from where I sit, this is more an issue with properly training developers in the fundamentals of programming. Expecting the language to somehow account and adapt to a developer's lack of knowledge is both dangerous and foolish. 4GLs anyone? It's already been tried and written off as an overwhelming failure that's only useful in a niche set of use cases.

JS is a general purpose language now. Please don't take steps to revert it back to its toy language heritage.

4 Likes

What I would suggest as a direct solution to the issue is to make a decorator. I'd personally choose something like @delegate. The operation is simple and only needs to be processed one time at class instantiation. It just creates a bound version of the function on the instance. No confusion with ===, no expensive extra operations, no memory leaks. Just a clean, 1 shot solution.

Its heritage is the reason JS will always remain a hodge-podge mess of legacy behaviours.

In a properly designed language:

f = o.foo
f(42)
// does the same thing as
o.foo(42)

Examples include Python (where o.foo binds o in both cases) or Lua (where o.foo doesn't bind o in either case). Especially people switching between Python and JS have to constantly be aware of this quirk of JS that "forgets this" if you don't call it immediately.

2 Likes

Well, the idea was worth a shot. There seems to be a general dislike for it, in any of its formulations, because of the added complexity it brings to an already complicated feature.

Thanks everyone for the feedback!

1 Like

you really shouldn't be using [artificially stateful] classes, when most javascript-problems are [stateless] message-passing tasks.

-// message-pass user between http-endpoint -> browser-ui
-// with stateful 'this' / class-instance
-@enforceInstanceRecievers
-class User {
-  age = 2
-  getAge() {
-    return this.age
-  }
-}
-let user = await fetch(url)
-user = await user.json()
-user = new User(user) // unnecessary, stateful class-instantiation
-document.querySelector("#user-age1").value = user.getAge()


+// message-pass user between http-endpoint -> browser-ui
+// with stateless static-function
+function userGetAge(user) {
+    return user.age
+}
+let user = await fetch(url)
+user = await user.json()
+document.querySelector("#user-age1").value = userGetAge(user);

No one said it was supposed to be a stateful class. Does this make you feel better?

@enforceInstanceRecievers
class User {
  age = 2
  constructor() {
    Object.freeze(this)
  }
  getAge() {
    return this.age
  }
}

It was just an example class to illistraight how it works, not a real-world scenario. You're right that it's probably best not to represent user information as a stateful construct.

my complaint is that this is irrelevant in the [stateless] message-passing problem-set -- which is the dominant problem-set javascript is meant to solve.

Please, as you’ve been asked many times before, stop making objectively false statements. That is NOT the dominant problem-set JavaScript is meant to solve; it’s merely one of many.

Despite my personal preferences (i don’t like using stateful classes), it is PERFECTLY FINE to use stateful classes to solve any problem in JavaScript if you wish it.

3 Likes

if its not a message-passing problem-set, its probably best left to webassembly, databases, child_process.spawn(...), etc... -- where again, javascript is primarily used as a message-passing intermediary between these external endpoints.

What it sounds like you're trying to say is that class syntax should not exist in JavaScript altogether. That's fine if you believe that, you wouldn't be alone, but for the purposes of this thread we're going (correctly) assume that people use it and have good use cases for it.

If you want to discuss whether or not we should continue to provide good support for class syntax and what-not, that can be done in a separate thread and we can link to it from here. But, that's a whole different discussion.

(I've done something similar to this in the past - instead of bringing up how I think our current path with inheritance might not be the best in any thread that talks about inheritance, I instead make a dedicated thread to just discuss it. Then, when I participated in inheritance-related threads, I could just do so under the assumption that TC39 wants to continue with their current trajectory, leaving the discussion of the trajectory itself to the other thread).

That is not javascript’s primary usage, even in those cases. Your opinion of “best left to” is fine, although i disagree with it - claims that JS is designed for a limited set of use cases are not.

1 Like

C++ (smart pointers) and Java (arrays and auto-boxed primitives) have their own legacy messes, so this isn't unique to languages that started as scripting languages.