Hacker News new | past | comments | ask | show | jobs | submit login
JavaScript: what is "this"? (howtonode.org)
96 points by juvenn on March 21, 2010 | hide | past | favorite | 47 comments



If anyone wonders why every JavaScript framework under the sun rolls its own object system, and they're all incompatible, and almost nobody uses native JavaScript alone anymore, this is one of the reasons.


The reason why some frameworks roll out custom object systems is because they try to make JavaScript look like a language with classes, "this" semantics isn't what's driving them. In a language with classes developers are used to "this" being an instance of the current class, and this expectation creates a consfusion in JavaScript. If you don't look at it through the prism of classes, the rule for "this" is pretty simple: "this" is the receiver. Create a function, apply it to any object, and that object will be the receiver available as "this". It's that simple. Now the only confusion left is who's the receiver. In "foo.bar()" like cases it's obvious, and the only remaining case (if you don't count the discouraged "with" statement) is "bar()" which calls a function on the global object (like in Python).


You've left out event handlers, which are another case in which `this` can get confusing.


Would you expand on this? Most frameworks share some convenience functions that make working in js or DOM easier and put all of their functions within a particular namespace instead of spamming global. Whats the problem?


Almost nobody? Seriously? I'd be interested to see some data to back that up.

'this' takes all of about 5 minutes to understand in Javascript. It's pretty logical and there aren't really any surprises. Same with the prototypal inheritance.


> rolls its own object system

=== "Provides convenience functions for working with prototype chains." That has nothing to do with what makes the this keyword special in JavaScript, beyond prototypes affecting this' properties.


> "Provides convenience functions for working with prototype chains."

Yes, that's pretty much what an object system is, isn't it? It's some convenience behaviour, but it would be nice if the language had a built-in way of doing things that wasn't so inconvenient.


I wasn't disputing that, I'm saying it's irrelevant to the discussion (the commenter implied that prototype modification is related to how this is determined in a given function call).


I think it's pretty relevant. For example, something that is absolutely trivial in other languages (making super calls), is incredibly difficult to implement generically in JS, partially because of the way this behaves. This is something every class system (and of course just using prototype chains alone) in JS seems to run into. We ran into it early with Objective-J, and then CoffeeScript had the same problem: http://news.ycombinator.com/item?id=1014524


To clarify, you're saying super is difficult to implement in JavaScript? I think the power of JavaScript's design is borne out by how easily one can recreate classical inheritance in the language, a view shared by Crockford.


I'm not sure I understand. I'm saying specifically that at the very least, this one feature of classical inheritance is not easy to implement and in fact almost always leads to bugs when people try to do it. The truth is that most of the interesting features of classical inheritance are pretty hard to implement:

1. super, as described above

2. private, protected, etc. Never seen this work in js with inheritance

3. inheriting class methods as well as instance methods

If all you care about is the most superficial copying of a superclass' methods, then yeah of course its easy to do in JS, its essentially just prototypal inheritance again. Its arguably just as easy to implement this watered down form of classical inheritance in C as well (or in any language I guess):

struct MySubclass { struct MySuperclass base; int newMember; }

I don't think this is necessarily an argument for elegance however.

The point is, if these are features that everyone has to implement, then at some point you have to say "maybe its not that everyone else is stupid and doesn't get it, but that its an actually worthwhile feature". In fact, they predicted this (which is why class and super are reserved words in JS despite not doing anything), and its supposedly expected to come in the next iteration of JS (as in, the one after the one currently not even implemented yet). So, in 50 years when we get that we'll finally have remedied this issue.


> 1. super, as described above

Make the super property a reference to the parent prototype in your "extends" function. ExtJS does this with the "superclass" property.

> 2. private, protected, etc. Never seen this work in js with inheritance

What? This is basic stuff.

Private:

  var fn = function() {};
Protected:

  this.fn = function() {};
Public:

  Cls.prototype.fn = function() {};
> 3. inheriting class methods as well as instance methods

See above. May I refer you to http://www.crockford.com/javascript/inheritance.html ?


1. As described this has serious issues when you reach depths greater than 1. This is why prototype has to use the hack of passing in a special super variable through the function arguments. This is also why in ExtJS, doing something that in any other language is a simple "super.method()" requires doing the following (from their own subclassing examples):

    Ext.ux.IconCombo = function(config) {
 
        // call parent constructor
        Ext.ux.IconCombo.superclass.constructor.call(this, config);
 
    };
Wow. Fantastic. Even if we ignore the Ext.ux, you can't tell me this isn't absolutely ridiculous. For starters, it requires access to the actual class object which is only slightly better than inlining the superclass' actual name yourself. It means if you ever change the class name there are additional points of failure (change it to IconComboBox, and now all your IconCombo.superclasses don't work if you happen to forget to modify them). Additionally, this is SO CONFUSING to beginners. Look, I can see that its "powerful" if thats what you want to call it, but people who want to write apps, and not sit around pontificating about languages have to understand so much to know what is happening in that one line of code. Beyond the verbosity, they need to get that they are taking someone else's function and then applying it to yourself while switching out the "this" (remember how I said this came into play with super calls). This is taking a problem that was hard to solve at the framework level and pushing it to the user.

