How do I make a JavaScript variable completely immutable? - javascript

I've heard similar questions, but not the answer that I wanted;
I do not count const because:
1).
it doesn't actually make it immutable, it only makes the reference immutable
2).
it messes with the scope, and I want it to work outside the block, too
3).
not all browsers support it yet
{
const hello = ["hello", "world"];
hello.push("!!!");
console.log(hello);//outputs "hello", "world", "!!!"
}
//and it doesn't, and shouldn't, work here
console.log(hello);

Just use Object.freeze
const immutableArray = Object.freeze([1,2,4])

You can use Object.freeze for this (obviously only on objects).
const hello = Object.freeze(["hello", "world"]);
// hello.push("!!!");
// will throw "TypeError: can't define array index property past the end of an array with non-writable length"
// hello.length = 0;
// will fail silently
// hello.reverse();
// will throw "TypeError: 0 is read-only"
// hello[0] = "peter";
// will fail silently
From MDN:
The Object.freeze() method freezes an object. A frozen object can no longer be changed; freezing an object prevents new properties from being added to it, existing properties from being removed, prevents changing the enumerability, configurability, or writability of existing properties, and prevents the values of existing properties from being changed. In addition, freezing an object also prevents its prototype from being changed. freeze() returns the same object that was passed in.
However, there is no keyword to define a completely immutable variable without using Object.freeze or Object.seal on the variable's value.
For a less restrictive approach Javascript also has Object.seal().

The way to do it without const is to use Object.defineProperty, and like I wanted, it behaves like var in terms of scope:
{
Object.defineProperty(typeof global === "object" ? global : window, "PI", {
value: Object.seal(3.141593),
enumerable: true,
writable: false,
configurable: false
});
}
console.log(PI); // 3.141593
The only problem is that it that it doesn't throw an error outside of strict mode.

Related

Is there a way of listening/proxying a variable in vanilla Javascript?

I'm building a web framework, something like React; one of the things which I would like to improve on React is state.
My idea is something like Svelte, to use state you just create a normal variable (in my case it would be okay to use a function when creating te state, but not when updating it), but how Svelte does this Magic is by compiling, and I would like it to work in vanilla Javascript.
From my understanding this is not exactly possible, but I've still been trying to hack something somehow.
So the part of this state system that is not possible is knowing when a primitive is set and got (setters & getters), I want it to work with scoped variables; so I can't use the Object.defineProperty on the window or globalThis. I've been hacking around for quite some time and here are the only solutions I thought have could worked:
Proxing a new String(string), has given weird error of this beeing of the wrong type, unknows values, and stuff.
Proxing the Funtion.arguments object, but this didn't work.
Using Symbol.toPrimitive, but I couldn't find a way of using it without a + or ${}.
But as you can see they all have problems, I'm stuck and can't find anything, is there any (even if hacky, though without legacy or deprecated code) way to do this? Thank you!
You can't do what you've described in JavaScript. You can't proxy a primitive, and you can't run code some other way (getter, setter) when a variable is read or set, only when a property of an object is read or set.
There is an awful thing you can do in loose mode that's disallowed (for good reasons) in strict mode where you have an object with getters and setters that you then put into the environment used for resolving freestanding identifiers using the with statement, but again, it's disallowed for good reasons in strict mode (which is the default for modules and other mechanisms that create new contexts, like the body of a class).
I hesitate to give an example of it, but for completeness:
// This only works in loose mode, not strict mode
let a = 0;
const obj = {
get a() {
console.log(`Getter called, returning a = ${a}`);
return a;
},
set a(value) {
console.log(`Setter called, setting a = ${value}`);
a = value;
}
};
with (obj) {
console.log(a);
a = 42;
console.log(a);
}
Re your updated question:
My idea is something like Svelte, to use state you just create a normal variable...but how Svelte does this this Magic is by compiling, and I would like it to work in vanilla Javascript.
I wouldn't try to do it with freestanding variables, have the user provide a state object and convert its data properties to getter/setter combinations (or replace it with a new version with getter/setter combinations, etc.):
// Userland code provides a state object
const state = {
a: 0,
b: "hi",
};
// Your framework code converts it to using getters/setters
function enhance(obj) {
const descrs = Object.getOwnPropertyDescriptors(obj);
for (const key of Object.keys(descrs)) {
const descr = descrs[key];
if (descr.configurable && "value" in descr && typeof descr.value !== "function") {
// A simple data property; wrap it in getter/setter
let value = descr.value;
if (typeof value === "object") {
enhance(value);
} else {
Object.defineProperty(obj, key, {
get() {
console.log(`Getter called, returning ${key} = ${value}`);
return value;
},
set(newValue) {
console.log(`Setter called, setting ${key} = ${newValue}`);
value = newValue;
},
enumerable: descr.enumerable,
configurable: true,
});
}
}
}
}
enhance(state);
// Their code using the properties triggers your getter/setters:
console.log(state.a, state.b);
state.a = 42;
state.b = state.b.toUpperCase();
console.log(state.a, state.b);

Confused about "hasOwnProperty" for detecting existence of functions

I'm sure this is a duplicate, but I couldn't find the right search terms to find an answer.
I'm trying to use hasOwnProperty() to determine if a function exists on an object or not. I know there are other ways to do this, but I want to understand why that method doesn't work the way I was expecting.
I typed this into a Chrome Dev Tools console:
window.hasOwnProperty("getSelection")
<- true
window.getSelection().hasOwnProperty("empty")
<- false
What I don't understand is why hasOwnProperty("empty") returns false, when that method does exist on the Selection object and I can call it.
window.getSelection().empty() // Returns no errors
getSelection returns a Selection object instance, which has an internal prototype of Selection.prototype. The prototype has the empty method on it; it's not on the instance itself:
const sel = window.getSelection();
console.log(
Object.getPrototypeOf(sel) === Selection.prototype,
Selection.prototype.hasOwnProperty("empty")
);
If you wanted to implement this sort of thing yourself:
class Foo {
method() {
console.log('method');
}
}
const f = new Foo();
f.method();
console.log(
f.hasOwnProperty('method'),
Foo.prototype.hasOwnProperty('method')
);
That's because it's not a property on that object, rather it's inherited. Inherited properties are not the object's own properties, as they come from the constructor or class. Far better is the in keyword:
console.log("getSelection" in window);
console.log("empty" in window.getSelection());

Is it safe to declare critical literals in "with (...) {...}" to sandbox code run within it?

I avoid using eval() or Functions created from a string. But when I need to run some subset of Javascript that can be entered by a user I'm tempted to use it just because it will save me much work writing a lexer/parser and interpreter.
Say I'd like to run this code:
a.toLowerCase() === 'xyz' || b == 1 || /pqr/.test(c)
the native approach would be to pass it into eval() like this:
with({a: ..., b: ..., c: ...}) {
ret = eval(code);
}
I cannot be sure that code always contains uncritical code like the above. This opens the possibilities to run malicious code.
I thought of passing an object re-defining critical Browser objects to with besides the actual data like:
var obj = {
// list incomplete ;)
console: true, XMLHttpRequest: true, document: true, window: true, addEventListener: true, removeEventListener: true, parent: true, top: true, history: true, ...,
// actual data
a: ..., b: ..., c: ...
};
with (obj) {
...
}
When running code within with access to the objects/methods is not possibe.
I know that it's still possible to indirectly access those methods if they are indirectly accessed though another object/function that is not re-defined. Let's assume I re-define these too.
Is it secure to sandbox code with a suffient list of objects and functions as content object?
What would be remaining the attack vectors in this case?
Edit 1:
The code should run within Firefox, Chrome, IE (10+), Opera, Safari.
No, this is not secure.
No matter what you do with your code's execution environment using with, it is still possible to retrieve the "real" global object using the following trick:
var window = (function(){ return this }).call(undefined);
This works because Function.call will use the global object as the value of this if it is explicitly passed as undefined or null.
If the shadowed variables were deleted ...
alert([1, window, document]);
var obj = {
document: true, window: true
};
with (obj) {
alert([2, window, document]);
delete window;
delete document;
alert([3, window, document]); //restored
}
Additionally if you exposed any DOM elements the document/window objects could be reached via ownerDocument/defaultView.
You should make use of a global (and "static") function to avoid giving access to other unwanted/private variables (i.e. a sub-function has access to all the variables of the parent function).
Second you want to remove a few keywords from the string to be evaluated to avoid problems as described by duskwuff and Alex K. Something like this:
function exec(e)
{
e = e.replace(/new/, "new_", "g")
.replace(/delete/, "delete_", "g")
.replace(/function/, "function_", "g")
.replace(/throw/, "throw_", "g")
.replace(/this/, "this_", "g")
.replace(/var/, "var_", "g")
.replace(/eval/, "eval_", "g");
obj = { ... };
with(obj)
{
eval(e);
}
}
Note that may not work in strict mode. As mentioned by Bergi in a comment, you could also protect the variables in obj and make them non-deletable so you cannot replace them.
The replace() can include many more things... you may want to look closer at what you are trying to achieve. If the evaluation string is expected to just be an expression, then all keywords should be removed (Except true and false and null). You may also want to remove a few other functions. Here I only removed eval.
If you'd like to only match words so the word anew does not match new you can use the \b flag in the regex. I do not know how compatible this flag is across browsers.
e.replace(/\bnew\b/, "new_", "g");
This would match new but not anew.

JavaScript object detection: dot syntax versus 'in' keyword

I have seen two ways of detecting whether a UA implements a specific JS property: if(object.property) and if('property' in object).
I would like to hear opinions on which is better, and most importantly, why. Is one unequivocally better than the other? Are there more than just these two ways to do object property detection? Please cover browser support, pitfalls, execution speed, and such like, rather than aesthetics.
Edit: Readers are encouraged to run the tests at jsperf.com/object-detection
if(object.property)
will fail in cases it is not set (which is what you want), and in cases it has been set to some falsey value, e.g. undefined, null, 0 etc (which is not what you want).
var object = {property: 0};
if(object.isNotSet) { ... } // will not run
if(object.property) { ... } // will not run
if('property' in object)
is slightly better, since it will actually return whether the object really has the property, not just by looking at its value.
var object = {property: 0};
if('property' in object) { ... } // will run
if('toString' in object) { ... } // will also run; from prototype
if(object.hasOwnProperty('property'))
is even better, since it will allow you to distinguish between instance properties and prototype properties.
var object = {property: 0};
if(object.hasOwnProperty('property')) { ... } // will run
if(object.hasOwnProperty('toString')) { ... } // will not run
I would say performance is not that big of an issue here, unless you're checking thousands of time a second but in that case you should consider another code structure. All of these functions/syntaxes are supported by recent browsers, hasOwnProperty has been around for a long time, too.
Edit: You can also make a general function to check for existence of a property by passing anything (even things that are not objects) as an object like this:
function has(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
Now this works:
has(window, 'setTimeout'); // true
even if window.hasOwnProperty === undefined (which is the case in IE version 8 or lower).
It really depends what you want to achieve. Are you talking about host objects (such as window and DOM nodes)? If so, the safest check is typeof, which works for all host objects I know of:
if (typeof object.property != "undefined") { ... }
Notes:
Avoid object.hasOwnProperty() for host objects, because host objects are not obliged to inherit from Object.prototype and therefore may not have a hasOwnProperty() method (and indeed in IE < 9, they generally do not).
A simple Boolean coercion (e.g. if (object.property) { ... }) is a poor test of the existence of a property, since it will give false negatives for falsy values. For example, for an empty textarea, if (textarea.selectionStart) { ... } will not execute the block even though the property exists. Also, some host object properties throw an error in older versions of IE when attempting to coerce to a Boolean (e.g. var xhr = new ActiveXObject("Microsoft.XMLHTTP"); if (xhr.responseXML) { ... }).
The in operator is a better test of the existence of a property, but there are once again no guarantees about support for it in host objects.
I recommend against considering performance for this kind of task. Choose the safest option for your project and only optimize later. There will almost certainly be much better candidates for optimization than property existence checks.
For more background on this, I recommend this excellent article by Peter Michaux.
Definitely if ('property' in object) is the right way to go. That actually tests if the property is in the object (or in its prototype chain, more on that below).
if (object.property) on the other hand, will coerce 'property' into a truth/flase value. If the property is unset, it will return "undefined", which will be coerced into false, and appear to work. But this will also fail for a number of other set values of properties. javascript is notoriously inconsistent in what it treats as truthy and falsy.
Finally, like I said above, 'property' in 'object' will return true if it's in anywhere in the prototype chain. If you want to test that's on the object itself, and not somewhere higher up in the chain, you use the hasOwnProperty method like so:
if (object.hasOwnProperty('property')) ...
The first one would fail if "property" is false of 0. To make sure that there actually exist a property you need to check that object.property !== undefined, or use the in-keyword.
[Edit]
There is also the hasOwnProperty-function, but I've never really used that one so I can't say much about it. Though I think it won't return true if the property is set in a prototype, which sometimes you want, other times you don't want.
This allows you to use window.hasOwnProperty as either referring to itself or something else, regardless of your scripting host.
// No enclosing functions here
if (!('hasOwnProperty' in this))
function hasOwnProperty(obj, prop) {
var method = Object.prototype.hasOwnProperty;
if (prop === undefined)
return method.call(this, obj);
return method.call(obj, prop);
}
//Example of use
var global = global || this; //environment-agnostic way to get the global object
var x = 'blah';
WScript.Echo(global.hasOwnProperty('x') ? 'true' : 'false'); //true
//Use as non-object method
var y = { z: false };
WScript.Echo(hasOwnProperty(y, 'z') ? 'true' : 'false'); //true
WScript.Echo(hasOwnProperty(y, 'w') ? 'true' : 'false'); //false
// true ಠ_ಠ
WScript.Echo(hasOwnProperty(global, 'hasOwnProperty') ? 'true' : 'false');

Hide method from enumeration

I want to add a method to every object.
When I just set Object.prototype.somefunction = ..., it will come up when somebody enumerates the object using the usual pattern:
for (i in o){
if (o.hasOwnProperty(i)){
// do something with object
}
}
I tried to set it higher up the prototype chain, but that is not possible (at least not possible in Chrome):
TypeError: Cannot set property 'blah' of undefined
Can I set some flag or something on the method so that it won't get enumerated, just like the native methods won't? (Or at least make hasOwnProperty() return false.)
Update: Sorry, I didn't look at it properly. I am using the ExtJS Framework and the object I was looking at had been processed by Ext.apply() which does the following:
for(var p in c){
o[p] = c[p];
}
That's where the "own property" flag gets lost.
So I guess I have no chance (in ECMAScript < 5) to inject a method into all objects that behaves like a native one?
I'm not sure I understand correctly. hasOwnProperty is needed exactly for this case, and enumerating an object via
for (i in o){
if (o.hasOwnProperty(i)){
// do something with object
}
}
should not include methods from Object.prototype. Can you please make a working example where you see this behaviour?
I also do not understand what you mean by
I tried to set it higher up the
prototype chain
as Object.prototype is the root of the chain, so you cannot get any higher.
In short, the solution is doing exactly what you claim you have done. If this does not work, probably you have made a mistake or found a bug.
I'm not sure what you mean. If a method/property is attached to the prototype, hasOwnProperty will return false. See this code:
function Con(){this.enumerableProp = true;};
Con.prototype.fun = function(){return 'that\'s funny'};
var fun = new Con;
alert(fun.hasOwnProperty('fun')); //=> false
alert(fun.hasOwnProperty('enumerableProp')); //=> true
So, what do you mean?
Make a base class and make all other classes extend it. Add the method to the base class.
ES5 has Object.getOwnPropertyNames() for this:
Object.prototype.lolwat = 42;
var obj = {
'foo': 1,
'bar': 2
};
Object.getOwnPropertyNames(obj); // ['bar', 'foo']
To see where it is supported: http://kangax.github.com/es5-compat-table/
However, for-in combined with a hasOwnProperty check should work too.
You get that error because there is nothing higher up the prototype chain.
Of note also is that adding to Object's prototype is not really recommended unless absolutely necessary for some reason
Edit: actually, my original answer was incorrect - as the others have pointed out, your object should not have that as own property if it's in Object's prototype.
In any case, if you want to create a prototype chain (or more importantly, avoid changing Object's prototype), you'll want to create your own class:
function MyBaseClass(){
}
MyBaseClass.prototype = new Object();
MyBaseClass.prototype.someMethod = function() { /* your method */ };
var o = new MyBaseClass();
o.hasOwnProperty('someMethod') //should be false

Categories