10

I am just learning Javascript and I was wondering, is using the prototype declaration, like this:

function TSomeObj()
{
  this.name="my object";
}

TSomeObj.prototype.showname = function() {
  alert(this.name);
}

Basically the same as doing it like this:

function TSomeObj()
{
  this.name="my object";
  this.showname = function() {
    alert(this.name);
  }
}

When I dump the object's properties I get the same result:

TSomeObj (inline version) =
{ 
    'name': 'my object',
    'test': function
}

TSomeObj (prototype declaration) =
{ 
    'name': 'my object',
    'test': function
}

What exactly is the benefit of using prototype declarations? Except less cluttering and more orderly sourcecode perhaps.

Update: I should perhaps have made it more clear that it was the final result i was curious about. The end result is ofcourse the same (i.e both register a new function in the object prototype) - but the way they do it is wildly different. Thank you for all replies and info!

Jon Lennart Aasenden
  • 3,920
  • 2
  • 29
  • 44
  • possible duplicate of [JavaScript: Setting methods through prototype object or in constructor, difference?](http://stackoverflow.com/questions/422476/javascript-setting-methods-through-prototype-object-or-in-constructor-differenc) – onteria_ May 28 '11 at 17:27
  • You can also just do `TSomeObj.showname = function() { alert(this.name); }` Which produces a static function as I understand it at least. – Jonathon May 28 '11 at 17:27
  • @onteria_ What is with all the duplicates lately, it seems that whenever a question is asked 5 other people almost instantly ask the same question with different wording. (some kind of point farming scheme)? – Jonathon May 28 '11 at 17:29
  • @jonathon: Cant believe you thought i was running a point scheme? Jez, give people the benefit of the doubt for christ sake... – Jon Lennart Aasenden May 28 '11 at 18:36

2 Answers2

23

Note: This answer is accurate but does not fully reflect the new way to create classes in JavaScript using the ES6 class Thing {} syntax. Everything here does in fact apply to ES6 classes, but might take some translation.

I initially answered the wrong question. Here is the answer to your actually-asked question. I'll leave my other notes in just in case they're helpful to someone.

Adding properties to an object in the constructor function through this.prop is different from doing so outside through Object.prototype.prop.

  1. The most important difference is that when you add a property to the prototype of a function and instantiate a new object from it, that property is accessed in the new object by stepping up the inheritance chain rather than it being directly on the object.

     var baseobj = {};
     function ObjType1() {
        this.prop = 2;
     }
     function ObjType2() {}
     ObjType1.prototype = baseobj;
     ObjType2.prototype = baseobj; // these now have the *same* prototype object.
     ObjType1.prototype.prop = 1;
     // identical to `baseobj.prop = 1` -- we're modifying the prototype
    
     var a = new ObjType1(),
       b = new ObjType2();
     //a.hasOwnProperty('prop') : true
     //b.hasOwnProperty('prop') : false -- it has no local property "prop"
     //a: { prop = 2 }, b : { prop = 1 } -- b's "prop" comes from the inheritance chain
    
     baseobj.prop = 3;
     //b's value changed because we changed the prototype
     //a: { prop = 2 }, b : { prop = 3 }
    
     delete a.prop;
     //a is now reflecting the prototype's "prop" instead of its own:
     //a: { prop = 3 }, b : { prop = 3 }
    
  2. A second difference is that adding properties to a prototype occurs once when that code executes, but adding properties to the object inside the constructor occurs each time a new object is created. This means using the prototype performs better and uses less memory, because no new storage is required until you set that same property on the leaf/proximate object.

  3. Another difference is that internally-added functions have access to private variables and functions (those declared in the constructor with var, const, or let), and prototype-based or externally-added functions do not, simply because they have the wrong scope:

     function Obj(initialx, initialy) {
        var x = initialx,
           y = initialy;
        this.getX = function() {
           return x;
        }
        var twoX = function() { // mostly identical to `function twoX() { ... }`
           return x * 2;
        }
        this.getTwoX = function() {
           return twoX();
        }
     }
    
     Obj.prototype.getY = function() {
        return y; // fails, even if you try `this.y`
     }
     Obj.prototype.twoY = function() {
        return y * 2; // fails
     }
     Obj.prototype.getTwoY = function() {
        return twoY(); // fails
     }
    
     var obj = new Obj();
     // obj.y : fails, you can't access "y", it is internal
     // obj.twoX() : fails, you can't access "twoX", it is internal
     // obj.getTwoX() : works, it is "public" but has access to the twoX function
    

General notes about JavaScript objects, functions, and inheritance

  1. All non-string and non-scalar variables in JavaScript are objects. (And some primitive types undergo boxing when a method is used on them such as true.toString() or 1.2.valueOf()). They all act somewhat like a hash/dictionary in that they have an unlimited(?) number of key/value pairs that can be assigned to them. The current list of primitives in JavaScript is: string, number, bigint, boolean, undefined, symbol, null.

  2. Each object has an inheritance chain of "prototypes" that go all the way up to the base object. When you access a property of an object, if that property doesn't exist on the object itself, then the secret prototype of that object is checked, and if not present then that object's prototype, so on and so forth all the way up. Some browsers expose this prototype through the property __proto__. The more modern way to get the prototype of an object is Object.getPrototypeOf(obj). Regular objects don't have a prototype property because this property is for functions, to store the object that will be the prototype of any new objects created using that function as their constructor.

  3. A JavaScript function is a special case of an object, that in addition to having the key/value pairs of an object also has parameters and a series of statements that are executed in order.

  4. Every time a function object is invoked it is paired with another object that is accessed from within the function by the keyword this. Usually, the this object is the one that the function is a property of. For example, ''.replace() boxes the string literal to a String, then inside the replace function, this refers to that object. another example is when a function is attached to a DOM element (perhaps an onclick function on a button), then this refers to the DOM element. You can manually choose the paired this object dynamically using apply or call.

  5. When a JavaScript function is invoked with the new keyword as in var obj = new Obj(), this causes a special thing to happen. If you don't specifically return anything, then instead of obj now containing the return value of the Obj function, it contains the this object that was paired with the function at invocation time, which will be a new empty object with the first parent in its inheritance chain set to Obj.prototype. The invoked Obj() function, while running, can modify the properties of the new object. Then that object is returned.

  6. You don't have to worry much about the keyword constructor, just suffice it to say that obj.constructor points to the Obj function (so you can find the thing that created it), but you'll probably not need to use this for most things.

Back to your question. To understand the difference between modifying the properties of an object from within the constructor and modifying its prototype, try this:

var baseobj = {prop1: 'x'};
function TSomeObj() {
   this.prop2 = 'y';
};
TSomeObj.prototype = baseobj;
var a = new TSomeObj();
//now dump the properties of `a`
a.prop1 = 'z';
baseobj.prop1 = 'w';
baseobj.prop2 = 'q';
//dump properties of `a` again
delete a.prop1;
//dump properties of `a` again

You'll see that setting a.prop1 is actually creating a new property of the proximate object, but it doesn't overwrite the base object's prop1. When you remove prop1 from a then you get the inherited prop1 that we changed. Also, even though we added prop2 after a was created, a still has that property. This is because javascript uses prototype inheritance rather than classic inheritance. When you modify the prototype of TSomeObj you also modify all its previously-instantiated objects because they are actively inheriting from it.

When you instantiate a class in any programing language, the new object takes on the properties of its "constructor" class (which we usually think of as synonymous with the object). And in most programming languages, you can't change the properties or methods of the class or the instantiated object, except by stopping your program and changing the class declaration.

Javascript, though, lets you modify the properties of objects and "classes" at run-time, and all instantiated objects of that type class are also modified unless they have their own properties that override the modification. Objects can beget objects which can beget objects, so this works in a chain all the way up to the base Object class. I put "classes" in quotes because there really isn't such a thing as a class in JavaScript (even in ES6, it's mostly syntactic sugar), except that the new keyword lets you make new objects with the inheritance chain hooked up for you, so we call them classes even though they're just the result of constructor functions being called with the new keyword.

Some other notes: functions have a Function constructor, objects have an Object constructor. The prototype of the Function constructor is (surprise, surprise) Object.

Inheriting from an object without the constructor function running

In some cases, it's useful to be able to create a new "instance of an object" without the constructor function running. You can inherit from a class without running the class's constructor function like so (almost like manually doing child.__proto__ = parent):

function inheritFrom(Class) {
   function F() {};
   F.prototype = Class.prototype;
   return new F();
}

A better way to do this now is Object.setPrototypeOf().

ErikE
  • 48,881
  • 23
  • 151
  • 196
  • I get the prototype/class "like" feature, what i was unsure about was if the prototype was.... ah, ofcourse not. It's executed in the context of a function, which means that each object will be initiated seperately and thus have a unique prototype - while when extending the prototype directly you affect all. So creating 1000 tSomeObj with inline code will create 1000 different versions, while declaring it as a prototype will reference the same template. I sort of knew that but got confused by the object dump. Thanks! – Jon Lennart Aasenden May 28 '11 at 17:42
  • No problem! I was just curious about the end-result of both approaches. My confusion was because i inspected the object *after* they were created, at which point there was no difference: The prototypes were the same. But the question of scope and (naturally) speed is the logical next question, so you killed 3 birds with one stone :) Thanks again! – Jon Lennart Aasenden May 28 '11 at 18:01
  • @Erik You said: "Each object has a prototype, which is a "template" object that is deep-copied when an instance of that object is generated." I think this is misleading. The prototype isn't deep-copied. What happens is that a property that isn't found in an object is searched in its prototype; and if it's not there in _its_ prototype, and etc. all the way up. That's why updating the prototype also "updates" all its derivated objects. – Zecc May 28 '11 at 18:08
  • I think there is some speed difference, where using prototype is faster because the job is done once instead of each time the object is instantiated. However, I don't think the difference is significant in most cases. – ErikE May 28 '11 at 18:09
  • @Zecc I will reword. I didn't mean deep-copied up the inheritance chain, but a deep copy of the properties as `x` in `this.a = {x:new Obj2()};`. But I actually could be wrong on this point. I don't have time to check right now but I will revisit this later. – ErikE May 28 '11 at 18:12
  • @Erik You can trust me on this. :) Property lookups are delegated upwards along the so-called prototype chain. I'd suggest reading this: http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ It clearly explains how JavaScript (ECMAScript-262) really works. – Zecc May 28 '11 at 18:19
  • Stoyan Stefanov takes on this problem in his book "Object-Oriented Javascript" which I am currently reading, and presents 3 different methods of cloning a prototype. What he refers to as "deepcopy" recursively goes through each property and clones every aspect. This has some speed penalty since it also clones sub-objects and every avenue of an architecture. The result is perhaps the closest thing to what you would expect in a "real" language like C++ or Object Pascal. Funny how limitations brings out the best :D – Jon Lennart Aasenden May 28 '11 at 18:21
  • The second method is more or less the same as you outline above. This is called "shallow copy" (prototypal inheritance, page 187), as it only clones the outer layer(s). This is the fastest and most common version since JS objects are rarely as complex as C++ or Object Pascal constructs. I personally would rather go for DeepCopy since I come from a Delphi background, but I have still much to learn. – Jon Lennart Aasenden May 28 '11 at 18:25
  • Hm. This is a brain teaser. If i clone "TSomeObject", and this object creates sub objects. How can these be initialized (e.g TSomeObject.A.B.C). But now that i think about it, as long as the cloning is recursive, each sub object will be re-created properly - because the prototype also includes the code that initializes these sub objects in the first place. Hm... well, better write a test for it because my mind refuse recusive thinking during weekends. As long as the constructur is invoked correctly, it should produce an identical copy in linear fashion – Jon Lennart Aasenden May 28 '11 at 19:16
  • @Zecc Perhaps I was not clear. I was having trouble imagining how you could possibly interpret my statements as saying that property lookups are not delegated upward along the prototype chain. I understand now it was due to my original point 2. However, please note that I was trying to address a a different issue: If an object is directly assigned a property which is itself an object (no prototypes or prototype properties involved here), are the sub-properties of that property (think a.b.c.d) also copied or do they keep their original references? After some thought I suspect the latter. – ErikE May 28 '11 at 19:19
  • @JonLennartAasenden Deep- vs shallow- copy is not a simple subject with a universal answer, and should be considered case by case. It's best if each object provides its own clone() method. Say for example you have a sheep and each sheep has legs and a flock it belongs too. When cloning a sheep you'll want to clone its legs, but not its flock (only the reference to it). – Zecc May 29 '11 at 21:48
  • 1
    @Erik Your objectCopy() function is wrong. Try this: `o1 = {name:'Oh One'}; o2 = objectCopy(o1); o1.name = 'Oh no!'; alert(o2.name) – Zecc May 29 '11 at 21:51
  • @Zecc Thank you kindly for taking the time to point out to me the flaw in my thinking! You're absolutely right. There's no copying going on... just hooking up the inheritance chain. I believe I've fixed my answer. If you notice any further mistakes I'd love to hear them. – ErikE May 30 '11 at 03:08
  • @Eric You need to fix your formatting in the first section, as all the code is in a single line. Point 5 in the second section is also misleading. The `new` operator provides a fresh object to the constructor function, through `this`, but this object will only be used as the result of `new` if the constructor does not return another object instead: `function NotReallyTwo() { this.val = '2'; return {val: 3}; }; three = new NotReallyTwo(); alert(three.val);` . All in all, I think cwolves' answer is a lot clearer and more to the point, sorry. – Zecc May 30 '11 at 09:29
7

The accepted answer missed the most important distinctions between prototypes and methods bound to a specific object, so I'm going to clarify

  • Prototype'd functions are only ever declared once. Functions attached using

    this.method = function(){}
    

    are redeclared again and again whenever you create an instance of the class. Prototypes are, thus, generally the preferred way to attach functions to a class since they use less memory since every instance of that class uses the same functions. As Erik pointed out, however, functions attached using prototypes vs attached to a specific object have a different scope, so prototypes don't have access to "private" variables defined in a function constructor.

  • As for what a prototype actually is, since it's an odd concept coming from traditional OO languages:

    • Whenever you create a new instance of a function:

      var obj = new Foo();
      

      the following logic is run (not literally this code, but something similar):

      var inheritsFrom = Foo,
        objectInstance = {};
      
      objectInstance.__proto__ = inheritsFrom.prototype;
      
      inheritsFrom.apply( objectInstance, arguments );
      
      return objectInstance;
      

      so:

      • A new object is created, {}, to represent the new instance of the function
      • The prototype of the function is copied to __proto__ of the new object. Note that this is a copy-by-reference, so Foo.prototype and objectInstance.__proto__ now refer to the same object and changes made in one can be seen in the other immediately.
      • The function is called with this new object being set as this in the function
    • and whenever you try to access a function or property, e.g.: obj.bar(), the following logic gets run:

      if( obj.hasOwnProperty('bar') ) {
          // use obj.bar
      } else if( obj.__proto__ ){
          var proto = obj.__proto__;
          while(proto){
              if( proto.hasOwnProperty('bar') ){
                  // use proto.bar;
              }
      
              proto = proto.__proto__;
          }
      }
      

      in other words, the following are checked:

      obj.bar
      obj.__proto__.bar
      obj.__proto__.__proto__.bar
      obj.__proto__.__proto__.__proto__.bar
      ... etc
      

      until __proto__ eventually equals null because you've reached the end of the prototype chain.

      Many browsers actually expose __proto__ now, so you can inspect it in Firebug or the Console in Chrome/Safari. IE doesn't expose it (and may very well have a different name for the same thing internally).

ortonomy
  • 653
  • 10
  • 18
  • Good answer. To be honest i was actually asking about the end result. And the end result is ofcourse that both examples are identical. But i learned a lot from your answere and eric's so i'm not complaining. Thank you for taking the time to reply, i gave the point to eric because he covered all aspects of my question from beginning to end - but your reply still matters! – Jon Lennart Aasenden May 28 '11 at 19:04
  • Besides perhaps not being complete enough, was there any misinformation in my answer? You seem more knowledgeable about this than I so I appreciate any critique. – ErikE May 28 '11 at 19:05
  • @Erik - I mostly just wanted to point out the fact that prototypes are preferred and how they work. A few things though: `2 - ... a "template" object that is copied` - Can lead to confusion. The object isn't exactly "copied", which would imply that it's a different object. It's more that it's "assigned" as the prototype of the new object. Same thing about #5. #6 - actually `.constructor` gets set to `obj.prototype.constructor`, which is the ___top___ element in the prototype chain unless you manually reset it after setting `obj.prototype`. Good answer in general, just wanted to clarify :) –  May 28 '11 at 19:14
  • I will go and do some more experimenting and try to tighten up my answer. I got frustrated with not understanding this stuff and spent probably 15 hours reading everything I could find, testing out my beliefs in code, and diagramming on a white board. Perhaps I didn't get it a comprehensively as I thought. I did partially update my answer. – ErikE May 28 '11 at 19:17
  • @Erik Again my advice is to go read http://dmitrysoshnikov.com/ecmascript/javascript-the-core/ . That contains priceless information about the usually misunderstood inner workings of JS. – Zecc May 29 '11 at 21:59