1

Given:

var x = function () {
};

x.prototype = {
    y: {
        z: function () {
            console.log(this);
        }
    }
};

var foo = new x();
foo.y.z();

Why is this logged in the console as y instead of x and how is that possible given y is a literal object without a constructor?

Brian Cray
  • 1,277
  • 1
  • 7
  • 19
  • I don't think you're using prototypes right. You shouldn't assign a new value to the prototype. Always add stuff only. Like `x.prototype.y = function () { ..` instead of `x.prototype = { y: function () { ..` – Halcyon Feb 06 '13 at 01:37
  • @FritsvanCampen: There's nothing wrong with doing it like `x.prototype = {...}`. You shouldn't do it when extending native objects but if the object is your own that shouldn't be a problem. – elclanrs Feb 06 '13 at 01:40
  • @Frits, it's common to declare prototypes as one declaration as it performs slightly better than repeated member access. – Brian Cray Feb 06 '13 at 01:43
  • @BrianCray This is a bottleneck in your code? – 1983 Feb 06 '13 at 01:47
  • @xtal not a bottleneck, but something I had difficulty grasping. – Brian Cray Feb 06 '13 at 01:52
  • Note that `this` is set entirely by **how the function is called**. Since you called `z` as a method of `y`, then `this` within the function will reference `y`. – RobG Feb 06 '13 at 02:33

3 Answers3

2

"Why is this logged in the console as y instead of x..."

Because that's how JavaScript works. The this value is set as the object from which the method was invoked. Generally you should keep objects off of the .prototype. They're going to be shared among all instances created using the constructor.


"...and how is that possible given y is a literal object without a constructor?"

It's easy. The this value is not tied to any constructor. It's a dynamic value that is set based on how you invoke a function or method.


There are utilities that let you manually override the natural value that this is set to in an invocation. Using the .call or .apply methods of the Function.prototype to invoke the method is one example

var foo = new x();
foo.y.z.call(foo);

Now this in the z method will be the foo object, because we manually set it by passing it as the first argument to .call.

The .call method sees that it was called as a method of the z method, and so it invokes z for you, but sets the this value of z to whatever you provided as the first argument... in this case the foo object.


But generally you won't use objects as values of the .prototype. The reason is that all instances created from the constructor get an implicit reference to the .prototype of the constructor, so updates to any objects on properties of the .prototype will be observed from all instances.

the system
  • 9,244
  • 40
  • 46
  • is it only under "use strict"; that this would not be the global (normally window) in the case of a literal object? – Brian Cray Feb 06 '13 at 01:50
  • @BrianCray: Object literal is just a syntax for creating objects. It has no impact on the value of `this`. The value of `this` is set to `window` (or `undefined` in strict mode) only when there is no other value provided. In other words, if I invoked a function that is referenced from a variable instead of an object property, its `this` will be `window` – the system Feb 06 '13 at 01:53
  • Example: `var foo = {bar:function() {console.log(this);}};` We have object literal syntax creating an object with a single `bar` property referencing a method. If we do `foo.bar()`, then `this` is the `foo` object. But if we do this: `var baz = foo.bar; baz();`, now `this` will be `window` even though we're invoking the very same function. We just detached it and assigned it to a variable. – the system Feb 06 '13 at 01:56
  • Is there a better way to namespace things in prototypes then? For example, if I have pubsub methods inside my prototype, ideally they'd all be under events.on, events.off, etc. – Brian Cray Feb 06 '13 at 01:57
  • @BrianCray: Depends on what you mean. You can create an extended prototype chain so that the `.prototype` object of the `B` constructor is an object that inherits from the `.prototype` of the `A` constructor, and so the objects that are created from `B` will inherit the methods from both contructor prototypes. It's not really namespacing, but it does allow different organization of code. – the system Feb 06 '13 at 02:04
1

To make this work, you need y to return the enclosing this.

var x = function () {};
Object.defineProperty(x.prototype, "y", { get: function() { return this; } })
x.prototype.y.z = function () { console.log(this); }

var foo = new x();
foo.y.z(); // x

Whatever is to the left of the . on z() is this. In your question, y returns the object literal: {z:function(){...}}.

Plynx
  • 11,341
  • 3
  • 32
  • 33
  • Thanks for offering a way to achieve my intended outcome! I'm going to accept the system's answer since it answers my original question, but if I could accept two I would :) – Brian Cray Feb 06 '13 at 01:49
0
var y=Object.create({z: function () { console.log(this); }});
var x=Object.create(y);
y.z();
x.z();

You may use Object.create to create an object instead of using constructor.

simonleung
  • 44
  • 2