This draft deletes the entire topic.
Examples
-
When a function is declared, variables in the context of its declaration are captured in its scope. For example, in the code below, the variable
x
is bound to a value in the outer scope, and then the reference tox
is captured in the context ofbar
:var x = 4; // declaration in outer scope function bar() { console.log(x); // outer scope is captured on declaration } bar(); // prints 4 to console
Sample output:
4
This concept of "capturing" scope is interesting because we can use and modify variables from an outer scope even after the outer scope exits. For example, consider the following:
function foo() { var x = 4; // declaration in outer scope function bar() { console.log(x); // outer scope is captured on declaration } return bar; // x goes out of scope after foo returns } var barWithX = foo(); barWithX(); // we can still access x
Sample output:
4
In the above example, when
foo
is called, its context is captured in the functionbar
. So even after it returns,bar
can still access and modify the variablex
. The functionfoo
, whose context is captured in another function, is said to be a closure.Private data
This lets us do some interesting things, such as defining "private" variables that are visible only to a specific function or set of functions. A contrived (but popular) example:
function makeCounter() { var counter = 0; return { value: function () { return counter; }, increment: function () { counter++; } }; } var a = makeCounter(); var b = makeCounter(); a.increment(); console.log(a.value()); console.log(b.value());
Sample output:
1 0
When
makeCounter()
is called, a snapshot of the context of that function is saved. All code insidemakeCounter()
will use that snapshot in their execution. Two calls ofmakeCounter()
will thus create two different snapshots, with their own copy ofcounter
.Immediately-invoked function expressions (IIFE)
Closures are also used to prevent global namespace pollution, often through the use of immediately-invoked function expressions.
Immediately-invoked function expressions (or, perhaps more intuitively, self-executing anonymous functions) are essentially closures that are called right after declaration. The general idea with IIFE's is to invoke the side-effect of creating a separate context that is accessible only to the code within the IIFE.
Suppose we want to be able to reference
jQuery
with$
. Consider the naive method, without using an IIFE:var $ = jQuery; // we've just polluted the global namespace by assigning window.$ to jQuery
In the following example, an IIFE is used to ensure that the
$
is bound tojQuery
only in the context created by the closure:(function ($) { // $ is assigned to jQuery here })(jQuery); // but window.$ binding doesn't exist, so no pollution
See the canonical answer on Stackoverflow for more information on closures.
-
Hoisting is a mechanism which moves all variable and function declarations to the top of their scope. However, variable assignments still happen where they originally were.
For example, consider the following code:
console.log(foo); // - > undefined var foo = 42; console.log(foo); // - > 42
The above code is the same as:
var foo; // Hoisted variable declaration console.log(foo); // - > undefined foo = 42; // variable assignment remains in the same place console.log(foo); // - > 42
Note that due to hoisting the above
undefined
is not the same as thenot defined
resulting from running:console.log(foo); // - > foo is not defined
A similar principle applies to functions. When functions are assigned to a variable (i.e. a function expression), the variable declaration is hoisted while the assignment remains in the same place. The following two code snippets are equivalent.
console.log(foo(2, 3)); // - > foo is not a function var foo = function(a, b) { return a * b; }
var foo; console.log(foo(2, 3)); // - > foo is not a function foo = function(a, b) { return a * b; }
When declaring function statements, a different scenario occurs. Unlike function statements, function declarations are hoisted to the top of their scope. Consider the following code:
console.log(foo(2, 3)); // - > 6 function foo(a, b) { return a * b; }
The above code is the same as the next code snippet due to hoisting:
function foo(a, b) { return a * b; } console.log(foo(2, 3)); // - > 6
Here are some examples of what is and what isn't hoisting:
// (A) valid code: foo(); function foo() {} // (B) **invalid**: bar(); TypeError: bar is not a function var bar = function () {}; // (C) valid: foo(); function foo() { bar(); } function bar() {} // (D) **invalid**: foo(); function foo() { bar(); // TypeError: bar is not a function } var bar = function () {}; // (E) valid: function foo() { bar(); } var bar = function(){}; foo();
Limitations of Hoisting
Initializing a variable can not be Hoisted or In simple JavaScript Hoists declarations not initialization.
For Example:
The below scripts will give different outputs.
<script> var x = 2; var y = 4; alert(x+y); </script>
This will give you an output of 6.
And
<script> var x = 2; alert(x+y); var y = 4; </script>
This will give you an output of NaN. Since we are initializing the value of y, the JavaScript Hoisting is not happening, so the y value will be undefined. The JavaScript will consider that y is not yet declared.
So the second example is same as of below.
<script> var x = 2; var y; alert(x+y); y = 4; </script>
This will give you an output of NaN.
-
-
(Note: All examples using
let
are also valid forconst
)var
is available in all versions of JavaScript, whilelet
andconst
are part of ECMAScript 6 and only available in some newer browsers.var
is scoped to the containing function or the global space, depending when it is declared:var x = 4; // global scope function DoThings() { var x = 7; // function scope console.log(x); } console.log(x); // >> 4 DoThings(); // >> 7 console.log(x); // >> 4
That means it "escapes"
if
statements and all similar block constructs:var x = 4; if (true) { var x = 7; } console.log(x); // >> 7 for (var i = 0; i < 4; i++) { var j = 10; } console.log(i); // >> 4 console.log(j); // >> 10
By comparison,
let
is block scoped:let x = 4; if (true) { let x = 7; console.log(x); // >> 7 } console.log(x); // >> 4 for (let i = 0; i < 4; i++) { let j = 10; } console.log(i); // >> "ReferenceError: i is not defined" console.log(j); // >> "ReferenceError: j is not defined"
Note that
i
andj
are only declared in thefor
loop and are therefore undeclared outside of it.There are several other crucial differences:
Global variable declaration
In the top scope (outside any functions and blocks),
var
declarations put an element in the global object.let
does not:var x = 4; let y = 7; console.log(this.x); // >> 4 console.log(this.y); // >> undefined
Re-declaration
Declaring a variable twice using
var
doesn't produce an error (even though it's equivalent to declaring it once):var x = 4; var x = 7;
With
let
, this produces an error:let x = 4; let x = 7;
TypeError: Identifier
x
has already been declaredThe same is true when
y
is declared withvar
:var y = 4; let y = 7;
TypeError: Identifier
y
has already been declaredHowever variables declared with let can be reused (not re-declared) in a nested block
let i = 5; { let i = 6; console.log(i); // >> 6 } console.log(i); // >> 5
Within the block the outer
i
can be accessed, but if the within block has alet
declaration fori
, the outeri
can not be accessed and will throw aReferenceError
if used before the second is declared.let i = 5; { i = 6; // outer i is unavailable within the Temporal Dead Zone let i; }
ReferenceError: i is not defined
Hoisting
See the main topic on Hoisting for further details.
var
can be referenced before assignment,let
cannot:console.log(x); // >> undefined console.log(y); // >> "ReferenceError: `y` is not defined" var x = 4; let y = 7;
The area between the start of a block and a
let
orconst
declaration is known as the Temporal Dead Zone, and any references to the variable in this area will cause aReferenceError
. -
The
apply
andcall
methods in every function allow it to provide a custom value forthis
.function print() { console.log(this.toPrint); } print.apply({ toPrint: "Foo" }); // >> "Foo" print.call({ toPrint: "Foo" }); // >> "Foo"
You might notice that the syntax for both the invocations used above are the same. i.e. The signature looks similar.
But there is a small difference in their usage, since we are dealing with functions and changing their scopes, we still need to maintain the original arguments passed to the function. Both
apply
andcall
support passing arguments to the target function as follows:function speak() { var sentences = Array.prototype.slice.call(arguments); console.log(this.name+": "+sentences); } var person = { name: "Sunny" }; speak.apply(person, ["I", "Code", "Startups"]); // >> "Sunny: I Code Startups" speak.call(person, "I", "<3", "Javascript"); // >> "Sunny: I <3 Javascript"
Notice that
apply
allows you to pass anArray
or thearguments
object (array-like) as the list of arguments, whereas,call
needs you to pass each argument separately.These two methods give you the freedom to get as fancy as you want, like implementing a poor version of the ECMAScript's native
bind
to create a function that will always be called as a method of an object from an original function.function bind (func, obj) { return function () { return func.apply(obj, Array.prototype.slice.call(arguments, 1)); } } var obj = { name: "Foo" }; function print() { console.log(this.name); } printObj = bind(print, obj); printObj();
This will log
"Foo"
The
bind
function has a lot going onobj
will be used as the value ofthis
- forward the arguments to the function
- and then return the value
-
6
When using arrow functions
this
takes the value from the enclosing execution context'sthis
(that is,this
in arrow functions has lexical scope rather than the usual dynamic scope). In global code (code that doesn't belong to any function) it would be the global object. And it keeps that way, even if you invoke the function declared with the arrow notation from any of the others methods here described.var foo = (() => this); console.log(foo() === window); //true var obj = { name: "Foo" }; console.log(foo.call(obj) === window); //true
NOTE: This will only work in a browser.
See how
this
inherits the contextvar obj = { bar : function() { var x = (() => this); return x; } }; var fn = obj.bar(); console.log(fn() === obj); //true
-
The
bind
method of every function allows you to create new version of that function with the context strictly bound to a specific object. It is specially useful to force a function to be called as a method of an object.var obj = { foo: 'bar' }; function foo() { return this.foo; } fooObj = foo.bind(obj); fooObj();
This will log:
bar
-
Invoking a function as an anonymous function,
this
will be the global object (self
in the browser).function func() { return this; } func() === window; // true
5In ECMAScript 5's strict mode,
this
will beundefined
if the function is invoked anonymously.(function () { "use strict"; func(); }())
This will output
undefined
-
When a function is invoked as a constructor with the
new
keywordthis
takes the value of the object being constructedfunction Obj(name) { this.name = name; } var obj = new Obj("Foo"); console.log(obj);
This will log
{ name: "Foo" }
-
Invoking a function as a method of an object the value of
this
will be that object.var obj = { name: "Foo", print: function () { console.log(this.name) } }
We can now invoke print as a method of obj.
this
will be objobj.print();
This will thus log:
Foo
-
Let's say we need to add a button for each piece of
loadedData
array (for instance, each button should be a slider showing the data; for the sake of simplicity, we'll just alert a message). One may try something like this:for(var i = 0; i < loadedData.length; i++) jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>") .children().last() // now let's attach a handler to the button which is a child .on("click",function() { alert(loadedData[i].content); });
But instead of alerting, each button will cause the
TypeError: loadedData[i] is undefined
error. This is because the scope of
i
is the global scope (or a function scope) and after the loop,i == 3
. What we need is not to "remember the state ofi
". This can be done usinglet
:for(let i = 0; i < loadedData.length; i++) jQuery("#container").append("<a class='button'>"+loadedData[i].label+"</a>") .children().last() // now let's attach a handler to the button which is a child .on("click",function() { alert(loadedData[i].content); });
An example of
loadedData
to be tested with this code:var loadedData = [ { label:"apple", content:"green and round" }, { label:"blackberry", content:"small black or blue" }, { label:"pineapple", content:"weird stuff.. difficult to explain the shape" } ];
Remarks
Scope is the context in which variables live and can be accessed by other code in the same scope. Because JavaScript can largely be used as a functional programming language, knowing the scope of variables and functions is important as it helps to prevent bugs and unexpected behavior at runtime.
Topic Outline
Sign up or log in
Save edit as a guest
Join Stack Overflow
Using Google
Using Facebook
Using Email and Password
We recognize you from another Stack Exchange Network site!
Join and Save Draft