Disable property mutation in JS - javascript

I was creating a component and was trying to break my implementation. The idea is to not allow user to manipulate the exposed properties.
The implementation was like this:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data; },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, setting the property is handled but user is still able to mutate it using .push. This is because I'm returning a reference.
I have handled this case like:
function MyClass(){
var data = [];
Object.defineProperty(this, 'data', {
get: function(){ return data.slice(); },
set: function(){ throw new Error('This operation is not allowed'); },
configurable: false,
});
}
var obj = new MyClass();
try {
obj.data = [];
} catch(ex) {
console.log('mutation handled');
}
obj.data.push('Found a way to mutate');
console.log(obj.data)
As you see, I'm returning a new array to solve this. Not sure how it will affect performance wise.
Question: Is there an alternate way to not allow user to mutate properties that are of type object?
I have tried using writable: false, but it gives me error when I use it with get.
Note: I want this array to mutable within class but not from outside.

Your problem here is that you are effectively blocking attempts to modify MyClass. However, other objects members of MyClass are still JavaScript objects. That way you're doing it (returning a new Array for every call to get) is one of the best ways, though of course, depending of how frequently you call get or the length of the array might have performance drawbacks.
Of course, if you could use ES6, you could extend the native Array to create a ReadOnlyArray class. You can actually do this in ES5, too, but you lose the ability to use square brackets to retrieve the value from a specific index in the array.
Another option, if you can avoid Internet Explorer, is to use Proxies (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
With a proxy, you can trap calls to get properties of an object, and decide what to return or to do.
In the example below, we create a Proxy for an array. As you see in the handler, we define a get function. This function will be called whenever the value of a property of the target object is accessed. This includes accessing indexes or methods, as calling a method is basically retrieving the value of a property (the function) and then calling it.
As you see, if the property is an integer number, we return that position in the array. If the property is 'length' then we return the length of the array. In any other case, we return a void function.
The advantage of this is that the proxyArray still behaves like an array. You can use square brackets to get to its indexes and use the property length. But if you try to do something like proxyArray.push(23) nothing happens.
Of course, in a final solution, you might want decide what to do based on which
method is being called. You might want methods like map, filter and so on to work.
And finally, the last advantage of this approach is that you keep a reference to the original array, so you can still modify it and its values are accessible through the proxy.
var handler = {
get: function(target, property, receiver) {
var regexp = /[\d]+/;
if (regexp.exec(property)) { // indexes:
return target[property];
}
if (property === 'length') {
return target.length;
}
if (typeof (target[property]) === 'function') {
// return a function that does nothing:
return function() {};
}
}
};
// this is the original array that we keep private
var array = [1, 2, 3];
// this is the 'visible' array:
var proxyArray = new Proxy(array, handler);
console.log(proxyArray[1]);
console.log(proxyArray.length);
console.log(proxyArray.push(32)); // does nothing
console.log(proxyArray[3]); // undefined
// but if we modify the old array:
array.push(23);
console.log(array);
// the proxy is modified
console.log(proxyArray[3]); // 32
Of course, the poblem is that proxyArray is not really an array, so, depending on how you plan to use it, this might be a problem.

What you want isn't really doable in JavaScript, as far as I'm aware. The best you can hope for is to hide the data from the user as best you can. The best way to do that would be with a WeakMap
let privateData = new WeakMap();
class MyClass {
constructor() {
privateData.set(this, {
data: []
});
}
addEntry(entry) {
privateData.get(this).data.push(entry);
}
getData() {
return privateData.get(this).data.concat();
}
}
So long as you never export privateData don't export from the module, or wrap within an IIFE etc.) then your MyClass instances will be able to access the data but external forces can't (other than through methods you create)
var myInstance = new MyClass();
myInstance.getData(); // -> []
myInstance.getData().push(1);
myInstance.getData(); // -> []
myInstance.addEntry(100);
myInstance.getData(); // -> [100]

Related

Is there a way to dynamically add getter to a class in javascript/typescript?

I need some guidance or expert knowledge on JavaScript capabilities.
I'm studying TypeScript ATM, specifically decorators functionality.
Is there a way to dynamically add a getter method to a Prototype object so that it is executed in place of the plain property access on an instance.
Here's some code for example:
class Car {
#decorate
color: string = 'red';
drive(): {
return 'Driving';
}
}
function decorate(target, key): void {
//would be cool to add a getter and update
//the prototype in target to contain such getter
//I know this won't work, but to get the idea.
target[key] = get function() {
console.log(`Accessing property: ${key}`);
return eval(`this.${key}`)
}
}
Then, when I would create and object and try to access .color
const car = new Car();
car.color;
ideally I would see at the console
Accessing property: color
You can use Proxy in JavaScript. As MDN states, it allows you to create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties. Proxy objects are commonly used to log property accesses, validate, format, or sanitize inputs, and so on.
class Car {
color = 'red'
drive() {
return 'Driving'
}
}
const proxy = new Proxy(new Car(), {
get(target, key) {
console.log(`Accessing property: ${key}`);
return Reflect.get(target, key)
}
})
proxy.color // prints "Accessing property: color" and returns value of color.
In plain javascript you could use Object.defineProperty to dinamicaly add getters and setters to object.
That was my inital comment.
If you will to decorate only certain fields of the object then MDN example would be the easiest way to this properly:
const o = {a: 0};
Object.defineProperty(o, 'b', { get() { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)

How to write a mock object that will never throw "Cannot read property '**' of undefined'" in JavaScript

With the intention to write unit tests, I want to pass an object to my function that mocks any possible property - Note: not a function property.
When I have a function like this:
function someFunc (config){
var something = config.params.innerParams;
}
when called someFunc({}) it will throw Cannot read property 'innerParams' of undefined'.
If there are many recursive properties in config, mocking it may be very time consuming. Is there a way to write a "mock" object that I can pass to my function to mimic any structure? It may assign undefined to all properties at the time of access, like this:
var magic = new Magic(); //magical mock created
var a1 = magic.something; //undefined;
var a2 = magic.something.innerSomething; //undefined
var a3 = magic.something.innerSomething.farAwaySomething; //undefined
All I want is to avoid Cannot read property '*' of undefined' being thrown.
You can use ES6 Proxy to pass access to notexisting property of object.
I would've return the object itself in that case.
But I think, it would be difficult to archive equallity to undefined.
At least I don't know the way (but I have some thoughts).
And don't forget to check Compatibility table.
function Magic() {
var res = new Proxy(this, { get(target, key, receiver) { return res } });
return res;
}
Can't make it equal to undefined, but can to false (nonstrictly):
function Magic() {
var res = new Proxy(this, { get(target, key, receiver) { return key === Symbol.toPrimitive ? Reflect.get(target, key, receiver) : res } });
res[Symbol.toPrimitive] = () => false;
return res;
}
Tested in FF44.
If you have Proxy support (Firefox, IE11, and Edge for now), you can pass in
new Proxy({}, { get(a,b,p) { return p; } })
This is a Proxy object which allows access on properties of any name. When accessed, the value of every such property is the original proxy object itself, thereby allowing infinite chains of property access.
Property access does not yield undefined, but there is no way to do that while simultaneously allowing further property access.

Sails.js : Add custom instace method result as a model property

I'm relatively new to JavaScript programming, so this problem may have a trivial solution. Working with Sails.js, I've created this model.
module.exports = {
tableName: 'FOO_TABLE',
attributes: {
FOO: 'string',
BAR: 'number',
BAR2: function() {
return this.BAR + 1;
}
},
};
Then, in a controller I get all the instances:
FOO_MODEL.find().exec(function(err, FOOS) {
return res.view({data: JSON.stringify(FOOS)});
});
The problem is that inside FOOS, it's not the BAR2 method. I've come with this solution (using Underscore.js):
FOOS = _.map(FOOS, function(FOO){ FOO.BAR2 = FOO.BAR2(); return FOO; });
But I don't see it efficient / smart, as I think I will probably find this problem again. How would you do it? Thank you
If all you want is to set a calculated value for each new instance, you could set BAR2 to be of type number in the model (instead of a function), and add a beforeCreate class method like:
beforeCreate: function(values, cb) {
values.BAR2 = values.BAR + 1;
return cb();
}
If you want to keep BAR2 as an instance method, but have it serialized along with the object, you could override the default toJSON instance method:
toJSON: function() {
var obj = this.toObject();
obj.BAR2 = obj.BAR2();
return obj;
}
Any time an instance is stringified, its toJSON method will be called.
Check Your Query Result
If you're calling your instance-method during a request, namely, within a query-callback -- you may want to check what result you are getting passed in.
In my case, I am using the Sails/Waterline ORM Model-method, find instead of findOne (like a bonehead), which actually argues a collection (array). So I was attempting something along the lines of:
[ {...} ].hasItem(id)
... while what I needed was something like:
myQueryResults[0].hasItem(id)
Or rather, query correctly using findOne. Idiocy is apparently a poor practice, but it happens I guess.
Hope this help!

Memoize a prototype method

I'm trying to use memoization to speed up my Javascript, but I need it on a proto method, and I need said method to have access to the this object, and it's giving me fits. Here's what I have:
MyObj.prototype.myMethod = function(){
var self = this
, doGetData = (function() {
var memo = []
, data = function(obj) {
var result = memo;
if (obj.isDirty) {
obj.isDirty = false;
result = $.map(obj.details, function(elem, i) {
return elem.export();
});
memo = result;
}
return result;
}
return data;
}())
;
return doGetData(self);
};
I can get it to run, I just can't get it to memoize. I know something is wrong, but I can't figure out what. I know there are tons of examples of how to memoize, but none that I've come across deal with scope like this.
If you want the function on the prototype, but you want it to memoize for each instance independently, then "memo" needs to be an instance property (or a closure could be the instance property, I guess; whichever).
MyObj.prototype.myMethod = function() {
if (!("memo" in this) || this.isDirty) {
this.isDirty =false;
this.memo = $.map(obj.details, function(elem, i) {
return elem.export();
});
}
return this.memo;
};
I don't think you can do it without polluting the instances, though you could use newer JavaScript features to keep the "memo" thing from being enumerable. The prototype is shared by all instances, after all.
It is possible to use a Map object for storing and retrieving method caches for all instances. Maps support objects as keys. (Kind of off-topic, but strict mode affects the propagation of "this". Now this works without "var self = this;".)
"use strict";
MyObj.prototype.myMethod = function() {
var prototypeCache = new Map();
return function(arg) {
if (!prototypeCache.has(this)) prototypeCache.set(this, new Map());
var instanceCache = prototypeCache.get(this);
if (instanceCache.has(arg)) return instanceCache.get(arg);
else {
//Do expensive calculation that is stored in result...
instanceCache.set(arg, result);
return result;
}
};
}();
Now for simplicity I also used a Map for the instanceCache. For speed one would typically use just an object. It depends on the keys one can make. It is typically possible to find a way to store by unique strings. (Numbers can be converted, Objects can have id strings and state/version properties, multiple arguments can be concatenated into one string...)
Another solution entirely is to create the method in the constructor instead of having it in the prototype. But that is just as bad pollution as having method caches in instance properties. Much worse, I think.

How to reset na object property to the default one?

I am using jQuery and I am still pretty new to JavaScript. I am implementing an object as the following:
MyObject = {
properties : [{}],
resetProperties: function resetProperties() { this.properties = [{}] }
};
As you can see in the above code I can reset the properties by running MyObject.resetProperties() but, in order to do that, I state two times the [{}] variable. How should I accomplish the same thing without repeating that code?
Update
I tried to do the following:
MyObject = {
properties : this.propertiesDefault,
resetProperties : function resetProperties() { this.properties = [{}] },
propertiesDefault: [{}]
};
but I get "TypeError: invalid 'in' operand MyObject.properties" and I am not sure that is the right way to proceed.
It seems to me that it would be impossible to avoid having your default / reset properties as a separate object to the one that will be modified.
I would recommend having a default value, and cloning it in your initialisation and reset function. Since you tagged your question with jQuery, I assume you are happy to clone the object with that:
MyObject = {
defaultProperties : [{}],
properties : jQuery.extend(true, {}, this.defaultProperties),
resetProperties: function() {
this.properties = jQuery.extend(true, {}, this.defaultProperties);
}
};
See this Stack Overflow question for more information on cloning objects:
What is the most efficient way to deep clone an object in JavaScript?
This is the documentation for jQuery.extend:
http://docs.jquery.com/Utilities/jQuery.extend
From what I know this isn't possible. You're going to have to hard-code the property reset. I tried setting a variable cache outside the object, but when I reset the property it unfortunately maintains its value.
var obj = {
p: [ {} ],
r: function() { this.p = this.cache; }
};
obj.cache = obj.p; // attempt to set to original
obj.p[0].m = 5; // modify
obj.r(); // reset
--------
>>> obj.p[0].m; // 5
We can assume the the cache property is being modified in the same way as p is. Therefore, we can't reset like that.
Depends on what you want. Since you're new to javascript, you may be unfamiliar with using functions to create custom objects, which is the general javascript "OOP" kinda way to do it.
function MyObjectClass() {
this.properties = null;
this.resetProperties();
}
MyObjectClass.prototype.resetProperties = function () { this.properties = [{}] };
var MyObject= new MyObjectClass();
But we don't really know that function MyObject needs to fulfill. There may be a requirement that it NEEDs to be a plain old javascript object. Or maybe not, and you're done.
Of course, you can always directly:
MyObject = {
properties : null,
resetProperties: function () { this.properties = [{}];}
};
MyObject.resetProperties();

Categories

Resources