Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Javascript Constructors and Prototypes (tobyho.com)
52 points by nccong on Aug 1, 2013 | hide | past | favorite | 51 comments


This article does two things that are very dangerous, and never mentions the reasons why they're dangerous.

* Messing with Function's prototype is very strongly frowned upon. It's tantamount to setting global variables. At the very least, give that definition an if (!Function.new) guard to prevent redefining another implementation that another script (or the browser!) has already given.

* Adding methods in the constructor is awful even if you're not using inheritance, because it burns memory like crazy. If you define sayHi on Person.prototype, then every Person gets a reference to the same sayHi method. If you define this.sayHi in the constructor, then each Person you create gets its own copy of the function, making every Person heavier in memory. Not a big deal for a simple console.log, but if you have more complicated objects with a few dozen methods that you're using a bunch of you can really make things chug.


Since this is usually downplayed because the "code is shared", to be more specific, for example in V8 in x64 (node.js, Google Chrome), the theoretical minimum memory used by a function object is 72 bytes:

- Map pointer: 8

- Properties pointer: 8

- Elements pointer: 8

- Code entry pointer: 8

- Initial Map/prototype pointer: 8

- SharedFunctionInfo pointer: 8

- Context pointer: 8

- Literals/Bindings pointer: 8

- Weak fields pointer: 8

So if you have a Person class with 30 methods, the methods are taking 30 * 72 = 2160 bytes at the very least. The actual data for a person might take 200 bytes, if we store e.g. full name, age and address so in this case there is like 10x overhead. If you print the details of 200 people per page request and there are 100 people connecting to your server, you are wasting 200 * 100 * 2160 =~ 40 megabytes on memory on storing all these useless function objects at that moment. That is just crazy.

And in GC language it's never just memory, a GC will eat exponentinally more CPU time when the amount of memory you use reaches closer and closer to limits.


> If you define this.sayHi in the constructor, then each Person you create gets its own copy of the function, making every Person heavier in memory.

Does anyone know whether this is true for more sophisticated JS engines like v8? I seem to recall reading something like that, that it detects that it is the same function, but I might be misremembering it.


I don't know, but I would suspect it's still the case, because doing anything else would require a lot of analysis of the code's behavior. In order to safely merge all those definitions into one, the engine would have to know:

* That the function in question never accesses anything in its closure that could be different for each construction

* That hasOwnProperty() is never called on the object to check which way the method is declared (alternatively, remember that it had done this optimization, and spoof the return value)

* That the function is never used as a constructor. Otherwise, Person could update sayHi's prototype and wind up changing its effects for all Persons, where it was supposed to create a customized inner class.

And if you think any of these are easy, remember that you can get a pointer to that function via the following:

  var a = 'say', b = 'hi'
  var sayHi = (new Person)[a + b]


It's especially weird that the author did this after:

(a) showing awareness that polluting the global namespace is bad

(b) demonstrating a technique for being able to use a constructor with apply (the "newless" constructor he shows earlier)

but I think a lot of the article is less "here's a set of good practices" and more "let's play with some of the less-familiar corners of JS and demonstrate how flexible it is."


"In the first version, each time you create a person, a new sayHi function will be created for him, where as in the second version, only one sayHi function is ever created, and is shared amongst all persons that are created - because Person.prototype is their parent. Thus, declaring methods on the prototype is more memory efficient."


Ah, you're right, I missed that. Small amount of bile revoked.

But it should have been MUCH more strongly emphasized as the only way to do it, unless you have a very good reason, not tucked down in the bottom as "oh, if you feel like it, here's a trick to make your code a little better." Especially in a tutorial that's meant for beginners in JS.


I wanted to read this because I believe it's a great article, but the fact that he doesn't use semicolons pretty much turned me off.

While I was looking at the JavaScript code my brain almost exploded.


Did you see the article he linked about why he doesn't use them?

http://mislav.uniqpath.com/2010/05/semicolons/

Not reading an article because of that would be like not reading other code because it uses 2 spaces for tabs instead of 4. Makes no difference to the language, only to you.


The article (justifying not using semicolons) seems like a rant with not real aim, beside "I am right, semicolons are wrong - and I have all these half-baked ideas to back me up".

The author was actually annoying me by the time I reached "It's good coding style".

One example:

