1

I have problem with create Object instance without reference.

I researched and found many people suggest using jQuery.extend to create object without reference.

Refer: What is the most efficient way to deep clone an object in JavaScript?

But it not success in my case.

Here is my code JSBin

var MyModel = (function() {
  MyModel = function() {};

  var myModelObj = {
    prop1: null,
    prop2: {
      sub1: null,
      sub2: null
    }
  };

  MyModel.prototype = {
    getProp1: function() {
      return myModelObj.prop1;
    },
    getSub1: function() {
      return myModelObj.prop2.sub1;
    },
    getSub2: function() {
      return myModelObj.prop2.sub2;
    },
    setProp1: function(val) {
      myModelObj.prop1 = val;
    },
    setSub1: function(val) {
      myModelObj.prop2.sub1 = val;
    },
    setSub2: function(val) {
      myModelObj.prop2.sub2 = val;
    },
    getObj: function() {
      return $.extend({}, myModelObj);
    },
    setObj: function(json_obj) {
      myModelObj.prop1 = json_obj.prop1;
      myModelObj.prop2.sub1 = json_obj.prop2.sub1;
      myModelObj.prop2.sub2 = json_obj.prop2.sub2;
    },
    setParam: function(prop1, sub1, sub2) {
      myModelObj.prop1 = prop1;
      myModelObj.prop2.sub1 = sub1;
      myModelObj.prop2.sub2 = sub2;
    }
  };
  return MyModel;
}());

var model1 = new MyModel();
model1.setParam('prop1', 'sub1', 'sub2');
var model2 = new MyModel();
model2.setParam('clone-prop1', 'clone-sub1', 'clone-sub2');
console.log("object 1");
console.log(model1.getObj());
console.log("object 2");
console.log(model2.getObj());

My expected result is

model1 = {
   prop1: 'prop1',
   prop2: {
      sub1: 'sub1',
      sub2: 'sub2'
   }
}

model2 = {
   prop1: 'clone-prop1',
   prop2: {
      sub1: 'clone-sub1',
      sub2: 'clone-sub2'
   }
}

But actually, model1 and model2 have same data of model2.

Can someone point me out where i made mistake?

=== Update ===

@arcyqwerty's solution help me solved create object without reference.

var MyModel = function() {
  this.prop1 = null;
  this.prop2 = {
    sub1: null,
    sub2: null
  };
};

MyModel.prototype = {
  getProp1: function() {
    return this.prop1;
  },
  getSub1: function() {
    return this.prop2.sub1;
  },
  getSub2: function() {
    return this.prop2.sub2;
  },
  setProp1: function(val) {
    this.prop1 = val;
  },
  setSub1: function(val) {
    this.prop2.sub1 = val;
  },
  setSub2: function(val) {
    this.prop2.sub2 = val;
  },
  getObj: function() {
    return $.extend({}, this);
  },
  setObj: function(json_obj) {
    this.prop1 = json_obj.prop1;
    this.prop2.sub1 = json_obj.prop2.sub1;
    this.prop2.sub2 = json_obj.prop2.sub2;
  },
  setParam: function(prop1, sub1, sub2) {
    this.prop1 = prop1;
    this.prop2.sub1 = sub1;
    this.prop2.sub2 = sub2;
  }
};

var model1 = new MyModel();
model1.setParam('prop1', 'sub1', 'sub2');
var model2 = new MyModel();
model2.setParam('clone-prop1', 'clone-sub1', 'clone-sub2');
console.log("object 1");
console.log(model1.getObj());
console.log("object 2");
console.log(model2.getObj());

But I also want use encapsulation feature in OOP. It means, we only get value object, property through get function. Is it possible on Javascript? It explain why i have an object inside Model (but it reference on same object)

Thank you very much!

Community
  • 1
  • 1
Manh Le
  • 1,630
  • 16
  • 26
  • 2
    Note that the accepted answer you linked to is not an answer to the question. BTW, the way you've wrapped your code in an IIFE does nothing useful, it's just confusing. Oh, your issue is that *model1* and *model2* both reference the same *myModelObj* object held in a closure by your IIFE. jQuery has nothing to do with the issue, nor does cloning the object. – RobG Mar 03 '16 at 03:10
  • @RobG: I want use `encapsulation` feature too. Is it possible on Javascript? Please see my updated question – Manh Le Mar 03 '16 at 03:29
  • Yes, it's possible. The thing being encapsulated (i.e. held in a closure) is *myModelObj*, but it seems that that isn't what you want to do. There is a public *MyModel* constructor in either case, so just use arcyqwerty's answer (which creates the same, single global: *MyModel*). – RobG Mar 03 '16 at 04:41
  • @RobG: I mean is it possible to keep it encapsulated & not refer to one object? – Manh Le Mar 03 '16 at 06:52

2 Answers2

3

Try this

var MyModel = function() {
  this.prop1 = null;
  this.prop2 = {
    sub1: null,
    sub2: null
  };
};

MyModel.prototype = {
  getProp1: function() {
    return this.prop1;
  },
  getSub1: function() {
    return this.prop2.sub1;
  },
  getSub2: function() {
    return this.prop2.sub2;
  },
  setProp1: function(val) {
    this.prop1 = val;
  },
  setSub1: function(val) {
    this.prop2.sub1 = val;
  },
  setSub2: function(val) {
    this.prop2.sub2 = val;
  },
  getObj: function() {
    return $.extend({}, this);
  },
  setObj: function(json_obj) {
    this.prop1 = json_obj.prop1;
    this.prop2.sub1 = json_obj.prop2.sub1;
    this.prop2.sub2 = json_obj.prop2.sub2;
  },
  setParam: function(prop1, sub1, sub2) {
    this.prop1 = prop1;
    this.prop2.sub1 = sub1;
    this.prop2.sub2 = sub2;
  }
};

var model1 = new MyModel();
model1.setParam('prop1', 'sub1', 'sub2');
var model2 = new MyModel();
model2.setParam('clone-prop1', 'clone-sub1', 'clone-sub2');
console.log("object 1");
console.log(model1.getObj());
console.log("object 2");
console.log(model2.getObj());

The problem with your original constructor is that instances of MyModel, although different objects created with the new keyword, all share the same myModelObj (which is only ever created once). Using this solution, new fields are created each time you craete a new MyModel.

This is similar to having MyModel = function() { this.myModelObj = {...}; } and accessing fields using this.myModelObj.prop but at that point, myModelObj is a bit redundant as you can just set the properties on this directly.

Also, using this solution, you can use model1.prop directly without having to say model1.getObj().prop (although that works too)

--

Note: it's also a little strange for me to see

var ClassName = (function() {
  ClassName = function() { ...; };
  ClassName.prototype = { ... };
  return ClassName;
})();

Is there a reason you're doing that instead of

var ClassName = function() { ... };
ClassName.prototype = { ... };

?

I suppose it makes sense in the original code if you didn't want to pollute the namespace with myModelObj, but it seems unnecessary otherwise.

--

Edit: encapsulation

If you require an object's properties to be set through getters/setters, you could try something like this:

var MyModel = function() {
  var privateObject = {
    prop1: null,
    prop2: {
      sub1: null,
      sub2: null
    }
  };
  Object.defineProperty(this, 'prop1', {
    get: function() { 
      console.log('Getting prop1 through getter');
      return privateObject.prop1;
    },
    set: function(value) {
      console.log('Setting prop1 through setter');
      privateObject.prop1 = value;
    }
  });
};

The downside is that you won't be able to share getter/setter functions using the prototype chain, meaning you'll have a lot of function objects hanging around. For a small number of instances, this is probably fine (performance-wise). It will also affect inheritance, if your class has subclasses.

If you're on a platform without defineProperty, you can also replicate this by keeping the var privateObject in the constructor and using this.getProp1 = function() { return privateObject.prop1; } in the constructor instead of on the prototype. The net effect is similar to using defineProperty.

--

Edit: or using getter/setter syntax

Note: the returned object is not an instanceof F.

function F() {
  var fields = { prop: null };
  return {
    get prop() {
      console.log("getter");
      return fields.prop;
    },
    set prop(value) {
      console.log("setter");
      fields.prop = value;
    }
  };
}
f = new F
f.prop = 123
f.prop
arcyqwerty
  • 10,325
  • 4
  • 47
  • 84
  • Thank you very much for your help. I tried your solution and it solved my problem. But my expectation is user cannot see property directly, like `encapsulation` feature in OOP. If they want get value, they **MUST** get through `get...` function. That's why i have an object inside Model. Any solution can solve this? – Manh Le Mar 03 '16 at 03:28
  • 1
    You can have strong encapsulation, with privacy more strict than in Java or C# (no reflection loopholes), and then have each object contain its own copy of its member functions, or you can have shared methods, in which case the data must be publicly available on the object. There aren't good options to achieve both. – Scott Sauyet Mar 03 '16 at 03:52
  • @ScottSauyet: of course, the correctness must be highest priority. But I wonder how we protect properties on `Javascript`? Example, use setter method, we can at least check input value before assign to variable. – Manh Le Mar 03 '16 at 04:05
  • 1
    Updated with a way to reach your goals, although it breaks prototypical inheritance and results in `fields * 2 * n` `Function` objects for `n` instances (as opposed to `fields * 2` `Function` objects for `n` instances) – arcyqwerty Mar 03 '16 at 14:38
  • Adding an answer that shows how you can achieve that sort of encapsulation, at the expense of losing the sharing of methods. – Scott Sauyet Mar 03 '16 at 16:23
  • @arcyqwerty: thanks mate! I will try your updated solution. But as you said, the model class become too complex and may affect to performance. I will consider about apply it or keep model simple with good performance :) – Manh Le Mar 04 '16 at 02:16
  • As an additional note, seeing you're coming from Java, some JS code uses appends `_` as part of a naming convention to denote private fields (for example, [Google JS style guide](https://google.github.io/styleguide/javascriptguide.xml -- it isn't mentioned specifically, but the example code uses this convention). It's easier to bypass the intended visibility in JS, but even in Java, devs can bypass the visibility intended by the author: http://stackoverflow.com/a/1555680) The takeaway is that you're expressing "this field is private, don't use it" and it's up to the dev to respect that. – arcyqwerty Mar 04 '16 at 14:44
1

This variant of arcyqwerty's answer demonstrates a much deeper encapsulation of your data. The trade-off is that each instance gets its own copy of the methods, rather than sharing them at a "class" level:

var MyModel = function() {
  var prop1 = null;
  var prop2 = {
    sub1: null,
    sub2: null
  };
  this.getProp1 = function() {
    return prop1;
  };
  this.getSub1 = function() {
    return prop2.sub1;
  };
  this.getSub2 = function() {
    return prop2.sub2;
  };
  this.setProp1 = function(val) {
    prop1 = val;
  };
  this.setSub1 = function(val) {
    prop2.sub1 = val;
  };
  this.setSub2 = function(val) {
    prop2.sub2 = val;
  };
  this.getObj = function() {
    return {
      prop1: prop1,
      prop2: {
        sub1: prop2.sub1,
        sub2: prop2.sub2
      }
    };
  };
  this.setObj = function(json_obj) {
    prop1 = json_obj.prop1;
    prop2.sub1 = json_obj.prop2.sub1;
    prop2.sub2 = json_obj.prop2.sub2;
  };
  this.setParam = function(_prop1, _sub1, _sub2) {
    prop1 = _prop1;
    prop2.sub1 = _sub1;
    prop2.sub2 = _sub2;
  };
};

You said,

But I wonder how we protect properties on Javascript?

I think that's the wrong question. JS is a different language, with different concerns than, say, Java or Ruby. It is at least as much a functional language as an OO one. You probably should not try to act as though you're working in Java when you're doing JS, but learn its folkways instead.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • thank you for explain to me! I come to JS with Java background, so they are new to me. And part of my question: `Is it possible on Javascript?` Thanks again :) – Manh Le Mar 04 '16 at 02:01