Copy object via closure - javascript

I'm trying to get a copy of an object like this:
graphs = (function () {
var trends = {
pointSize: 10,
};
// Object Oriented JavaScript - pp 109
var lockVariable = function(x) {
return function() {
return x;
}
};
var getTrendsConfig = function() {
return lockVariable(trends)();
};
return {
getTrendsConfig : getTrendsConfig
};
}());
c = graphs.getTrendsConfig();
c.pointSize = 11;
console.log(graphs.getTrendsConfig())
I was expecting to get printed a "{pointSize: 10}"
Because the getTrendsConfig function would pass the trends object to the lockVariable function, which would return the local value object: "{pointSize : 10}", instead I get "{pointSize: 11}".
I'm taking this from an example of the book "Object Oriented JavaScript" pp 108-109:
How do I get my expected result? Is it possible? And why this doesn't work?

Primitive values, such as numbers, in JavaScript, are immutable. You can copy i to x (as per the book), change i and leave x unchanged.
Objects are not immutable and are only ever addressed by reference. When you return the value of trends (c = graphs.getTrendsConfig();), you are returning a reference to the object (so c and trends both contain a reference to the same object). When you modify it, you modify that object. Getting another copy of trends gives you another copy of the reference… which still points to the same object.
The simple way to deal with this is to move the logic that creates the object inside the function that gets called.
graphs = (function() {
var lockVariable = function(x) {
return function() {
return x;
}
};
var getTrendsConfig = function() {
var trends = {
pointSize: 10,
};
return lockVariable(trends)();
};
return {
getTrendsConfig: getTrendsConfig
};
}());
c = graphs.getTrendsConfig();
c.pointSize = 11;
console.log(graphs.getTrendsConfig())
Although you could simplify that to
graphs = (function() {
var getTrendsConfig = function() {
return {
pointSize: 10,
};
};
return {
getTrendsConfig: getTrendsConfig
};
}());
c = graphs.getTrendsConfig();
c.pointSize = 11;
console.log(graphs.getTrendsConfig())
To go with something close to what your original code is trying to acheive, you could return an explicit copy of the object by using Object.assign()
graphs = (function() {
var trends = {
pointSize: 10,
};
// Object Oriented JavaScript - pp 109
var lockVariable = function(x) {
return Object.assign({}, x);
};
var getTrendsConfig = function() {
return lockVariable(trends);
};
return {
getTrendsConfig: getTrendsConfig
};
}());
c = graphs.getTrendsConfig();
c.pointSize = 11;
console.log(graphs.getTrendsConfig())

graphs = (function () {
var trends = {
pointSize: 10,
};
// Object Oriented JavaScript - pp 109
var lockVariable = function(x) {
return function() {
return x;
}
};
var getTrendsConfig = function() {
return lockVariable(trends)();
};
return {
getTrendsConfig : getTrendsConfig
};
}());
c = Object.assign({}, graphs.getTrendsConfig()); // same as ...graphs.getTrendsConfig() in ES6 spread syntax
c.pointSize = 11;
console.log(graphs.getTrendsConfig());
console.log("c =", c);
It's not producing your expected result because you re-assigned c.pointSize to 11 and that's why you're getting 11;
In JavaScript, assigning an object to a variable is done by reference and not by value. This means that you're simply copying over the object's location in memory, causing any modification to affect the original value.
In your example when you assign c = graphs.getTrendsConfig();, c will now point to the same object's location/address.
When you did this c.pointSize = 11, you modified the same (original) object and not a copy.
Solution:
In other to make a copy of graphs.getTrendsConfig() you could use Object.assign() or the new ES6 spread syntax .... By making a copy you won't be modifying the original object's pointSize variable.

Related

Why is the JSON stringification of a subclassed array an object?

/* StackOverflow needs a console API */ console.log = function(x) { document.write(x + "<br />"); };
B = function() {}
B.prototype = Array.prototype;
var a = new Array();
var b = new B();
a[0] = 1;
b[0] = 1;
console.log(JSON.stringify(a));
console.log(JSON.stringify(b));
JSON stringifies the subclass as an object ( { "0": 1 } ) instead of as an array ( [1] )`
Is there any way to modify this behaviour?
EDIT
I'm using (non-negotiably) ES5. I've simplified the example slightly. In reality, the subclassing is set up through a function inherit() which does this:
var inherit = function(base, derived) {
function F() {}
F.prototype = base.prototype;
derived.prototype = new F();
derived.prototype.constructor = derived;
};
As far as I know, you cannot inherit from array. As soon as you create a constructor function, the instances of it will be objects. When you want the functionality of an array, rather create an array and add the methods on it you want. This can be done with a function:
function createExtendedArray () {
var a = [];
a.method1 = function() {};
return a;
}

Setting properties on functions in JavaScript object literal notation

var api = {};
api.c = function () {return 1};
api.c.m = function () {return 2};
alert(api.c()); // returns 1
alert(api.c.m()); // returns 2
var api2 = {
c: function () {}; // can't put m inside c in object literal notation
};
How would we embed m in c in object literal notation?
You can't. However, you could do
Object.defineProperty(api.c, 'm', { value: function() { return 2; } });
Since Object.defineProperty returns the object, you could do
var api = {
c: Object.defineProperty(function() { }, 'm', {
value: function() { return 2; }
})
};
Or for multiple properties:
var api = {
c: Object.defineProperties(function() { }, {
m: { value: function() { return 2; } },
...
})
};
This may come closest to satisfying your desire to write the function properties in object literal form.
Or, you could use the extend feature available in most frameworks (or Object.assign in ES6):
var api = {
c: Object.assign(function() { }, {
m: function() { return 2; }
)
};
Feel free to replace Object.assign with $.extend, _.extend, etc.
Depending on your tolerance for ugliness, you might try the following, a variation on #zerkms's proposal without the IIFE (you'll need a variable x):
var api = {
c: (x = function() { }, x.m = function() { return 2; }, x)
};
It technically is possible, but it's ugly
var api2 = {
c: (function() {
var f = function() {};
f.m = 'something else';
return f;
}())
};
So I personally don't see a good reason to do it that way instead of how you do it in the 1st case.

Programmatically create a new object

I am trying to create a function which will take arguments arg1, arg2... then pass them into a constructor for a new object C like so: new C(arg1, arg2...), so to make a new instance of C the user would simply have to call C(arg) instead of new C(arg). Here is my first attempt:
var C = function(a){ this.a = a; }
var Cn = function(){
new C.apply(this, arguments);
}
Cn(0) // Should make a new C with a property a equal to 0
new C(0) // ie the same as this
Edit: Note, I need it to take an arbitrary number of arguments and not use eval. I'm making a library implementing Algebraic Data Types in js.
Edit: The solution was to take Jeremy's Idea and adapt it to take an unbounded number of arguments:
var C = function() {
// A unique object so we can identify when we used the 'newless' constructor
var newlessConstructorObj = {}
// Check to see if C has been called with `new`
if(!(this instanceof C))
// If not pass arguments as single arg back to C
return new C(newlessConstructorObj, arguments);
// Check to see if we got here from the line above, if so the arguments were passed in the second arg
var args = (arguments[0] === newlessConstructorObj) ? arguments[1] : arguments
// Do stuff with args
this.a = args[0];
}
C(0);
new C(0);
If you want to be able to call the function with or without the new keyword, you have to follow this pattern:
C = function(a) {
if (!(this instanceof C)) {
return new C(a);
}
this.a = a;
}
so to create a new instance of "C":
c = new C(a);
and
c = C(a);
will both return a properly formed instance of C
I would have picked Fabrizio Calderan's solution any day, but because you want this specific functionality - here is what predecessors tell us:
you can apply arguments to prototype.constructor (but there are issues when doing it with native types, like Number):
var Cn = function(){
return C.prototype.constructor.apply(C, arguments);
}
Link: Instantiating a JavaScript object by calling prototype.constructor.apply
or.. use eval:
function construct(Constructor)
{
/*
* or Array.prototype.slice.call(arguments, 1).map(function() { ... })
* in JavaScript 1.6+, compatibles, and with augmented Array.prototype
*/
var args = [];
for (var i = 1, len = arguments.length; i < len; i++)
{
args[i - 1] = "arguments[" + i + "]";
}
/* or args.join(", ") if you need it pretty-printed */
return eval("new Constructor(" + args + ")");
}
function Foo()
{
window.alert(Array.prototype.slice.call(arguments, 0).join(", "));
}
var f = construct(Foo, /bar/g, {baz: 42});
Link: http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/ff1a104bdc33d5c8
something like this?
var C = function(obj){
var a;
for (a in obj) {
this[a] = obj[a];
}
}
var Cn = function(obj) { return new C(obj); }
instance = Cn({
a : 1,
b : 2
})
instance.a //1
instance.b //2
instance.c //undefined
Doing it with a fixed number of args is simple:
// assuming one arg
function Cn(arg) {
return new C(arg);
}
// assuming two args
function Cn(arg0, arg1) {
return new C(arg0, arg1);
}
and so on. You can even make a general version for any number of parameters by iterating over arguments to create a string, then eval it. Crass, but effective.
But what's the point? Just to save typing 4 characters?
If you don't care about having a correct instanceof check you can try this:
var C = function(a){ this.a = a; }
var Cn = function(){
return C.apply({}, arguments); // notice an empty object here
}
Cn(0) // Should make a new C with a property a equal to 0
new C(0) // ie the same as this
Updated for ES6, you can use the spread operator:
var C = function(a){ this.a = a; }
var Cn = function(...args){
return new C(...args);
}
assert.deepStrictEqual(Cn(10), new C(10));

cloning an object in Javascript maintaining instanceof

I want to clone an object in Javascript. I have:
iPath = function () { this.heading = 0; this.path = []; };
loop = new iPath();
I know with jQuery I can do something like:
cloneLoop = $.extend(true, {}, loop);
but than
assert(cloneLoop instanceof iPath, "fails because loop is not an iPath");
How can i do a deep clone fulfilling last assert statement?
How about this:
cloneLoop = $.extend(true, new iPath(), loop);
...though I'm not sure if you'd want to do a deep copy. I'd think this would be better:
cloneLoop = $.extend(new iPath(), loop);
Extend simply copies properties from one object to another. So you have to start with a pristine copy of the object you want to copy into. So use new iPath() instead of {}.
var iPath = function () { this.heading = 0; this.path = []; };
loop = new iPath();
cloneLoop = $.extend(true, new iPath(), loop);
alert(cloneLoop instanceof iPath);
If not supporting older browsers is an option, you should be able to use Object.create:
var cloneLoop = Object.create(loop);
Here's a demo
function Foo() {
this.x = 1;
this.y = 1;
this.blah = { f: "a", g: "b" };
}
var f = new Foo();
var clone = Object.create(f);
alert(clone instanceof Foo);
alert(clone.blah.f);
alerts true, then a (at least on Chrome, older browsers will not support Object.create)
You'll need to write your own clone method:
Something along the lines of:
iPath.prototype = {
clone: function () {
var p;
p = new iPath();
$.extend(true, p, this);
return p;
}
}
cloneLoop = loop.clone();

Set length property of JavaScript object

Let's say I have a JavaScript object:
function a(){
var A = [];
this.length = function(){
return A.length;
};
this.add = function(x){
A.push(x);
};
this.remove = function(){
return A.pop();
};
};
I can use it like so:
var x = new a();
x.add(3);
x.add(4);
alert(x.length()); // 2
alert(x.remove()); // 4
alert(x.length()); // 1
I was trying to make .length not a function, so I could access it like this: x.length, but I've had no luck in getting this to work.
I tried this, but it outputs 0, because that's the length of A at the time:
function a(){
var A = [];
this.length = A.length;
//rest of the function...
};
I also tried this, and it also outputs 0:
function a(){
var A = [];
this.length = function(){
return A.length;
}();
//rest of the function...
};
How do I get x.length to output the correct length of the array inside in the object?
You could use the valueOf hack:
this.length = {
'valueOf': function (){
return A.length;
},
'toString': function (){
return A.length;
}
};
Now you can access the length as x.length. (Although, maybe it's just me, but to me, something about this method feels very roundabout, and it's easy enough to go with a sturdier solution and, for example, update the length property after every modification.)
If you want A to stay 'private', you need to update the public length property on every operation which modifies A's length so that you don't need a method which checks when asked. I would do so via 'private' method.
Code:
var a = function(){
var instance, A, updateLength;
instance = this;
A = [];
this.length = 0;
updateLength = function()
{
instance.length = A.length;
}
this.add = function(x){
A.push(x);
updateLength();
};
this.remove = function(){
var popped = A.pop();
updateLength();
return popped;
};
};
Demo:
http://jsfiddle.net/JAAulde/VT4bb/
Because when you call a.length, you're returning a function. In order to return the output you have to actually invoke the function, i.e.: a.length().
As an aside, if you don't want to have the length property be a function but the actual value, you will need to modify your object to return the property.
function a() {
var A = [];
this.length = 0;
this.add = function(x) {
A.push(x);
this.length = A.length;
};
this.remove = function() {
var removed = A.pop();
this.length = A.length;
return removed;
};
};
While what everyone has said is true about ES3, that length must be a function (otherwise it's value will remain static, unless you hack it to be otherwise), you can have what you want in ES5 (try this in chrome for example):
function a(){
var A = [],
newA = {
get length(){ return A.length;}
};
newA.add = function(x){
A.push(x);
};
newA.remove = function(){
return A.pop();
};
return newA;
}
var x = a();
x.add(3);
x.add(4);
alert(x.length); // 2
alert(x.remove()); // 4
alert(x.length); // 1
You should probably use Object.create instead of the function a, although I've left it as a function to look like your original.
I don't think you can access it as a variable as a variable to my knoledge cannot return the value of a method, unless you will hijack the array object and start hacking in an update of your variable when the push/pop methods are called (ugly!). In order to make your method version work I think you should do the following:
function a(){
this.A = [];
this.length = function(){
return this.A.length;
};
this.add = function(x){
this.A.push(x);
};
this.remove = function(){
return this.A.pop();
};
};
These days you can use defineProperty:
let x = {}
Object.defineProperty(x, 'length', {
get() {
return Object.keys(this).length
},
})
x.length // 0
x.foo = 'bar'
x.length // 1
Or in your specific case:
Object.defineProperty(x, 'length', {
get() {
return A.length
}
})
function a(){
this.A = [];
this.length = function(){
return this.A.length;
};
this.add = function(x){
this.A.push(x);
};
this.remove = function(){
return this.A.pop();
};
};

Categories

Resources