> My advice on JSLint: don’t use it. Why would you use it? If you believed that it helps you have less bugs in your code, here’s a newsflash; only people can detect and solve software bugs, not tools. So instead of tools, get more people to look at your code.

Pretty sure lots of people use JSLint, pep8 checkers, gofmt, or whatever the equivalent tool for the language at hand is. They certainly help, one cannot deny that.

Then the author goes on to pick at Crockford for suggesting people space their JS with four spaces… Yep, I'm done.


Giving advice to not use JSLint without a real alternative is very naive in my opinion. When I was working with a team of developers with some novice JS developers, JSLint was a godsend to pick up simple, avoidable bugs while enforcing some sanity with regards to code style.


> only people can detect and solve software bugs, not tools

Stupid compilers, they've been doing it wrong this whole time.


The thing is, semicolons do make a difference with JavaScript.

It's now 404ing, but there was a pretty well-known argument on one of Twitter Bootstrap's GitHub issues[1]. The Bootstrap guys didn't use semicolons, and it caused problems when minifying the code. It's an edge case, I know, but it was a problem.

I never understood the whole anti-semicolon thing anyway. It just seems really hipster to me. Use CoffeeScript, if you don't want semicolons.

  [1] https://github.com/twitter/bootstrap/issues/3057


The link to the issue you mention is broken because the bootstrap repository moved from twitter to twbs. The new link is https://github.com/twbs/bootstrap/issues/3057.

Also note, that ultimately regardless of personal semicolon preference, this was an issue because of a parsing bug in JSMin. It was fixed in JSMin with this commit[0]. Other minifiers such as Closure and YUI did not exhibit any issues with the code as it had been written.

[0] https://github.com/douglascrockford/JSMin/commit/5ca277ea452...


I've always equated javascript without semi-colons with missing comments and poor commit messages. It's just inconsiderate to other developers who have to read your code.

I understand that it is a "feature" of javascript (misfeature in my opinion), but the point that "everyone else does it is not a good argument" is false. We've got like 15 years of javascript written with semicolons. Stop being an inconsiderate prick and just friggin write the code like everyone else!

Don't be that guy (or gal). Comment your code. Write good commit messages. Use semicolons in your javascript.


It shouldn't, they genuinely are optional, and Javascript coders need to know when they can be inserted because you can't turn that behaviour off.

However, needing to understand the rules is one thing - I much prefer to see the semi-colons in there. I dislike seeing any formatting that is substantially different from the accepted norm and find it harms readability.


If it caused problems when minifying the code, the problem was with the minifier not parsing correctly - a minifier shouldn't change the way the code is interpreted.

I understand that using existing minifiers is a possible reason to use semicolons, but not using semicolons is not an inherent issue here.


Why does it seem hipster to use a language feature? Is it hipster to use null coalesce as well?

The article linked above explains the reasons to omit semi-colons very well. I would personally prefer if JS forced you to terminate all statements with a semi-colon to avoid any ambiguity, but there you go.

Speaking of ambiguity caused by whitespace, Coffeescript is a 1st degree offender for this. All you have to do is indent the wrong block of code, and you completely change the scope of a nested function, and you have no visual indication of your mistake whatsoever.


It's hipster because it goes against the accepted standard way of writing Javascript. See https://news.ycombinator.com/item?id=1547647&utm_source=twit...


My interpretation of "hipster" is being different because you think it's cool to be different. Being different because you think it's a better approach is called "making progress", either because you'll be proven right or proven wrong.

I've thought about the de-facto standard way a lot and I think that it does nothing to avoid bugs while potentially misleading a coder about the language. Therefore I think it's worse.

The only reason I follow the de-facto standard is because the time spent arguing about it with my peers is better spent getting work done.


Nobody has any good reasons that apply to most people, both for and against semicolons. There really isn't a huge difference in either style except in rare cases.

> The only reason I follow the de-facto standard is because the time spent arguing about it with my peers is better spent getting work done.

Which is why that is an excellent reason. Standards are useful, so if there is one, stick to it. If there is no reason to go against the standard, then don't.

> I think that it does nothing to avoid bugs while potentially misleading a coder about the language. Therefore I think it's worse.

Not seeing any semicolons can also potentially mislead about the language. It is not worse, they are both misleading until you realise ASI exists.

Omitting semicolons also does nothing to avoid bugs, and introduces a different (additional?) set of edge cases where bugs may appear.


