4 ms·
I'm a little amazed that you read all that, and still think it's so simple. Here's a simple counter argument to your "left of the dot" hypothesis: setTimeo
by jdmichal 9y ago
I'm a little amazed that you read all that, and still think it's so simple. Here's a simple counter argument to your "left of the dot" hypothesis:
setTimeout(foo.doStuff, 0);
When `doStuff` executes, `this` will not point to `foo`, even though it was "left of the dot". This same counter argument applies to every usage of a function as a first-class value.
- spion 9y agoBecause I said left to the dot at "call time". More succintly: > When you call a function, whatever is left of the dot is passed as the `this` argument The analogy is pretty good - you can't pass an argument to a function until you actually call it. In your example, the function hasn't been called yet, so no argument has been passed. foo.doStuff // <-- syntax to access a field member foo.doStuff() // syntax to call a field member with a `this` argument passed left of the dot. Furthermore, on the receiving side, the setTimeout function is seeing: function setTimeout(fn, delay) { // just fn, but not the argument. } Which makes it clear that you can no longer call that function with the proper `this` argument. My point was that `this` is only as confusing as you make it, and there is a simple and straightforward way to teach it (function argument). If you do that, the dozens of seemingly special cases go down to just a couple. Its unfortunate we don't yet have the bind operator https://github.com/tc39/proposal-bind-operator https://github.com/tc39/proposal-bind-operator If we had it, the explanation could be made even clearer. You would be able to call free-standing functions with a `this` argument left of the double colon, i.e. `thisArgument::freeStandingFunction()` - then the dot method call syntax `thisArgument.method()` could be explained as a syntax sugar for `thisArgument::(thisArgument.method)()` - a syntax sugar to at the same time access a function attached as a member field of an object and call it with that object as the `this` argument. The bind operator would make all those other `this` cases seem less special too. Of course you can call various bits of code with whatever `this` argument you want. The event handler code? `someElement::eventHandlerCode(event)`. A constructor? `Object.create(constructor.prototype)::constructor()`. Nothing special here, just a function argument, can have different values in different functions, depending what was passed as `this`. Its bizarre that this operator got stuck in stage 0 and is being subsumed by the pipeline operator (a terrible match for JavaScript). Looks like TC39 isn't interested in untangling the mess.
- jdmichal 9y agoThe thing is, I don't think anyone is really getting confused about `this` in your simplistic call-site left-of-the-dot model. They get confused when they do exactly the code I put, or similarly in a DOM event handler. And then there's weird cases, like calling bind on a bound function does nothing... If we what to talk about what we wish it looked like, I wish it was more like Rust, where left-of-the-dot is an implicit argument to an explicit parameter within the function.
- spion 9y agoThat example is still explained by the simplistic model. You have a function function setTimeout(callback, delay) { do the set timeout thing, at the right time, call callback(); } You pass a function argument to it, but you're not calling that function, so you haven't passed the `this` argument: setTimeout(someObject.someFn, delay) From within setTimeout, the code gets that function and calls it without anything left of the dot callback(); So the `this` argument gets the default value in that environment (undefined/global/window) Calling bind on a bound function is also explained by the simplistic model: function bind(f, o) { return function(...args) { return o::f(...args) } } Once you partially apply the `this` argument you get a new function that calls the old function with the specified object as the `this` argument; that newly created function has its own implicit `this` argument, but it doesn't use it for anything, so it doesn't matter if you bind it. Prototypical inheritance is also explained by the simplistic model. You call a method: o.f() The method is not on the object, so its looked up in its prototype chain. Lets say its found after the 2nd prototype: [o.__proto__.f, o.__proto__.__proto__.f] Once found its called, but left of the dot is still the original object `o` so that is what is going to become the `this` argument. o::(o.__proto__.__proto__.f)() To me this is simpler than any other late binding explanation I've read, for any language, ever.
- jdmichal 9y agoSo it's simplistic as long as you are capable of programming all these built ins with lower-level primitives... Which is not simple at all from the perspective of a beginner. I would think that once you get that far, `this` is probably not confusing anymore.