Im struggling with something quite simply in javascript. I want to have an array which is modifiable only through methods of an object. Consider the following example:
var Cart = function() {
this.items = [];
}
Cart.prototype.getItems = function() {
return this.items;
}
Cart.prototype.addItem = function(item) {
this.items.push(item);
}
module.exports = Cart;
I want to be able to add new items through the addItem method and to retrieve the items through the getItems method. I dont want to be able to just do Cart.items.push(item) for example.
How can i achieve this?
With ES6 WeakMap you can do it like this:
var items = new WeakMap();
var Cart = function() {
items[this] = [];
};
Cart.prototype.getItems = function() {
return items[this];
};
Cart.prototype.addItem = function(item) {
items[this].push(item);
};
module.exports = Cart;
In javascript you don't have private properties. Here are some options:
1 - call property _items. That's a well known convention for naming private properties;
2 - use closure:
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
};
this.addItem = function(item) {
items.push(item);
};
}
3 - use symbols. They are not truly private, but hard to discover:
var items = Symbol();
var Cart = function() {
this[items] = [];
}
Cart.prototype.getItems = function() {
return this[items];
}
Cart.prototype.addItem = function(item) {
this[items].push(item);
}
4 - use private-symbol module instead. This will work only in node.js/io.js (uses v8 C++ API), but gives you truly private symbols
5 - use WeakMap
var items = new WeakMap();
var Cart = function() {
items.set(this, []);
}
Cart.prototype.getItems = function() {
return items.get(this);
}
Cart.prototype.addItem = function(item) {
items.get(this).push(item);
}
Will this meet your requirements?
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
};
this.addItem = function(item) {
items.push(item);
};
}
You can achieve that functionality by leveraging the scope of your constructor.
Instead of defining the array as a public property, you define it as a variable:
var Cart = function() {
var items = [];
this.getItems = function() {
return items;
}
this.addItem = function(item) {
items.push(item);
}
this.addAndRetrieve = function(item) {
items.push(item);
return items;
}
}
module.exports = Cart;
Then, you can access it through the publicly exposed methods getItems and addItem
var x = new Cart(),
y = new Cart();
x.addItem(1);
y.addItem(2);
x.getItems(); // [1]
y.getItems(); // [2]
EDIT1: If these are the only 2 methods that you want to chain on your instance, you can simply combine them in a new function and achieve the combined functionality.
Related
I'm trying to implement the Builder Pattern to generate a JSON string of options that are passed into a library to generate widgets. I can't understand why at console.log below that this.options is undefined.
let Options = function(options) {
this.options = options;
}
let OptionsObjectBuilder = function () {
let options;
return {
addConstantLineToValueAxis: function (lineValue) {
console.log(this.options); // EQUALS UNDEFINED SO CAN'T ADD TO THIS OBJECT
this.options.valueAxis.constantLine.value = lineValue;
return this;
},
build: function () {
return new Options(this.options);
}
};
};
let option = new OptionsObjectBuilder().addConstantLineToValueAxis(1000000000).build();
There are two different ways for the builder to store the temporary states:
In the builder object itself (by setting this.options =)
In a closure (by setting options =)
The closure example has the benefit that the temporary builder state is not accessible to the outside.
You can use either way, as long as the builder uses them from the correct place. I will fix the broken example from the post you mentioned. I think they started using closures, and it didn't work because the param name was shadowing the closure variable and they ended up getting confused switching to using this instead. They forgot to update their build() function to read from the correct place.
Using builder object state - Exposes internal state
let Task = function(name, description, finished, dueDate) {
this.name = name;
this.description = description;
this.finished = finished;
this.dueDate = dueDate;
}
let TaskBuilder = function () {
return {
setName: function (name) {
this.name = name;
return this;
},
setDescription: function (description) {
this.description = description;
return this;
},
setFinished: function (finished) {
this.finished = finished;
return this;
},
setDueDate: function (dueDate) {
this.dueDate = dueDate;
return this;
},
build: function () {
return new Task(this.name, this.description, this.isFinished, this.dueDate);
}
};
};
let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
.setDueDate(new Date(2019, 5, 12));
let task = builder.build();
// Notice the builder does expose the name/description... properties
console.log({builder, task});
Using closure variables - Hides internal state
let Task = function(name, description, finished, dueDate) {
this.name = name;
this.description = description;
this.finished = finished;
this.dueDate = dueDate;
}
let TaskBuilder = function () {
let name;
let description;
let isFinished = false;
let dueDate;
return {
setName: function (pName) {
name = pName;
return this;
},
setDescription: function (pDescription) {
description = pDescription;
return this;
},
setFinished: function (pFinished) {
finished = pFinished;
return this;
},
setDueDate: function (pDueDate) {
dueDate = pDueDate;
return this;
},
build: function () {
return new Task(name, description, isFinished, dueDate);
}
};
};
let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
.setDueDate(new Date(2019, 5, 12));
let task = builder.build();
// Can't see the name/description... properties on the builder, just the methods
console.log({builder, task});
I believe I should only ever use this when returning at the end of the add functions in the builder pattern. I'm still not sure why in the example (zetcode.com/javascript/builderpattern) I was basing my code off of.. they set values with this.name, but passed name in their build function.
// #Steven de Salas: expando function: https://stackoverflow.com/a/44014709/1432612
function buildObjectDepth(obj, base) {
return Object.keys(obj)
.reduce((clone, key) => {
key.split('.').reduce((innerObj, innerKey, i, arr) =>
innerObj[innerKey] = (i+1 === arr.length) ? obj[key] : innerObj[innerKey] || {}, clone)
return clone;
}, Object.assign({}, base));
}
let Options = function(options) {
this.options = options;
}
let OptionsObjectBuilder = function () {
let options = {};
return {
addConstantLineToValueAxis: function (lineValue) {
options = buildObjectDepth({"valueAxis.constantLine.value": lineValue}, options);
return this;
},
build: function () {
return new Options(options);
}
};
};
let obj = new OptionsObjectBuilder().addConstantLineToValueAxis(1000000000).build();
str = JSON.stringify(obj);
str = JSON.stringify(obj, null, 4); // indented output.
alert(str);
I want to create a factory that is responsible for generating a PlayerList, but I'm having problems accessing the variables I set in the initialize function. The code is
app.factory("PlayerList", function(){
// Define the PlayerList function
var PlayerList = function() {
this.initialize = function() {
// create an array for our players
var players = [];
};
this.add = function(player) {
this.players.push(player);
}
this.remove = function(player) {
if ( players.length > 0 )
{
this.players.splice(players.indexOf(player), 1);
}
}
this.initialize();
};
return (PlayerList);
});
I want to refer to the players array inside the add and remove methods but I'm getting back undefined.
Here var players = []; is local variable for initialize but your are expecting this.players.push(player); means players should be in PlayerList scope.
So your factory should be look like
app.factory("PlayerList", function () {
// Define the PlayerList function
var PlayerList = function () {
var self = this;
this.players = [];
this.initialize = function () {
self.players = [];
};
this.add = function (player) {
self.players.push(player);
console.log(self.players);
}
this.remove = function (player) {
if (self.players.length > 0) {
self.players.splice(self.players.indexOf(player), 1);
}
}
this.initialize();
};
return (PlayerList);
});
var playerList = (function (){
var playerLists = {};
playerList.playerList = function() {
this.initialize = function() {
// create an array for our players
var players = [];
};
this.add = function(player) {
this.players.push(player);
}
this.remove = function(player) {
if ( players.length > 0 )
{
this.players.splice(players.indexOf(player), 1);
}
}
this.initialize();
};
return playerLists;
})();
app.factory("PlayerList",playerList.playerList);
I have the following service:
app.service('ItemService', function() {
my_items = [];
var removeItem = function(newObj) {
var toRemove = newObj;
var indexOf = my_items.indexOf(toRemove);
my_items.splice(indexOf, 1);
};
var resetItems = function(){
my_items = [];
};
var getItems = function(){
return my_items;
};
return {
removeItem: removeItem,
resetItems: resetItems,
getItems: getItems
};
});
and in my controller i have:
$scope.my_items = ItemService.getItems();
$scope.myCheckPush = function(item) {
ItemService.addItem(item);
}
$scope.myCheckRemove = function(item) {
ItemService.removeItem(item);
};
$scope.reset = function () {
ItemService.resetItems();
};
Here my remove function works well. But the reset function is not working as expected. Any ideas what i might be doing wrong?
Both my_items and $scope.my_items point to the same array. When you do my_items = [], you just remove the reference from my items, but since $scope.my_items still holds the reference the array is not deleted.
To delete the array use my_items = 0, which will actually delete the array's contents effecting $scope.my_items as well.
var resetItems = function(){
my_items.length = 0;
};
I have a sealed object with an array member on which I want to prevent direct pushes.
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d; }
});
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness
How can I prevent the push?
UPDATE:
Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.
you could override the push method:
var _d = [];
_d.__proto__.push = function() { return this.length; }
and when you need to use it in your module, call Array.prototype.push:
_b.addD = function (newD) {
Array.prototype.push.call(_d, newD);
};
I haven't done any performance tests on this, but this certainly helps to protect your array.
(function(undefined) {
var protectedArrays = [];
protectArray = function protectArray(arr) {
protectedArrays.push(arr);
return getPrivateUpdater(arr);
}
var isProtected = function(arr) {
return protectedArrays.indexOf(arr)>-1;
}
var getPrivateUpdater = function(arr) {
var ret = {};
Object.keys(funcBackups).forEach(function(funcName) {
ret[funcName] = funcBackups[funcName].bind(arr);
});
return ret;
}
var returnsNewArray = ['Array.prototype.splice'];
var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];
var funcBackups = {};
overwriteFuncs(returnsNewArray, function() { return []; });
overwriteFuncs(returnsOriginalArray, function() { return this; });
overwriteFuncs(returnsLength, function() { return this.length; });
overwriteFuncs(returnsValue, function() { return undefined; });
function overwriteFuncs(funcs, ret) {
for(var i=0,c=funcs.length;i<c;i++)
{
var func = funcs[i];
var funcParts = func.split('.');
var obj = window;
for(var j=0,l=funcParts.length;j<l;j++)
{
(function() {
var part = funcParts[j];
if(j!=l-1) obj = obj[part];
else if(typeof obj[part] === "function")
{
var funcBk = obj[part];
funcBackups[funcBk.name] = funcBk;
obj[part] = renameFunction(funcBk.name, function() {
if(isProtected(this)) return ret.apply(this, arguments);
else return funcBk.apply(this,arguments);
});
}
})();
}
}
}
function renameFunction(name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
})();
You would use it like so:
var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal
This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.
It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself
Thank you, dandavis!
I used the slice method:
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d.slice(); } // UPDATED
});
_b.updateC = function (newValue) {
_c = newValue;
};
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness
This allows me to protect from direct push calls enforcing some logic.
I am working on trying to make an JS object and access to private methods. The problem I am running into when trying to return a function is the private methods cannot be accessed. Code Below.
var Item = (function() {
var price = 0;
var name = '';
var description = '';
var quantity = '';
var attributes = {};
var Item = function(data) {
}
function setPrice(variable) {
this.price = variable;
};
function getPrice() {
return this.price;
};
function setName(variable) {
this.name = variable;
};
function getName() {
return this.name;
};
function setDescription(variable) {
this.description = variable;
};
function setQuantity(variable) {
this.quanity = variable;
};
return function(data){
setPrice : setPrice;
getPrice : getPrice;
setName : setName;
setDescription : setDescription;
setQuantity : setQuantity;
return new Item(data);
}
})();
item2 = Item();
item2.setPrice('3');
alert(item2.getPrice());
With this setup, how can I access the private methods?
I don't think that pattern will work for what you're trying to do. I think using a pattern like this one will keep your code smaller and more reusable. This way you also get rid of the set functions.
var Item = function(options) {
var opts = $.extend({
price: 0,
name: '',
description: '',
quantity: '',
attributes: {}
}, options);
// ...
this.getPrice = function() {
return opts.price;
};
// ...
};
var item = new Item({
price: 100,
name: 'onehundred',
// ...
});
alert(item.getPrice());
Fixed your code here: http://jsfiddle.net/pratik136/JryAk/
Items changed:
Check your return statement
Item is a var, you were trying to instantiate a class object item2