Please let us know of one equivalent using semicolons for this semicolon-less bug:

  a = b + c
  (d + e).print()
Which is evaluated as

  a = b + c(d + e).print();
Taken straight out of http://mislav.uniqpath.com/2010/05/semicolons/

> Standards are useful, so if there is one, stick to it. If there is no reason to go against the standard, then don't.

Bingo. I think this is another reason to stick with the standard.


CoffeeScript certainly has its syntax quirks, but after some forced time using it I've found I can't go back to happily writing plain old JavaScript.

The first week or two you'll regularly need to peek at the compiled source to be sure it's doing what you want, but you'll quickly get a feel for it.

Also, a lot of the weird ambiguous cases are disambiguated by including parenthesis, so if you're unsure just include the parens and you should be ok.


Oddly,

> Also, a lot of the weird ambiguous cases are disambiguated by including parenthesis, so if you're unsure just include the parens and you should be ok.

this is exactly the parallel I was going to make to the discussion we're currently having here. People who want semicolons on every line, even when they don't matter, are the same people who want to parenthesize every infix operation, even when the natural precedence the expression has without parentheses is already correct. I'm not sure I understand it in either case--are you afraid that someone might edit the code without understanding the "implicit defaults" of the language's syntax? Why are you letting that person near your codebase?


Some programmers are good at remembering large numbers of arbitrary rules. Others aren't, and I've worked with good and bad programmers of both kinds.

So yes, I'm worried that a colleague might read the code and not know what the precedence is, and they will have to waste their time looking it up (thankfully some IDEs now have an command to add parentheses quickly, but it's still a distraction from their actual task).

Pretty much all languages have some features that are more confusing than helpful, and good codebases avoid using those features (whether via formal policy or not). IMO most precedence rules fall into that category; it would be better if e.g. "a && b || c" were a syntax error until bracketed properly.


Why would they be looking up the precedence?

If the code currently works, then they can read it, and infer that whatever precedence the operators have is the correct one for producing the result the code produces. If "a + b * c" is producing 17 where (a=2,b=3,c=5), then you know that your language makes multiplication precede addition.

If the code doesn't currently work, then they'll have to figure out via some external method (looking up the original formula used in the code, say) what the precedence needs to be, in order to parenthesize to make it work.

On a separate note,

> it would be better if e.g. "a && b || c" were a syntax error until bracketed properly.

this reminds me of the horribly-confusing practice of using "a && b || c" to mean "a ? b : c" in shell-scripting. It almost works, too... unless (a=true,b=false), in which case you unintentionally get the side-effects of c.


>If the code currently works, then they can read it, and infer that whatever precedence the operators have is the correct one for producing the result the code produces. If "a + b * c" is producing 17 where (a=2,b=3,c=5), then you know that your language makes multiplication precede addition.

By that logic why bother using a font in which * and + look like different symbols? Heck, why read the code at all? If the code is working you can infer what it must be doing by observing what comes out when you feed it different inputs.

You read code precisely because you don't know what it does for every input, or don't know how it implements the algorithm; you want to be able to look at a line and see what it does, without having to fire up a repl and run through several examples. I mean, the idea that code should be readable - i.e. that you should be able to tell what a given line of code does without having to run it or look it up - is about as fundamental a good coding principle as it gets.


There is a fundamental difference.

When you read "a + b * c"--and then test your assumption of what it does in a REPL--the result is a learning moment where that knowledge now sticks to you; from then on, you know which of the two operators come first. You only have to do it once.

On the other hand, "using a font in which * and + look like [the same symbol]" means never being able to recognize the pattern, which means never learning anything and having to check every time.

Also,

> the idea that code should be readable - i.e. that you should be able to tell what a given line of code does without having to run it or look it up - is about as fundamental a good coding principle as it gets.

I would agree that that is a good coding principle for low-level C/C++/Java code; in these languages, you can't separate the abstract meaning of code from its implementation, so it's better to just keep the two things together.

But on the other hand, the equivalent coding principle for Lisp is "create a set of macros which form a DSL to perfectly articulate your problem domain--and then specify your solution in that DSL." The equivalent for Haskell is "find a Mathematical domain isomorphic to your problem domain; import a set of new operators which match the known Mathematical syntax of that domain; and then state your problem in terms of that Mathematical domain by using those operators." In either of these cases, nobody can really be expected to just jump in and read the code without looking something, or a lot of somethings, up.

