How to avoid accidentally implicitly referring to properties on the global object? - javascript

Is it possible to execute a block of code without the implicit with(global) context that all scripts seem to have by default? For example, in a browser, would there be any way to set up a script so that a line such as
const foo = location;
throws
Uncaught ReferenceError: location is not defined
instead of accessing window.location, when location has not been declared first? Lacking that, is there a way that such an implicit reference could result in a warning of some sort? It can be a source of bugs when writing code (see below), so having a way to guard against it could be useful.
(Of course, due to ordinary scoping rules, it's possible to declare another variable with the same name using const or let, or within an inner block, to ensure that using that variable name references the new variable rather than the global property, but that's not the same thing.)
This may be similar to asking whether it's possible to stop referencing a property from within an actual with statement:
const obj = { prop: 'prop' };
with (obj) {
// how to make referencing "prop" from somewhere within this block throw a ReferenceError
}
It's known that with should not be used in the first place, but unfortunately it seems we have no choice when it comes to the with(global), which occasionally saves a few characters at the expense of confusing bugs which pop up somewhat frequently: 1 2 3 4 5 6. For example:
var status = false;
if (status) {
console.log('status is actually truthy!');
}
(the issue here: window.status is a reserved property - when assigned to, it coerces the assigned expression to a string)
These sorts of bugs are the same reason that explicit use of with is discouraged or prohibited, yet the implicit with(global) continues to cause issues, even in strict mode, so figuring out a way around it would be useful.

There are some things you need to consider before trying to answer this question.
For example, take the Object constructor. It is a "Standard built-in object".
window.status is part of the Window interface.
Obviously, you don't want status to refer to window.status, but do you want Object to refer to window.Object?
The solution to your problem of it not being able to be redefined is to use a IIFE, or a module, which should be what you are doing anyways.
(() => {
var status = false;
if (!status) {
console.log('status is now false.');
}
})();
And to prevent accidentally using global variables, I would just set up your linter to warn against it. Forcing it using a solution like with (fake_global) would not only have errors exclusively at run time, which might be not caught, but also be slower.
Specifically with ESLint, I can't seem to find a "good" solution. Enabling browser globals allows implicit reads.
I would suggest no-implicit-globals (As you shouldn't be polluting the global scope anyways, and it prevents the var status not defining anything problem), and also not enabling all browser globals, only, say, window, document, console, setInterval, etc., like you said in the comments.
Look at the ESLint environments to see which ones you would like to enable. By default, things like Object and Array are in the global scope, but things like those listed above and atob are not.
To see the exact list of globals, they are defined by this file in ESLint and the globals NPM package. I would would pick from (a combination of) "es6", "worker" or "shared-node-browser".
The eslintrc file would have:
{
"rules": {
"no-implicit-globals": "error"
},
"globals": {
"window": "readonly",
"document": "readonly"
},
"env": {
"browser": false,
"es6": [true/false],
"worker": [true/false],
"shared-node-browser": [true/false]
}
}

If you're not in strict mode, one possibility is to iterate over the property names of the global (or withed) object, and create another object from those properties, whose setters and getters all throw ReferenceErrors, and then nest your code in another with over that object. See comments in the code below.
This isn't a nice solution, but it's the only one I can think of:
const makeObjWhosePropsThrow = inputObj => Object.getOwnPropertyNames(inputObj)
.reduce((a, propName) => {
const doThrow = () => { throw new ReferenceError(propName + ' is not defined!'); };
Object.defineProperty(a, propName, { get: doThrow, set: doThrow });
return a;
}, {});
// (using setTimeout so that console shows both this and the next error)
setTimeout(() => {
const windowWhichThrows = makeObjWhosePropsThrow(window);
with (windowWhichThrows) {
/* Use an IIFE
* so that variables with the same name declared with "var" inside
* create a locally scoped variable
* rather than try to reference the property, which would throw
*/
(() => {
// Declaring any variable name will not throw:
var alert = true; // window.alert
const open = true; // window.open
// Referencing a property name without declaring it first will throw:
const foo = location;
})();
}
});
const obj = { prop1: 'prop1' };
with (obj) {
const inner = makeObjWhosePropsThrow(obj);
with (inner) {
// Referencing a property name without declaring it first will throw:
console.log(prop1);
}
}
.as-console-wrapper {
max-height: 100% !important;
}
Caveats:
This explicitly uses with, which is forbidden in strict mode
This doesn't exactly escape the implicit with(global) scope, or the with(obj) scope: variables in the outer scope with the same name as a property will not be referenceable.
window has a property window, which refers to window. window.window === window. So, referencing window inside the with will throw. Either explicitly exclude the window property, or save another reference to window first.

Somewhat simpler to implement than #CertainPerformance's answer, you can use a Proxy to catch implicit access to everything except window. The only caveat is you can't run this in strict mode:
const strictWindow = Object.create(
new Proxy(window, {
get (target, property) {
if (typeof property !== 'string') return undefined
console.log(`implicit access to ${property}`)
throw new ReferenceError(`${property} is not defined`)
}
}),
Object.getOwnPropertyDescriptors({ window })
)
with (strictWindow) {
try {
const foo = location
} catch (error) {
window.console.log(error.toString())
}
// doesn't throw error
const foo = window.location
}
Notice that even console has to have an explicit reference in order to not throw. If you want to add that as another exception, just modify strictWindow with another own property using
Object.getOwnPropertyDescriptors({ window, console })
In fact, there are a lot of standard built-in objects you may want to add exceptions for, but that is beyond the scope of this answer (no pun intended).
In my opinion, the benefits this offers fall short of the benefits of running in strict mode. A much better solution is to use a properly configured linter that catches implicit references during development rather than at runtime in non-strict mode.

Perhaps slightly cleaner (YMMV) is to set getter traps (like you did), but in a worker so that you don't pollute your main global scope. I didn't need to use with though, so perhaps that is an improvement.
Worker "Thread"
//worker; foo.js
addEventListener('message', function ({ data }) {
try {
eval(`
for (k in self) {
Object.defineProperty(self, k, {
get: function () {
throw new ReferenceError(':(');
}
});
}
// code to execute
${data}
`);
postMessage('no error thrown ');
} catch (e) {
postMessage(`error thrown: ${e.message}`);
}
});
Main "Thread"
var w = new Worker('./foo.js');
w.addEventListener('message', ({data}) => console.log(`response: ${data}`));
w.postMessage('const foo = location');
Another option that may be worth exploring is Puppeteer.

Just use "use strict". More on Strict Mode.

Related

Can you trap/identify if an object is being "invoked" as a function in runtime?

Given some basic class, such as a logical predicate class here:
const isACat = new Predicate(obj => obj.cat === true);
Is there a way to determine/trap/identify (perhaps via Reflection?) the context under which isACat is being "invoked/evaluated"? By "invoke/evaluate"--because I can't really think of a better word to use right now--I mean something like this:
console.log(isACat(object1)); // being "invoked/evaluated" as a function
console.log(isACat); // being "invoked/evaluated" as a non-function
I specifically mean this in the "runtime" sense, not in the typeof/instanceof sense.
For the ultimate goal of performing contingent behavior, such as (perhaps via a Proxy) returning a default function if that instance is being "invoked/evaluated" as a function.
Edit: Maybe in more precise terms, is there such a thing as a "default getter" when no further child prop is passed (i.e. isACat, but not isACat[ prop ])?
I am not seriously suggesting that you do any of the things presented bellow and you will spot their limitation immediately but I thought it was kind of fun to demonstrate.
[{dog: true}, {cat: true}].filter(isACat);// (referenced(), [{cat: true}])
isACat({dog: true}); // (referenced(), false)
let lives = 0;
lives += isACat; // (referenced(), 7)
`Felix ${isACat}` // (referenced(), "Felix is a cat")
The above requires the following, which you could probably generate with a Babel plugin or something (I mean: don't, obviously)
const referenced = (obj) => () => {
console.log(obj, 'was referenced');
return obj;
}
const _isACat = obj => obj.cat === true;
Object.defineProperty(_isACat, 'toString', {
value: () => 'is a cat'
});
Object.defineProperty(_isACat, 'valueOf', {
value: () => 7
});
Object.defineProperty(window, 'isACat', {
get: referenced(_isACat)
});
I don't know what I like the most about it: deceiving expectations thanks to getters, magical type coercion, or local variables leaking to the global scope. It is pure poetry.
More seriously, I don't think Javascript is the language for this but if for some reason you need meta-programming power, maybe give Clojure a go. You can also use macros with ClojureScript, which compiles to Javascript and has Javascript interop, but there is a runtime/compile time distinction which will limit what you can do.
No, there is no such thing. You're just accessing a variable, or whatever the reference to your object is stored in, and you get back a reference value. The object itself has no say about this process - it isn't even looked at yet. This is not "invocation" or "evaluation".
Really, if you need something like this, make the invocation explicit, and write isACat() (vs isACat(object) vs isACat.prop()).

JavaScript inner lambda function without var, let or const

In the following React-Application, I am using a JS-Feature to declare a function within a function and call it on a certain event:
export default function App() {
OnClick = (input) => { //Why is no var/let needed
//do something...
}
return (<button onClick={this.OnClick}/>); //and it is called like this
}
My resulting questions are:
How is the "feature"/"notation" called? - it seems weird not to use var/let/const
In which ES-Version was it introduced?
Does it only work with Lambda-Functions?
How is the "feature" called? - it seems weard not to use var/let/const
I call it The Horror of Implciit Globals. OnClick gets created as a global variable, not a local one. I don't think it has an official name. A less opinionated one might be "implicit global variable creation." :-)
In wich ES-Version was it introduced?
Before ECMAScript version 1. I think it was in the very first version of JavaScript ever in 1995.
Does it only work with Lambda-Functions?
No, you can do this with any value. OnClick = 42 would also "work."
But don't. Again, it creates a global, not a local. Instead, declare your variables.
Use const for your example (or let if you prefer). I also recommend using JavaScript modules ("ESM" or "ECMAScript" modules), because code in modules is in strict mode and assigning to an undeclared identifier is the error it always should have been in strict mode. If you don't want to use JavaScript modules, add "use strict"; to the top of your files to put them in strict mode.
Here's a demonstration of the fact it creates a global:
function example(value) {
OnClick = () => {
console.log(value);
};
}
example(42);
OnClick(); // Logs 42
console.log(typeof window.OnClick); // "function"

Using IIFEs in JS for protecting data by creating private variables

I have created a small program to print some message in the console when a button in the DOM is clicked.
Code-
<!DOCTYPE html>
<html>
<body>
<button onClick="printName()">Click</button>
</body>
<script>
const personDetails = (function() {
let name = 'john';
function displayMsg() {
console.log(name);
}
return {
displayName: displayMsg
}
})();
function printName() {
personDetails.displayName();
}
</script>
</html>
In above code, to protect name, I am using IIFE and returning a method displayName inside personDetails. On click of button, printName is executed which further executes personDetails.displayName. Here are my concerns -
Is this the right way to use IIFEs to protect the data? If no, then please provide a solution. This is getting confusing everyday.
Even if we are keeping only 1 global variables, other exposed
variables are accessible through the global one like displayName
method.
Let's say I have successfully protected the data, still I am able to
change the function definition in the console through
personDetails.displayName which should be avoided.
Old method:
function displayMsg() { console.log(name); }
New method:
personDetails.displayName = function() { console.log('Hiiii'); }
Welcome to StackOverflow. Please take the tour and visit the help center.
1. Is this the right way?
There is at least one 'close' vote for this question being primarily opinion-based. That voter is correct that this portion of the question is hard to answer definitively.
So let me just say that this is at least one appropriate way to do this. There are many others. You could simplify this, if you like, to
const personDetails = (function() {
let name = 'john';
return {
displayName: function() {
console.log(name);
}
}
})()
And you could simplify it still more by using arrow functions.
2. Why are other variables still exposed?
That is the point of such code: to create a public interface that makes reference to some private data. You could also skip personDetails and expose only displayName by returning the function and not an object with that function as a property.
But if you want to be able to use that data, then you need at least some public interface!
3. How is it that this function reference can still be changed?
There are techniques that would allow you to protect this variable from change. But then, perhaps, a user will redefine personDetails to be something else. Could you protect this? Maybe. But then what's to stop someone from intercepting your Javascript before it's interpreted by the browser, altering these definitions?
The point is that you will only ever have so much control. If you don't trust users, you shouldn't be shipping them code... which means you shouldn't be writing for the web.
Using IIFEs to protect data from accidental changes, or even well-meaning but dangerous intentional changes, is fine. Trying to pretend that you're entirely in charge of the code once it leaves your hands is folly however.
Yes
Yes, but there is only one global variable. This massively reduces the risk of other code accidentally overwriting it.
Yes. You can't stop deliberate modification to code once it has left your control and arrived at the client.
Minimising use of globals is a strategy to reduce the likelihood of bugs caused by other code. It isn't a form of DRM.
Let's say I have successfully protected the data, still I am able to
change the function definition in the console through
personDetails.displayName which should be avoided.
This can be avoided by changing the value of the writable property of the returned object.
Modify the function assignment of personDetails as follows:
"use strict";
const personDetails = (function() {
let name = 'john';
function displayMsg() {
console.log(name);
}
var obj = {};
Object.defineProperty(obj, 'displayName', {
value: displayMsg,
writable: false
});
return obj;
})();
Now, if anyone tries to overwrite the property value,
function printName() {
personDetails.displayName = 10;
}
printName();
you get an error like this:
Uncaught TypeError: Cannot assign to read only property 'displayName' of object '#<Object>'
at printName (<anonymous>:17:35)
Check out this writeup for more details.
Note: This will throw an error only in strict mode and silently reject any change otherwise.

Howto: reference a library in JSLint

I'm using JSLint to do code review/cleanup on a massive series of scripts that interact with an external ERP system.
The problem I am having is that JSLint complains that the methods in the API are used before they are defined.
How can I reference a library within JSLint so that proper lookups are done? I have 493 functions I need to reference so /*Global*/ is not an option.
If this is not possible, is there a better way to do this?
I referenced this post which is asking for something similar, however the answer requires a restructure of the API which is not possible for me to do.
What's the library? Usually, you hope a library has everything namespaced properly so it's not an issue, as in /*global $*/ for jQuery. If the lib is internal and you can edit it, consider doing so (if you want to stick with strict JSLint compliance).
So...
function myFunc1(param1) {
return param1 + "some string1";
}
function myFunc2(param2) {
return param2 + "some string2";
}
becomes...
var fauxNamespace = {
myFunc1: function (param1) {
return param1 + "some string1";
},
myFunc2: function (param2) {
return param2 + "some string2";
}
};
Then you can /*global fauxNamespace*/ and profit. I realize that's non-trivial, but it's The Right Thing to Do (c) Crockford 2008.
Otherwise you're back to the normal "Use JSHint. It's happier because more options," or "JSLint has very clean code. You can actually hack it up yourself to ignore that error if you want," sorts of answers.
What you're looking for is currently on around line 2459:
// If the master is not in scope, then we may have an undeclared variable.
// Check the predefined list. If it was predefined, create the global
// variable.
if (!master) {
writeable = predefined[name];
if (typeof writeable === 'boolean') {
global_scope[name] = master = {
dead: false,
function: global_funct,
kind: 'var',
string: name,
writeable: writeable
};
// But if the variable is not in scope, and is not predefined, and if we are not
// in the global scope, then we have an undefined variable error.
} else {
token.warn('used_before_a');
}
} else {
this.master = master;
}
Is the library public?

How can I log information about global variables whenever these are created?

Background
I just learned that calling keys(window) (or Object.keys(window)) in the DevTools console reveals variables in the global scope (source). I called that code on the StackOverflow page and got this result:
Variable i got my attention as it seems to be in the global scope by a mistake. I tried locating code that is responsible for declaring i, but it turned out to be cumbersome (there is a lot of code and a lot of is).
Question
Getting console warnings that say
Global variable "i" created (main.js:342)
could be useful. How can I implement that feature?
Research
I figured that I need some kind of an event whenever new variable is created.
We do have setters in JavaScript. However, creating a setter requires that you provide a property name. Since I want to monitor all properties I can't really use that.
__noSuchMethod__ (MDN) would be perfect but it only covers methods (and there is no __noSuchProperty__ method).
Object.observe (HTML5 Rocks) doesn't reveal anything about the code that created the property (console.trace() gives me only the name of the observer function).
Object.prototype.watch (MDN) - same as the setter, you have to specify a property name.
Calling Object.preventExtensions(window) (MDN) causes errors with a nice stack trace whenever new global variable is created. The problem with this solution is that it interferes with the script execution and may change its behaviour. It also doesn't allow me to catch the error and format it properly.
Notes
I know about jshint/jslint and I still think that catching these declarations in the runtime could be useful.
I don't care about i variable on the SO page that much, you can probably find the declaration using setters. My question concerns the general solution for this problem.
IMO you have two decent options:
JSHint.
Strict mode.
Both will yell at you when you leak a global. Strict mode will probably be better for your usecases.
You've definitely done your homework, and you thought of all the things I would have thought of and, as you discovered, none of them fit.
The only way I can think of doing is to just monitor the global object (this example is using window as the global object: modify accordingly for Node or another JavaScript container). Here's an example that monitors new globals and deleted globals (if you don't need to monitor deletions, you can remove that functionality):
var globals = Object.keys(window);
var monitorGlobalInterval = 50;
setInterval(function(){
var globalsNow = Object.keys(window);
var newGlobals = globalsNow.filter(function(key){
return globals.indexOf(key)===-1;
});
var deletedGlobals = globals.filter(function(key){
return globalsNow.indexOf(key)===-1;
});
newGlobals.forEach(function(key){
console.log('new global: ' + key);
});
deletedGlobals.forEach(function(key){
console.log('global deleted: ' + key);
});
globals = globalsNow;
}, monitorGlobalInterval);
See it in action here: http://jsfiddle.net/dRjP9/2/
You can try this method to get a list of global variables that you've created:
(function(){
var differences = {},
ignoreList = (prompt('Ignore filter (comma sep)?', 'jQuery, Backbone, _, $').split(/,\s?/) || []),
iframe = document.createElement('iframe'),
count = 0; ignoreList.push('prop');
for (prop in window) {
differences[prop] = {
type: typeof window[prop],
val: window[prop]
}; count++;
}
iframe.src = 'about:blank';
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe = iframe.contentWindow || iframe.contentDocument;
for (prop in differences) {
if (prop in iframe || ignoreList.indexOf(prop) >= 0) {
delete differences[prop];
count--;
}
}
console.info('Total globals: %d', count);
return differences;
})();

Categories

Resources