Object.create in javascript

A place to discuss the implementation and style of computer programs.

Moderators: phlip, Prelates, Moderators General

Object.create in javascript

Postby Zamfir » Fri Jan 13, 2012 12:04 pm UTC

I am currently learning to use javascript for more than one-off scripts, so I am trying to figure out which practices are good and which will bite me in the ass. It's mostly for own use, so I don't deeply care about old-browser-compatibilty.

The internetz seem highly divided on new, and some people seem to go a long way to avoid new in their code. Apparently some guru named Crockford has convinced the powers-that-be to make an Object.Create function that fits nicer in the prototype-based system of Javascript. Should I make this my standard approach to create derived objects, instead of playing around with new and .prototype?

If I understand it correctly, the main complaints about new are
A. if you forget to use it, the code screws up silently. I lack the experience to judge if this is important, or a minor issue.
B. new is a hack to make the language look misleadingly Java-esque with classes.
While on the plus side:
C. You often want a constructor function for objects anyway.
D. Separating templates from instances is good, even if javsascript doesn't enforce that separation

At the moment I am highly sympathetic to B. I really feel the itch to create java-like class hierarchies with abstract classes and what not, and avoiding "new" helps to think more javascripty. But is it also wise in the long run? Or is the constructor-function-with-new approach a good one, even if it's also a hack to make the language look more classical?
User avatar
Zamfir
 
Posts: 6125
Joined: Wed Aug 27, 2008 2:43 pm UTC
Location: Nederland

Re: Object.create in javascript

Postby Jplus » Fri Jan 13, 2012 2:47 pm UTC

I think you shouldn't worry about it and just do whatever seems most practical to you. Your own list of pros/cons seems quite complete to me, so I'll just go over it:
Zamfir wrote:If I understand it correctly, the main complaints about new are
A. if you forget to use it, the code screws up silently. I lack the experience to judge if this is important, or a minor issue.
Well of course it is important if a language feature introduces additional room for programmers to make mistakes. Still, it depends on what you want; you may find the benefits larger than the tradeoffs. Lots of professional programmers are happily using C even though pointers are rather tricky business.
Zamfir wrote:B. new is a hack to make the language look misleadingly Java-esque with classes.
I doubt whether this was the real motive for introducing the keyword 'new'. It's a solution to a problem. It's not the only possible solution, and perhaps not the best, but apparently it seemed like a good idea at the time. Once you choose this strategy, I definitely think 'new' is best possible name for the construct. Also note that 'new' in Java in fact doesn't mean the same as 'new' in C++. Such keywords have different meanings in different languages, simply because it would be impossible for them to have the same meaning.

Speaking of possible solutions, note that you can easily write a constructor function that doesn't make use of the 'new' keyword. Instead of this:
Code: Select all
function Vector (x, y) {
    this.x = x;
    this.y = y;
    this.length = function() { return Math.sqrt(this.x * this.x + this.y * this.y); };
}

Just write this:
Code: Select all
function Vector (x, y) {
    var self = { };
    self.x = x;
    self.y = y;
    self.length = function() { return Math.sqrt(this.x * this.x + this.y * this.y); };
        // in the line above perhaps replace 'this' by 'self', not sure
    return self;
}

But note that the former approach saves you two utterly repetitive lines of code, in exchange for using one additional keyword on instantiation.

Zamfir wrote:While on the plus side:
C. You often want a constructor function for objects anyway.
Yep. IMHO the constructor approach is the most convenient.
Zamfir wrote:D. Separating templates from instances is good, even if javsascript doesn't enforce that separation
Well... "good" is a subjective notion. But as I said, I think the constructor approach can be very practical.

Concluding, I think (B) is not as serious as it may seem, and (C) may outweigh (A) sufficiently to keep using 'new'. If you think otherwise, go ahead and switch to Object.create and nobody will be hurt.
If you feel that 'new' coerces you into Java-like behaviour, then perhaps you should just stop projecting Java onto everything. ;)