This isn't because the code is "objectively bad", but rather that it has externalized abstractions which in a lower-level language like C would have to be written explicitly into the boilerplate-structure of your code. These are just two different cultures.


>When you read "a + b * c"--and then test your assumption of what it does in a REPL--the result is a learning moment where that knowledge now sticks to you; from then on, you know which of the two operators come first. You only have to do it once.

If you're good at memorizing essentially arbitrary rules then you only have to do it once. For +/* you can argue that the precedence is standard in the domain language (mathematics), but in C-like languages there are often a dozen operators with their order, and there's no natural reason why >> should be higher or lower than /.

>The equivalent for Haskell is "find a Mathematical domain isomorphic to your problem domain; import a set of new operators which match the known Mathematical syntax of that domain; and then state your problem in terms of that Mathematical domain by using those operators." In either of these cases, nobody can really be expected to just jump in and read the code without looking something, or a lot of somethings, up.

Sure - code is written in the language of the domain. If you don't know what an interest rate calculation is then even the best-written implementation won't be readable to you. But that domain terminology should make sense (indeed a large part of understanding a field is understanding its terminology), whereas many language precedence rules don't - they're completely arbitrary, there's no way to derive them from first principles if you forget.

I'd argue that a language where you don't have to memorize a precedence list (such as Lisp - the precedence is always explicit from the syntax, one simply can't write (+ a b * c - so I'm kind of surprised you mention it). I'm surprised if Haskell is different in this regard) is, all other things being equal, better than a language where you do have to memorize a precedence list. But rather than having to write a whole new language, it's more lightweight to form a "dialect" by declaring "we will write C (or whatever), but only use constructs that do not require memorizing the precedence table".


But it's the same as the problem with ASI. By making the parens optional you are inviting ambiguity and mistakes.


Terrible article in my opinion teaching some really bad habits that will cause hard to find bugs over time. Truth is, omitting semi-colons is probably fine at smaller scale, but for larger javascript code base with multiple maintainers, it's definitely a better practice in my opinion. Oh well, no point in me saying what has already been said :)


>Did you see the article he linked about why he doesn't use them?

Yes, it's bollocks, goes against standard practice, and abuses a misfeature of the JS interpreter.


I agree with you, but still, I look at (a lot of) JavaScript every day and it's hard to explain it, but for some reason I find the lack of semicolons disturbing. I have no problem with CoffeeScript and I've also worked with Python a lot so I appreciate a language without semicolons, but I don't approve mixing the styles.

I think this is more of a psychological thing than a syntax thing because if you're used to a syntax of a language, it gets embedded in your brain and with years of practice you can spot a missing semicolon from a mile away after only a quick glance over the code. People are afraid of changes and if they are used to something and that thing is taken away they become unsettled.

Or their brain explodes if they see JavaScript with missing semicolons :P.


Maybe it is not necessary but I don't see anything against it. That was the weird thing about that article all kinds of semi legit reasons why it is not necessary to use them but no reason why not to use them. I would say if there are some semi legit reasons to use them and no reason not to use them than just use them :)


I defer to Brendan Eich on the matter:

https://brendaneich.com/2012/04/the-infernal-semicolon/


I totally understand the preference for using explicit statement termination in JS. I can even understand the general preference for a non-whitespace token to terminate a statement.

But I don't understand why that would cause someone's brain to explode or otherwise make it difficult for them to pick out the author's points about the language from the example code.


I used the prototype mechanism several times when debugging a script that I couldn't easily change source code for. I just typed:

SomeClass.prototype.someMethod

Which outputs the methods source. Then you can add some debug code like console.log(xxx) or even fix a simple bug, copy the whole thing and set it again:

SomeClass.prototype.someMethod = function ...

Voila! Hot code push without an IDE. While that's obviously not ideal, it certainly works in some cases.


My favorite trick along those lines:

  var cache = SomeClass.prototype.someMethod;
  SomeClass.prototype.someMethod = function () {
    debugger; // or `throw "stacktrace"`
    cache.apply(this, arguments)
  }
Inserts a stack trace right in the middle of executing someone else's code, which is nice for tracking down why/when that function gets called if documentation is poor, without changing functionality.


Good point. It's a quick way to debug/experiment.


I'm bracing for the downvote, but I'm going to say this anyway: are we all taking crazy pills?

I understand that we're more or less stuck with JavaScript, but this is nuts. JS has got to be one of the least intuitive, cobbled together languages I've ever had to work with. In what other language are people still having holy wars over how to separate statements?

Why the heck would a constructor be a regular function if calling it without new pollutes the global namespace? JS gives us prototypes so that we can have inheritance! Great, but that doesn't actually give us a simple ability to call super. Using functions as everything is fine and good, until you have to shoehorn all the functionality you actually need into mysterious constructs like prototype, new, and apply. When the symmetries are so half-assed, at some point, it seems to me to make way more since to stop overloading the same language construct and make separate constructs for separate uses.

And don't even get me started on implicit variable declaration, function hoisting, the double-equals, for...in, the necessity of self-calling functions, etc.

Thank God we have libraries and alternative syntaxes now that more or less smooth over these issues and coerce the programmer into writing reasonable JS code, because green field JS is a complete quagmire. Are we seriously incapable of doing better?


> In what other language are people still having holy wars over how to separate statements?

Probably most of the languages in popular use. It's just that the fight is usually directed towards another language.

JS happens to be one of the minority where the standard allows some flexibility and debate about usage.

> mysterious constructs like prototype, new, and apply

They're well-defined, so they're not particularly mysterious if you take the time.

> Are we seriously incapable of doing better?

Really depends on how much effort you're willing to invest in learning JavaScript, instead of trying to write the code you're used to from $OTHER_OO_LANGUAGE. Everyone unwilling to do that is probably never going to be capable of doing better.

This isn't to say there's anything wrong with finding another familiar set of abstractions friendly or personally productive, but personally, I find greenfield JS is pretty fun, flexible, and capable.

Then again, I think the same thing about Perl (which everyone knows is one of the worst languages ever), so YMMV.


Maybe mysterious wasn't the best choice of word, but my point is that it's quite possible for something to be well-defined and yet poorly usable.

I long since gave up on trying to write the code I'm used to in other OO languages in JS. When I'm in JS Land, I put a lot of effort into using best practices. I just find it frustrating that so many of those best practices do not at all seem to adhere to any sort of principle of least surprise. And I wouldn't say that my problem is being too tightly bound to a particular OO paradigm; I've had great fun programming in functional styles and Prolog.


"Someone pointed out though, that you can prevent this polluting of the namespace (those are just big words for creating global variables) by using this trick:

     function Person(name){
        if (!(this instanceof Person))
            return new Person(name)
        this.name = name
     }"
This is useful for situations where you might want to be able to create anonymous objects and call a series of methods on them, which can be a nice API as any user of jQuery knows.

It's also an arguably cleaner way of achieving the constructor+apply thing he does later -- no global modification of the Function prototype.

A lot of the time, though, I'll just consider this my default "class" definition:

    function Person (args) { this.init(args); }
If you want to be warned (or want others to be warned) when Person is called w/o new, this will do it (unless someone has defined init on the global namespace, so avoid that).

This also makes it easier to hand around the method that does the actual construction, which is helpful for cases like constructor + apply:

    var p = new Person;
    p.init.apply(p,args);
(again, without global modification of the Function prototype) or for cases where you want to refer to "superclass" methods down an inheritance hierarchy.


>>Cat.prototype = new Mammal()

This is only useful if Mamaml has only methods and no properties which does not happen that often.

>> Cat.prototype.constructor = Cat

I never saw a practical usage of this one :-)

I think these days the standard way of doing inheritance should be -> MyClass.prototype = Object.create(baseClass.prototype)


I wrote a similar (2 series) article a while back that attempts to explain the same thing, but with a few diagrams. I know sketching out what was going on really helped me ...

http://www.looselytyped.com/blog/2012/08/18/on-prototypal-in...

http://www.looselytyped.com/blog/2012/08/22/on-prototypal-in...


oh God!! why do some people have problem with adding semicolon?


If the arguments you wanted to pass to a constructor were variable in length, why not just send them in an array normally instead of having to jury-rig this? Are there benefits I'm missing?


I wrote an interactive guide to javascript that covers a lot of the same material.

http://caplin.github.io/new2JS/


Looks nice and it helpful, thanks. Maybe you could change the output so the latest output is on top instead on the bottom (more blog style). Also it would be nice to reset the output altogether


The article could have addressed overriding methods and calling the super method. Because that isn't simple.


It's breathe not breath...




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

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

Search: