var foo = "bar"; // LHS foo, RHS "bar", foo global scope
function bar() { // remember bar is a declaration in global scope
var foo = "baz"; // LHS foo, RHS "baz", foo local to bar()
function baz(foo) { // remember `foo` is declared here as local to baz
foo = "bam";
bam = "yay"; // remember this is a global scope `bam`
}
baz(); // this is a RHS because it's not an LHS
}
bar(); // this execures line 6, ignored during compilation.
foo; // RHS foo, foo in global scope
bam; // RHS bam, bam = "yay"
baz(); // RHS baz, baz is undeclared -- RHS doesn't work the same way as LHS (reference error)
Note: in strict mode, both LHS and RHS return reference errors with undeclared symbols Note: in non-strict mode, only RHS returns reference errors -- undeclared LHS gets declared automatically in global scope
Does the statement start with a function
keyword? If so, it's a function declaration. If not, it's an expression.
Function expressions should always have a name.
// this is a "named" function expression
var foo = function bar () { // this is an expression -- `bar` does not get declared in the outer scope, but within its own scope
var foo = "baz";
function baz(foo) {
foo = bar; // this is the enclosing `bar` function, because we have a "named" function expression
foo; // function...
}
baz();
};
foo();
bar(); // error -- bar is not declared in the global scope
my question What about ECMAScript 2015 classes?
Functions are not exactly the smallest atomic unit in JS. Try/Catch has "block scope"
var foo;
try {
foo.length;
}
catch (err) {
console.log(err); // TypeError
}
console.log(err); // reference error
Note: Linters complain about re-declaring variables between block-scopes, even though each var is in a different scope.
Lexical Scope model -- we've been discussing, it's in most languages Dynamic Scope -- alternative, not present in JS. Bash uses this, Perl has an opt-in for this.
"Lex" in Lexical refers to "Lexing" or parsing during compiliation. Lexical is compile-time scope. Compiler decides what your scope is during compiler time, scope doesn't get decided after that.
What would Dynamic scope look like if we had it in JavaScript? (This is useful when talking about ... this
)
Lexical, for example, is like looking for something on a floor of a building.
If you don't find it, go up a floor.
When you get to the top, you're out of floors.
References are cached during compile time.
var bar = "bar";
function foo(str) {
eval(str); // cheating. We modify our scope at run-time.
console.log(bar); // 42, not "bar"
}
foo("var bar = 42;");
When we do this, it slows down our code. The compiler cannot do the optimizations.
Note: in "strict" mode, eval
gets its own scope and the code can be better optimized.
If you have to ask, do not use eval.
Note: setTimeout also uses eval
when using the string syntax.
var obj = {
a: 2,
b: 3,
c: 4
};
// without the `with` keyword
obj.a = obj.b + obj.c;
obj.c = obj.b - obj.a;
// with the `with` keyword
with (obj) {
a = b + c; // `a` is defined in the obj
c = b - a; // `c` is defined in the obj
d = 3; // ?? d is not defined in the obj, gets created in global scope
}
with
keyword creates a whole new lexical scope at run-time
Note: in "strict" mode, with
is completely disabled.
Immediately Invoked Function Expression Prevents things from getting into the global namespace, avoids collisions
var foo = "foo";
(function($) { // This is still an expression, not a declaration -- we could have given it a name
// we've ensured that $ effectively points to jQuery, avoiding collisions with other libraries
var foo = "foo2"; // attached to local function scope, doesn't pollute outer scope.
console.log(foo); // "foo2"
})(jQuery); // this immediately executes our expression
console.log(foo);
Runtime says, "I have a function expression
// There are two different stylistic choices. Functionally the same.
(function(){
})(); // this is one choice, Douglas Croffard calls this "Donkey Balls"
(function(){
}()); // this is the alternative syntax
ES6+ has "block scope" with "let" -- let
will hijack the scope of whatever block we happen to be in.
function foo() {
var bar = "bar";
for (let i=0; i<bar.length; i++) { // if this was `var` and not `let`, then `i` would be attached to the function
console.log(bar.charAt(i));
}
console.log(i); // reference error
}
foo();
let
lets us declar vars as only being availabe within a block (i.e. if
or for
etc)
The let
keyword does not "hoist" -- it exists only after it's been declared
The let
keyword means we have to pay attention to what blocks we declare variables in -- extra mental tax
function foo(bar) {
if (bar) {
let baz = bar;
if (baz) {
let bam = baz;
}
console.log(bam); // Error
}
console.log(baz); // Error
}
foo("bar");
Alternative form (rejected by TC39 from ES6):
function foo(bar) {
let (baz = bar) {
console.log(baz); // "bar"
}
console.log(baz); // error
}
foo("bar");
Alternative alternative:
function foo(bar) {
/*let*/ { let baz = bar;
console.log(baz); // "bar"
}
console.log(baz); // error
}
foo("bar");
What if we added a tool to make the alternative form, which is not valid JS, work in JS?
let-er
is a transpiler that adds support for let-blocks -- uses try{throw void 0} catch (foo) { ... }
to force block scopes
This is the "standard" way to force block-scoping in ES5 -- that is, to use try/catch
Use a transpiler!