For further inspiration, you might want to have a look at the various ways in which classes are simulated in Lua (5 pages). Like JavaScript, Lua is prototype-based, and tables in Lua are the analogue to objects in JavaScript. Some of the approaches in Lua also involve 'new' (with yet a slightly different meaning), although it's only by convention.
Feel free to call me Julian. J+ is just an abbreviation.
Image coding and xkcd combined
User avatar
Jplus
 
Posts: 1427
Joined: Wed Apr 21, 2010 12:29 pm UTC
Location: classified

Re: Object.create in javascript

Postby Pepve » Fri Jan 13, 2012 7:36 pm UTC

Zamfir wrote:D. Separating templates from instances is good, even if javsascript doesn't enforce that separation

I don't follow, care to elaborate?
Pepve
 
Posts: 57
Joined: Wed Jul 28, 2010 9:47 am UTC

Re: Object.create in javascript

Postby Xanthir » Fri Jan 13, 2012 11:36 pm UTC

How well do you understand prototype-based OO like what Javascript practices, as opposed to class-based OO like Java?

Prototype-based OO is really simple - if you attempt to read a property from an object, and it doesn't exist on the object, it'll instead look for the property on the prototype, and the prototype's prototype, etc. until it hits a cycle or an object without a prototype.

Setting up prototypes is interesting. If you call a function as a constructor (by using new), the language creates a brand-new empty object, sets its prototype to the prototype of the constructor function, and then sets this inside the constructor to the new object (and also forces the constructor to return that object, if it doesn't return something useful).

There's some debate over what to use as a prototype when creating "subclasses". "Typical" practice in the wild is to make a fresh instance of the parent object and use that. For example:

Code: Select all
function List(){...}
List.prototype.add = function(){...}
List.prototype.index = function(){...}
List.prototype.map = function(){...}
...

function InfiniteList(){/* loops when you try to access past the end */}
InfiniteList.prototype = new List();
InfiniteList.prototype.index = function(){...}
...


This is mostly fine. A problem occurs, though, if a fresh instance of the superclass has significant instance-level state. For example, in the above example, a List might have a length property that gets set on the instance and updated by add(), del(), etc. An InfiniteList *won't* have a length, though, for obvious reasons. However, since InfiniteList's prototype is a List instance, it has a length property on its prototype chain (probably set to 0):

Code: Select all
var a = new InfiniteList(1,2,3);
console.log(a.length); // logs "0"


We can get around this by explicitly setting length on the prototype to Infinity, or running "delete InfiniteList.prototype.length;", or similar, but you have to do this for *every* piece of instance-level state on the prototype.

Another way is to avoid the instance and just descend from the prototype chain, like so:

Code: Select all
function InfiniteList(){...};
InfiniteList.prototype = Object.create(List.prototype, {});
InfiniteList.prototype.index = function(){...};


Here, InfiniteList's prototype is a fresh empty object with its prototype set to List.prototype. This is exactly the same as the previous situation (if it uses a List instance as its prototype, the the List's prototype is List.prototype, obviously), except that we're guaranteed that there's no extra state hanging around, since List's constructor never gets called. Since length only exists on List instances, not on its prototype, there's no pollution.

I recommend using the latter pattern. Note that you still construct stuff with the new keyword. Anyone who recommends otherwise is trying to trick you.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex

Re: Object.create in javascript

Postby Pepve » Sun Jan 15, 2012 3:36 pm UTC

Maybe I just do not really understand prototypes (I've never written anything that used them), maybe I was too impressed when Crockford said "power constructor". But I much prefer this code to the above:

Code: Select all
function makeList() {
    var items = [];
    return {
        add: function add(item) { items.push(item); },
        length: function length() { return items.length; }
    }
}

function makeInfiniteList() {
    var list = makeList();
    return {
        add: function add(item) { list.add(item); },
        length: function length() { return Infinity; }
    }
}

Which gives me plenty of OO, including encapsulation. So why should I even bother with prototypes and new?

Also, composition over inheritance.
Pepve
 
Posts: 57
Joined: Wed Jul 28, 2010 9:47 am UTC

Re: Object.create in javascript

Postby Xanthir » Sun Jan 15, 2012 10:19 pm UTC

Nonono, that's a *really bad* pattern. It means that every single instance of a List has its own unique copy of all the list functions, which is really memory-wasteful for no reason. It also makes it much harder to extend lists with new functions - you have to rewrite the constructor and/or monkey-patch every existing instance, rather than just hanging a new method off of the prototype.

Crockford has some very strange ideas about what makes "good" javascript. I would take everything he says with several grains of salt.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex

Re: Object.create in javascript

Postby Pepve » Mon Jan 16, 2012 10:34 pm UTC

The memory may be worth it, of course. Any idea how we could test/benchmark the different approaches?

Extension is indeed not really a feature of the idiom, but then again, I don't need it that often. Here it is in code (to show it's not really that awful):

Code: Select all
function makeBetterList() {
    var list = makeList();
    list.isEmpty = function isEmpty() { return this.length() === 0; }
    return list;
}


I would take everything anyone on the internet says with several grains of salt.
Pepve
 
Posts: 57
Joined: Wed Jul 28, 2010 9:47 am UTC

Re: Object.create in javascript

Postby Xanthir » Tue Jan 17, 2012 5:27 am UTC

That's not as useful of an extension - it means that you have to explicitly pass every single list you want to use through the function. Here's an even easier way, if you've done stuff properly:

Code: Select all
List.prototype.isEmpty = function() {
  return this.length == 0;
}


Bam, every instance of List in existence gets powered up without you having to do anything else. And you only have a single copy of the isEmpty function, instead of n copies.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex

Re: Object.create in javascript

Postby Zamfir » Tue Jan 17, 2012 7:05 am UTC

Thanks for the responses, it really helped to think this through. Patterns like Xanthir's first post are what brought me here in the first place. They made me wonder: is there in practice much use for the prototypical inheritance mechanism? That pattern is as close you can get to class-based object creation, with the .prototype properties of the constructor function acting as effectively passive classes. You could nearly do a string replace and end up with Python classes.

I thought that the idea behind prototypical inheritance was that code close to a GUI tends to have lots of one-off objects, with other objects that are similar but each with their own ideosyncracies. So it's attractive to just clone an existing object and make changes as needed, and only make factories or constructors when you know you need many instances.

Do you people have some examples where prototyping gets often used in a way that would be complicated with statically defined classes?
User avatar
Zamfir
 
Posts: 6125
Joined: Wed Aug 27, 2008 2:43 pm UTC
Location: Nederland

Re: Object.create in javascript

Postby Xanthir » Tue Jan 17, 2012 7:26 am UTC

I believe the main impetus behind prototypal inheritance is that it's much simpler than having a full-blown class system where classes and instances are fundamentally different kinds of objects, but it gets you 90% of the power. It does let you easily create objects that are nearly the same as an existing object, but I don't think I've ever actually used it like that.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex

Re: Object.create in javascript

Postby Zamfir » Tue Jan 17, 2012 9:45 am UTC

That makes sense.

I found this article from 1995 about NewtonScript, where they present the prototype style is a real advantage, not just a cheap&simple alternative. Javascript is from around those days, so I assume they had that kind of thing in mind when they made it.

The class-&instance kind of OOP became really big in the years afterwards, and nowadays everyone who does a little programming gets familiar with it. That might reduce the simplicity advantage of prototyping, since people already have an elaborate and experienced way of working with classes.

Another question, if you allow me: how much use do you make of inheritance in javascript? So not the single-level inheritance to make instances, but extensions like your InfiniteList above.

I personally find that I use inheritance a fair amount in static languages, since it plays nice with the IDE and compile-time sanity checking. But I used to do a lot of Python programming in the past, and there I rarely derived a class from another. In prety much every case where it might have made sense, I found it clearer to use composition, to include the 'base' class as property of other classes instead of parent. Does that sound familiar?
User avatar
Zamfir
 
Posts: 6125
Joined: Wed Aug 27, 2008 2:43 pm UTC
Location: Nederland

Re: Object.create in javascript

Postby Xanthir » Tue Jan 17, 2012 10:25 pm UTC

On second thought, using prototypal inheritance to slightly twerk an object is something that I *should* be using a lot more.

A common pattern in JS for handling lots of optional arguments is to accept an object as one of your arguments, with the keys being your keyword "arguments" (example, another example). The simplest possible way to merge the "default" options with the provided options is to set the default options object as the prototype of the provided options. The keys you pass in will shadow the defaults, but the rest will get taken from the default object.

In that NewtonScript paper, the advantage they cite *is* that it's cheaper and simpler. The class-based examples they present are more heavyweight, both conceptually and in actual computer memory.

Zamfir wrote:Another question, if you allow me: how much use do you make of inheritance in javascript? So not the single-level inheritance to make instances, but extensions like your InfiniteList above.

I personally find that I use inheritance a fair amount in static languages, since it plays nice with the IDE and compile-time sanity checking. But I used to do a lot of Python programming in the past, and there I rarely derived a class from another. In prety much every case where it might have made sense, I found it clearer to use composition, to include the 'base' class as property of other classes instead of parent. Does that sound familiar?


I don't know that I'm typical, but when I use inheritance, it's usually of the "abstract class" variety - that is, I create a base class solely to advertise to myself what the contract is that I'm expecting from several other types, and to provide a root for type-checking.

I also subclass Array reasonably often, so that I get all the array functions for free without having to explicitly delegate them.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex

Re: Object.create in javascript

Postby Zamfir » Wed Jan 18, 2012 4:04 pm UTC

I think I know what you mean, that's basically what I was referring to in the OP. I found myself wanting to make abstract classes in javascript, and I thought, WTF am I doing? The language doesn't enforce such contracts anyway, and an 'abstract class' is about the polar opposite of prototyping. The abstract class won't be a class, and it won't be abstract.

I could just as well write a comment describing the abstract class, and I am probably better off. No risk of calling an unimplemented method, have it delegated to the prototype, and get a silent fail.

So I wondered: is the syntax suckering me into 'thinking java', or are formal class hierarchies so useful that I should implement them even when the language doesn't ask or care for them?

Am I right that you are in the latter camp, that it's often a good idea to make javascript work like a class-based language?
User avatar
Zamfir
 
Posts: 6125
Joined: Wed Aug 27, 2008 2:43 pm UTC
Location: Nederland

Re: Object.create in javascript

Postby Xanthir » Wed Jan 18, 2012 9:59 pm UTC

No, I'm not - like I said, I use the "abstract class" concept for ad hoc contract advertising to myself, and for type checking, that's all.

For example, here's a snippet from my incomplete drawing library:

Code: Select all
function Paint() {}; // superclass for all paint objects

function SolidColor(color) {
  this.color = color;
}
SolidColor.prototype = new Paint();
...

function LinearColorPatch(tl,tr,br,bl) {
  ...
}
LinearColorPatch.prototype = new Paint();


This way, functions that expect a paint object can verify that with a simple "x instanceof Paint" check. This is obviously somewhat limiting - for example, I have some objects that are both Geometry and Warps, but I can't easily work around that with current JS. In the future we'll have things like WeakMaps and Unique Names that allow easier typechecking.
(defun fibs (n &optional (a 1) (b 1)) (take n (unfold '+ a b)))
User avatar
Xanthir
My HERO!!!
 
Posts: 4225
Joined: Tue Feb 20, 2007 12:49 am UTC
Location: The Googleplex


Return to Coding

Who is online

Users browsing this forum: No registered users and 12 guests