0

I explore expressions and closures in javascript and i don't understand something. I code two objects X and Y, i call the second object in the first and when it run if the objects have methods with same names, the next call of a method of the first object will be set to the method of the class of the second object.

( function( prototypeY ) {
    Y = ( function() {
        return function() {
            prototypeY.getName = function() {
                return "Y";
            };
        };
    })();
})( Object.prototype );

( function( prototypeX ) {
    X = ( function() {
        return function() {
            prototypeX.getName = function() {
                return "X";
            };
            prototypeX.getObject = function() {
                return new Y();
            };
        };
    })();
})( Object.prototype );

var objectX = new X();
var objectY = objectX.getObject(); //new Y()

alert( objectX.getName() ); // "Y"

i hope someone can explain me why and show me how to transform it in order to make this works

user3511595
  • 77
  • 2
  • 7
  • Keep it simple always. You don't need closures here at all. – thefourtheye Oct 25 '14 at 10:24
  • What is your end goal? I can't work out what you're trying to achieve here. Do you want to establish some kind of inheritance hierarchy between `X` and `Y`? – T.J. Crowder Oct 25 '14 at 10:26
  • How can i call the Y constructor in the X constructor and not to touch the prototype of X ? – user3511595 Oct 25 '14 at 13:20
  • Maybe the following answer can explain constructor functions and prototype: http://stackoverflow.com/questions/16063394/prototypical-inheritance-writing-up/16063711#16063711 – HMR Oct 25 '14 at 14:33

1 Answers1

2

The reason you're seeing "Y" at the end is that you're overwriting the value you've put on Object.prototype.getName. When you call new X(), you're putting a getName property on prototypeX, which is a reference to (not a copy of) Object.prototype. So now Object.prototype has a getName which returns "X". When you call objectX.getObject(), that's doing a new Y() call, which involves setting getName on prototypeY. prototypeY is also a reference to (not a copy of) Object.prototype, and so the value set by Y overwrites the value set by X.

The solution is don't change Object.prototype. Adding properties to Object.prototype is generally a bad idea. Adding enumerable properties (ones that show up in for...in loops) like your getName and getObject properties is always a bad idea.

Below you've said:

I have a background in java and i would like to do that :

class Y, class X ( public Y getY() ( return new Y )

That's dramatically simpler than what you have:

// Constructor function X
function X() {
}

// Add some things to the prototype it assigns objects created via `new`.
// Note that the object `X.prototype` refers to has `Object.prototype` as
// its prototype, that's already been set up for you.
X.prototype.getName = function() {
    return "X";
};
X.prototype.getObject = function() {
    return new Y();
};

// Constructor function Y    
function Y() {
}

// Add something to the prototype it assigns objects created via `new`.
// `Y.prototype` also refers to an object that uses `Object.prototype` as
// its prototype.
Y.prototype.getName = function() {
    return "Y";
};

Or if you want to use named functions for debugging purposes, or have information that is only available to code within the various functions related to X and Y (like statics in Java), you might do this:

// Define X
var X = function() {
    // If you had `var` statements here, the vars would be accessible to
    // the code within this anonymous function, kind of like Java static
    // variables within a class.

    // Constructor function X
    function X() {
    }

    // Add some things to the prototype it assigns objects created via `new`.
    // Note that the object `X.prototype` refers to has `Object.prototype` as
    // its prototype, that's already been set up for you.
    X.prototype.getName = X$getName;
    function X$getName() { // There's nothing special about this name
        return "X";
    }
    X.prototype.getObject = X$getObject;
    function X$getObject() {
        return new Y();
    }

    return X;
}(); // () at the end runs the above, assigns its result to our variable

// Define Y
var Y = function() {
    // Constructor function Y    
    function Y() {
    }

    // Add something to the prototype it assigns objects created via `new`.
    // `Y.prototype` also refers to an object that uses `Object.prototype` as
    // its prototype.
    Y.prototype.getName = Y$getName;
    function Y$getName() {
        return "Y";
    }

    return Y;
}();

At one point I thought your goal was to set up an inheritance hierarchy where Y is derived from X, using constructor functions. According to your comment you don't, but I'll leave this in case others do want to. Here's how you'd do that hierarchy:

// Set up X
function X() {
}
X.prototype.getName = function() { 
    return "X";
};
X.prototype.getObject = function() { 
    return new Y(); // <== But this is usually a bad idea from an OOP perspective,
                    //     base classes really shouldn't refer to subclasses
};
X.prototype.answer = function() { // Something for Y objects to inherit
    return 42;
};

// Set up Y, where Y objects have a prototype that in turn has X.prototype
function Y() {
    X.call(this); // Chain to base constructor function
}
Y.prototype = Object.create(X.prototype);
Y.prototype.constructor = Y;
Y.prototype.getName = function() { 
    return "Y";
};

var x = new X();
console.log(x.getName()); // "X"
console.log(x.answer());  // "42"

var y = new Y();
console.log(x.getName()); // "Y", because Y overrides the X version of `getName`
console.log(x.answer());  // "42", because Y inherits the X version of `answer`

Object.create was added in ES5, but can be partially shimmed/polyfilled for older browsers (sufficient for our use above); here's MDN's version but with a different name for a variable where the MDN version used Object, which makes no sense:

if (typeof Object.create != 'function') {
  Object.create = (function() {
    var ctor = function() {};
    return function (prototype) {
      if (arguments.length > 1) {
        throw Error('Second argument not supported');
      }
      if (typeof prototype != 'object') {
        throw TypeError('Argument must be an object');
      }
      ctor.prototype = prototype;
      var result = new ctor();
      ctor.prototype = null;
      return result;
    };
  })();
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875