2. For starters, you conveniently didn't mention how to handle private and protected member variables which is the actual interesting bit here. The goal is to not have people accidentally write into your internal data representations. This is modularization 101 and is really difficult to do with JS, since this._x = blah is accessible to EVERYONE. this._x is NOT protected in any way, every party has access to it and can change it. The "var fn" of course doesn't work with private member variables since you need one for each individual instance, you can of course do the closure trick inside your constructor:

function MyClass() { function() { var myVar = 5; this.getMyVar = function() { return myVar; } }

But this is recommended against by everyone since it 1. no longer has the benefit of modifying the prototype so its hard to inherit these methods without further trickery, and 2 is very very slow.

So no, this is not basic stuff. Not if you actually want to implement classical inheritance instead of just using names from classical inheritance and applying them to random orthogonal features.

3. Again, you run into the same super problems.

Look, I am willing to believe that people like prototypal inheritance, and I can respect the position that JS doesn't have classical inheritance because its not good or because you should use prototypal or whatever. But come on, lets not call a few hacks on top of it "classical inheritance".

Yes, I've seen Crockford's page. Its not particularly enlightening to this discussion. It presents several different forms of inherity-stuff, none of which are particularly classical in my opinion, which comes right back to the OP's original point: everyone ends up doing inheritance slightly differently and spends way too much time thinking about this problem. The fact that so much has been written on how to do inheritance in JS should be a red flag in and of itself. I will gladly take other language's "stricter less powerful" models because at least I can focus on programming and not the volumes and volumes that have been written on how to do something that should be as simple as inheriting methods.


> The "var fn" of course doesn't work with private member variables since you need one for each individual instance,

This makes me believe that you have no idea what you're talking about.


Normally I'd take the time to explain, but I'm pretty sure the code example right below that statement makes evident what I was getting at, and its pretty clear to me that you just aren't reading my responses, so there's really no point.

But you're probably right, I probably have no idea what I'm talking about despite having written a popular JS framework and the most widely used mobile browser on the planet.


That is just a ridiculous thing to say to Francisco Tomalsky - I think he's already demonstrated that he knows a thing or two about hacking JavaScript (see Cappuccino - http://cappuccino.org/learn/). How about you respond by giving us a code example that shows how he is wrong, rather than trolling?


It's not entirely our fault, the language was designed to work like one thing (scheme-like), but look like another (c-like).

The baseless assertion rears its head again. People keep repeating this, and it's not true. JavaScript is not Scheme. It's not any more Scheme-like than Java, C++, Python, Perl, or many other languages. Lua and Ruby are far more Scheme-like than JavaScript.

I've commented before on people repeating this statement without ever examining it. I don't want to dredge up the arguments against it again ( http://news.ycombinator.com/item?id=1171202 ), mostly I just want to whine at it because it's so annoying. Why do you have to make JavaScript seem like something it's not? Are you trying to seem cooler by equating the language you work in to Scheme? What's wrong with JavaScript just being what it is?


I think the author is just citing how influential Scheme was to JavaScript's design.

In "Coders at Work", page 141, Brendan Eich talks about wanting to build a scheme-like language for the browser. The c-like syntax was adopted because Netscape wanted it to look like Java.


Maybe he wanted to. If that was his goal, I think he failed.

    Scheme: First-class functions, tail-call elimination, macros,
            hygienic macros, write and parse the AST directly.
    JavaScript: First-class functions.
    
    Scheme: Immutable arrays and lists, mutable strings.
    JavaScript: Mutable-only arrays, immutable-only strings.
Yes, I have, and have read, Coders at Work. I don't see how it's relevant. JavaScript and Scheme share only one defining feature in common, first-class functions. It's an important feature, no doubt, but many other languages have it: C/Objective-C (with Apple's blocks extension), C++ (with Boost.Lambda), Perl, Python, Ruby, Lua, Io, OCaml, ML, Haskell, the list goes on. People compared Perl to Lisp once, briefly, when it was one of the few dynamic languages, but they don't anymore.


They share only one defining feature? Bullshit. If we include "garbage collected", that gets rid of C and C++. If we include "dynamically typed", that gets rid of OCaml, ML, Haskell. If we include "single namespace for functions and variables", that gets rid of Ruby, Io, and Perl. Now we're down to JavaScript, Python, and Scheme, which, yes, are all in basically the same family of languages. If we include the (admittedly subjective) feature "small language spec", that gets rid of Python, and we're left with JavaScript and Scheme as the only two mainstream languages with those particular defining features.

If I were describing JavaScript to someone who was a programming language expert but somehow was never exposed to it, I would describe it as "Scheme, with C syntax, no tail calls, and a strange prototype-based object system".


People compared Perl to Lisp once, briefly, when it was one of the few dynamic languages, but they don't anymore.

They should; Perl 5 compares quite well and Perl 6 fares even better (of the nine fundamental features of a Lisp, lacking only homoiconicity). See the book Higher-Order Perl, for example.

http://hop.perl.plover.com/


Oh, come on, you're cherry picking the features that suit your argument. If we include "garbage collected", that gets rid of C and C++. If we include "dynamically typed", that gets rid of OCaml, ML, Haskell. If we include "single namespace for functions and variables", that gets rid of Ruby, Io, and Perl. Now we're down to JavaScript, Python, and Scheme, which, yes, are all in basically the same family of languages. If you add the (admittedly subjective) feature "small language spec", that gets rid of Python, and we're left with JavaScript and Scheme as the only two mainstream languages with that particular feature set.


You have to compare JavaScript with Scheme of the time when JavaScript was designed. Both macros and immutable structures are pretty recent (1999 and 2006 IIRC) additions to Scheme, previously such facilities were either non-existent or implementation-specific.

While Scheme's syntax superficially looks like it's data, specification does not imply that there should be accessible AST as list structure anywhere (in contrast to Common Lisp, which is specified in terms of list structures and algorithm to build them from text serialization)

And by the way: You have left off the one most important defining feature of Scheme: first-class continuations. And JavaScript really does not have them (I would say that this is completely reasonable omission for JS's purpose)


JavaScript definitely isn't Scheme (just as it definitely isn't Java either), but it is correct that it was conceived as a somewhat Scheme-like language with an OO-model inspired by Self, but for marketing reasons it was given a superficially Java-like syntax (and name!).

It is interesting to imagine what could have happened if Netscape had shipped with a Lisp. Probably a lot more people would hate Lisp today!


Egh, some of those other languages are more Scheme-like than others. In particular, Javascript is certainly more scheme-like than Java, which lacks even first-class functions, for example. And, well, Python is basically just Lisp with different syntax (or so Norvig claims; I'm not sure I'd go that far: http://norvig.com/python-lisp.html).

So I can't really agree that it's not any more Scheme-like than anything else out there. I do agree it's not particularly Scheme-like.


Javascript's scoping is also closer to Scheme's lexical scoping, I think. If you always use var for local variables, their scope will always be the immediately enclosing function. With Python, the scope of a variable is less clear. To me, the combination of first class functions, anonymous function syntax, and lexical scope are very high on the list of features that make a language "Scheme-like."


How are Python's scoping rules less clear? They are identical to Javascript's (and Scheme's), except that instead of defaulting to global scope, they default to local scope (which can be overridden with `global foo`). Also, it has one extra kind of scope, class scope. I would agree with you if you said Python's class scope is rather strange, of course.


This point of view is also maintained by D Crockford[1] who is supposed to know JavaScript's background.

[1] IIRC eg in this presentation: http://www.infoq.com/presentations/The-State-and-Future-of-J...


"Some other guy who other people say is right said something similar. Here is a link to a talk he once did."


Some guy who happens to be a member of the ecmascript committee ...


I had a lot of trouble with this as a newbie to Javascript. Glad to see it posted, even if it's months and months late for me.


The var statement declares a variable as local to the current scope and the entire current scope, not just from the var statement onward. These local variables shadow any existing variables from outer scopes.

Err, I tested this on Chrome's Inspector console, and I don't see such behaviour. The article says name should be undefined at the time of the comparison in the below code, but it isn't (and I frankly can't see why anyone would want it to be). Is this a feature of my execution environment, or have I just completely misunderstood what the author means?

  (function(name){var cmp=name=="tim"; var name; return cmp})("tim") // returns true


Since the comparison happens before var is declared, it will always evaluate to 'true'. I believe the author was thinking about something like so:

    (function(name) { var cmp = function() { return name=="tim"; }; var name; return cmp();})("tim");
This still evaluates to true, but I believe it has to do with the rules of shadowing. If you change the declaration to 'var name="bob";' it will return false.


Good catch, I'm looking into this one. I know I've seen this behavior before in some JS environment. I'm glad it's not the case for V8 and will shortly update the article.


Honestly, I think this is all too complicated. In Javascript, when you look at a line like this:

  a=1
you have to guess in which scope it changes or creates the variable a. I prefer the way PHP is dealing with scope. You simply know that "a=1" is only affecting the current scope.


Which is why PHP is so much less flexible than JS. Also try this:

  $a = 1;
  function qqq() {
    global $a;
    unset( $a );
  }
  qqq();
  var_dump( $a );
P.S.: In PHP 5.1.6 calling unset() actually increases memory usage...


I find it hard to believe that the largest cause (or even a significant cause) in the difference of flexibility of two languages is whether or not you can declare variables to belong to a given scope, and to which scope they default to.


I was referring more to the "feel" of the language rather than the fact that this individual feature is the single reason for why JavaScript is more flexible.


> Which is why PHP is so much less flexible than JS.

When running, I want enough flexibility to move freely but not so much that my knees break. When programming, I'd like to rely on "undefined" not being 6, and I'd even like to have a reasonable built in dictionary type that other code can't extend to add keys to by default, or overwrite "hasOwnProperty".


I never claimed that JavaScript was better, just more flexible. I agree that all the things you mentioned are pain points with JS.


global $a in PHP is just another way to write $a =& $GLOBALS['a'];

I.e. you just create reference to a global variable. Unsetting reference just breaks the tie between variable name and content.


Exactly, which means that unset( $a ) is not the same thing as unset( $GLOBALS['a'] ). It makes perfect sense, just not a kind of thing you might not expect at first.


This kind of a bind is available in dojo via dojo.hitch and in google closure via goog.bind (make sure to require goog.base)

There's a $.hitch plugin in JQuery, it doesn't come with it by default.



I feel it's worth noting that JavaScript developers refer to "this" as the execution context.


It's this.


I appreciate that HN isn't Reddit, and expect that comment to be treated appropriately, but somewhere I hope there's a Hacker who loves Faith No More and just smiled.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: