JavaScript removing with() expressions
-
I decided it might be fun to take an old Firefox addon, and maintain and modernize it for Pale Moon. I want to be able to turn on strict mode, but the original author liked to use
with(x)
to create namespaces, andwith()
isn't allowed in strict mode.I am unsure how to translate that into a different way of doing it, and so far the things I've read haven't really helped.
Here is the object where the the global namespace is created. I'm afraid I don't know enough to know if there is anything bad about it, or how to change it.
// Global object for NoSquint. 'NoSquint' is the only name added to the global // namespace by this addon. var NoSquint = { id: 'NoSquint', namespaces: [], _initialized: false, dialogs: {}, // dialogs namespace ns: function(fn) { var scope = { extend: function(o) { for (var key in o) this[key] = o[key]; } }; scope = fn.apply(scope) || scope; NoSquint.namespaces.push(scope); return scope; }, /* This function is the load handler. It calls init() on all namespaces * previously registered with ns(), which happens for most .js files that * are loaded via the overlay. * * Consequently, init() for each namespace should be kept light so as not * to adversely affect load times. * * Currently initialization takes about 5-10ms with ff4 on my fairly peppy * Thinkpad (i7 M 620 2.67GHz), which isn't horrible, but there's room for * improvement. */ init: function() { if (NoSquint._initialized) return; NoSquint._initialized = true; //var t0 = new Date().getTime(); for (let i = 0; i < NoSquint.namespaces.length; i++) { //var t1 = new Date().getTime(); var scope = NoSquint.namespaces[i]; if (scope.init !== undefined) scope.init(); //dump(scope.id + " init took " + (new Date().getTime() - t1) + "\n"); } //dump("Total init took: " + (new Date().getTime() - t0) + "\n"); }, destroy: function() { // Invoke destroy functions in all registered namespaces for (let i = 0; i < NoSquint.namespaces.length; i++) { var scope = NoSquint.namespaces[i]; if (scope.destroy !== undefined) scope.destroy(); } } }; window.addEventListener("load", NoSquint.init, false); window.addEventListener("unload", NoSquint.destroy, false);
Other files mostly follow the pattern of:
NoSquint.interfaces = NoSquint.ns(function() { with (NoSquint) { // Implementation here }
Does anyone have any tips I could follow?
-
Assuming that I've understood your question, the starting point could just be to get rid of the
with
statements and to write out the references to anything from NoSquint in full, i.e.NoSquint.interfaces = NoSquint.ns(function() { with (NoSquint) { // doing something with NoSquint, e.g. dialogs.foo = "bar"; } });
becomes
NoSquint.interfaces = NoSquint.ns(function() { // doing something with NoSquint, e.g. NoSquint.dialogs.foo = "bar"; });
That won't significantly improve the code quality (which from the fragment you've provided looks suspiciously cargo-cultish) but could at least get the thing running in strict mode, after which you could begin more drastic refactoring.
with
doesn't do anything particularly clever, it just adds an extra scope for variable look-up. It doesn't create a closure or grant any additional access to an object that wouldn't otherwise be accessible, it's really just a way of saving some typing (adding some nasty ambiguity along the way).
-
When you refer to a name in Javascript, it searches the scope chain for that name. All that
with()
does is add an object at the top of the scope chain, so that its properties can be accessed without repeating the object name.E.g.
with (Math) { console.log(cos(PI)); }
is exactly the same as:
console.log(Math.cos(Math.PI));
The
with(Math)
makes it so that any name you use will automatically referenceMath
, if such a property exists in it. (Otherwise, the Javascript engine will continue searching the scope chain to find it.)Note that the
with
object is the first place that Javascript will search, so this:var PI = 3; with (Math) console.log(PI);
will log the value of
Math.PI
(because it exists), not the value assigned to the variablePI
in the line directly above. This, on the other hand:var PIE = 3; with (Math) console.log(PIE)
logs the value assigned to the variable
PIE
,3
. TheMath
object does not have a property namedPIE
, so the scope chain is searched further and the variablePIE
is found.So you can see the advantage and disadvantage of
with
: on the one hand, your code becomes more succinct, because you don't have to continue repeating the same object name over and over; but on the other hand, it causes the Javascript engine to have to do more work because it has to check in an extra place to find any name that you use, and if misused, it can easily lead to ambiguity that makes the code difficult to read and debug.As far as removing the
with()
expressions, if the code is not overly complex then it is fairly straightforward: simply find every use of a property of thewith
object, and add the object's name before it explicitly. As long as you know what properties the object has, this is not too difficult.
-
@anotherusername @japonicus Now that makes a lot more sense. Why couldn't anyone else on the internet have explained it like that...
I think part of my problem is I come from Java/Kotlin, so it slightly baffles me how JS knows where things are without some sort of import statement. Heh.
-
with
can get really ugly sometimes, which is why it's considered bad form. For example, suppose you're lazy and you wrote this:with (Math) with (console) { // now I can access properties of both console and Math objects directly! log(cos(PI)); }
is the same as
console.log(Math.cos(Math.PI))
, but in the reverse order:with (console) with (Math) { // now I can access properties of both console and Math objects directly! log(cos(PI)); }
since
Math
was most recently added to the top of the scope chain, and sinceMath.log
exists,log
will actually refer to that.Also you've got really bizarre edge case stuff, like this:
var NS = {a: 3}; with (NS) { var a = 5, b = 7; console.log(a, b); // 5 7 } console.log(a, b); // undefined 7 console.log(NS); // Object { a: 5 }
(the variable
b
is created as you expect, but sinceNS.a
exists,var a
actually doesn't create a new variable; instead, it just updates the value ofNS.a
.)or this:
with (Math) { var PI = 3; console.log(PI); // 3.141592653589793 } console.log(Math.PI); // 3.141592653589793 console.log(PI); // 3
(because
Math.PI
is not a writable property, it does create a new variable calledPI
-- which cannot be accessed inside thewith
scope, becauseMath.PI
exists!)
-
@anotherusername said in JavaScript removing with() expressions:
var a
actually doesn't create a new variableI thought the point of
var
declarations was to explicitly create a new variable, as opposed to assigning without a keyword.JavaScript is dumb.
-
@pie_flavor said in JavaScript removing with() expressions:
@anotherusername said in JavaScript removing with() expressions:
var a
actually doesn't create a new variableI thought the point of
var
declarations was to explicitly create a new variable, as opposed to assigning without a keyword.JavaScript is dumb.
var
declarations in JavaScript are scope-level, so they work like normal assignment if the variable is already declared in the scope.You can use variables before the
var
statement as well.
-
@pie_flavor said in JavaScript removing with() expressions:
@anotherusername said in JavaScript removing with() expressions:
var a
actually doesn't create a new variableI thought the point of
var
declarations was to explicitly create a new variable, as opposed to assigning without a keyword.Ah, but as far as Javascript is concerned,This is wrong; actually,a
already exists in the scope of the command -- sovar a
does not actually createa
; it simply reassigns it.var a = 5
does createa
, but then it doesn't actually assign the same variablea
that it created! Javascript's hoisting can be weird.Remember, you can use
var a
as many times as you like, and it'll only ever create the variable once (per scope) -- and only if it doesn't exist. This is even true in strict mode. This is perfectly fine:(function () { 'use strict'; var a = 5; console.log(a); // 5 var a = 7; console.log(a); // 7 })();
Due to the way that
var
is hoisted, that's essentially:(function () { 'use strict'; var a; a = 5; console.log(a); a = 7; console.log(a); })();
In fact, you could even write
(function (a) { 'use strict'; var a = 5; console.log(a); var a = 7; console.log(a); })();
...since
a
already exists, it is not created; instead, the originala
variable is overwritten.
-
@anotherusername ... then that means that
with
doesn't create a scope, which is similarly stupid.
-
@pie_flavor said in JavaScript removing with() expressions:
@anotherusername ... then that means that
with
doesn't create a scope, which is similarly stupid.In JavaScript, only functions create scopes.
-
@pie_flavor no,
with
doesn't create a scope. That's why the variableb
still existed outside of thewith
in which it was created, in that same example in my post....holy fuck, I just realized something.
var a
does createa
, but then it assignsNS.a
!So this:
(function () { var NS = {a: 3}; with (NS) { var a = 5, b = 7; console.log(a, b); } console.log(a, b); console.log(NS); })();
doesn't crash, but this does:
(function () { var NS = {a: 3}; with (NS) { let a = 5, b = 7; console.log(a, b); } console.log(a, b); console.log(NS); })();
The first is equivalent to:
(function () { var NS, a, b; NS = {a: 3}; with (NS) { a = 5, b = 7; console.log(a, b); } console.log(a, b); console.log(NS); })();
while the second is equivalent to:
(function () { var NS; NS = {a: 3}; with (NS) { let a = 5, b = 7; console.log(a, b); } console.log(a, b); // ReferenceError: a is not defined console.log(NS); })();
-
@ben_lubar said in JavaScript removing with() expressions:
var
declarations in JavaScript are scope-level, so they work like normal assignment if the variable is already declared in the scope.You can use variables before the
var
statement as well.Var declarations are "hoisted" (brought to the top of the closest function body, or the whole file if outside a function), much like function declarations.
The alternative is
let
, which also declares variables without hoisting.
-
@anotherusername any construct with braces should create a new scope, always.
-
In case anyone was curious, this is the repo: