3 ms·
That example is still explained by the simplistic model. You have a function function setTimeout(callback, delay) { do the set timeout thing, at the right ti
by spion 9y ago
That 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.
- nybble41 9y agoThe confusing part for non-experts is that there are two "callable" types in JavaScript, functions and methods, but the language pretends that these two types are equivalent. Functions are self-contained and can be called without an object; methods depend on an implicit 'this' parameter. The fact that you can define and call a method using the syntax for a normal function call, without generating a type error, is a key part of the problem. The sequence "g = x.f; g()" should either be equivalent to "x.f()" or result in an error. Accepting both forms, but with subtly different behavior regarding implicit parameters, is bound to cause confusion. Note that in C++ there is a type-level distinction between pointers to ordinary functions (without 'this') and pointers to member functions, so the concepts expressed above are not without precedent in "real" languages. > To me this is simpler than any other late binding explanation I've read, for any language, ever. The object system in Lua achieves the same effect with much less complexity, IMHO. Methods in Lua are just ordinary functions which take an object as an explicit first parameter; there is no implicit 'this' argument. Lua objects are tables (associative arrays), with metatables providing a fallback for missing keys much like JavaScript's prototype chain. The dot syntax (x.f) is nothing more than syntactic sugar for the table lookup x['f']. To call methods idiomatically you use the colon operator x:f(...), which is equivalent to x.f(x, ...) except that x is only evaluated once. Like JavaScript, Lua is dynamically typed, with missing arguments defaulting to nil, so you won't get a clean type error if you forget to pass the first parameter. However, there is no hidden, context-sensitive 'this' to worry about, and if you pass an argument fn=x.f to a function which calls it as fn(...) then the arguments will be exactly the same as if you had written x.f(...) directly—most likely not what you intended, but it is at least apparent why it failed.
- spion 9y agoNo, there aren't two callable types in JavaScript. Thats precisely the point. Every single function has a `this` argument: function f() { return this + 5; } But there are only two ways to call it: f.call(5) // calls with `this` argument set to 5 and let x = new Number(5) x.f = f; x.f(); // syntax sugar for to x.f.call(x) The confusing part here is that `x.f()` is very different from `x.f`, and is most definitely NOT the same as `(x.f)()`, but more of an `(x.f).call(x)` Other than that and the fact that the argument is always there even if you don't specify it, its exactly the same as Lua, metatables (prototypes) and all. To resolve this confusion once and for all, we desperately need the bind operator [1] to provide a primitive on top of which the rest can be explained. [1]: https://github.com/tc39/proposal-bind-operator https://github.com/tc39/proposal-bind-operator
- nybble41 9y ago> No, there aren't two callable types in JavaScript. Thats precisely the point. Yes, that _was_ precisely the point. Even though "methods" which require a context and "functions" which do not are logically two distinct types, JavaScript treats them as the same: 'this' is always present in some form. Then, since many (most?) functions _don't_ actually require a context, it allows "methods" to be called without an explicit value for 'this', which amounts to leaving out a parameter. Except that this particular parameter has a confusing default which depends on the context in which the call occurs. Contrast this with other languages where the equivalent of 'this' is either included in the function's argument list or available only to code with a special "method" type which must be called with an explicit context. > The confusing part here is that `x.f()` is very different from `x.f`, and is most definitely NOT the same as `(x.f)()`, but more of an `(x.f).call(x)` Yes, exactly. The behavior changes depending on whether "x.f" is treated as a method call ("x.f()") or a plain function call ("(x.f)()"). And yet the only visible difference is putting parentheses around a legal value expression, or assigning the value to a variable or function parameter before calling it. Contrast this with Lua, where the colon/method-call operator is only legal within a function call ("x:f" is a syntax error unless followed by an argument list) and there is no difference between "x.f()" and "(x.f)()". > Other than [the difference between (x.f)() and x.f()] and the fact that the argument is always there even if you don't specify it, its exactly the same as Lua, metatables (prototypes) and all. Agreed, though Lua also has a dedicated "method call" operator which might be considered significant. However, those differences are exactly why the Lua approach is less "magical" and easier to comprehend and work with. The underlying workings are bound to be similar since they are both Turing-complete dynamically-typed imperative languages with prototypical object systems.