A Quick Review on Hoisting
What's hoisting? →
- hoisting is the processing of declarations before any code is executed.
- what's a declaration?
- a declaration is a way of telling the interpreter (or compiler) that a name or identifier exists
- Soooo… Hoisting basically brings declarations to the top of the current scope
- which means declarations do not have to occur before they are used depending on how you declare identifiers!
Hoisting Continued?
What are some hoisting rules? →
let
andconst
declarations are sort of hoisted (their names are created, but they can't be used until they're actually declared)var
declarations are brought to the top of the scope- assignment / initialization is not hoisted (it happens at the original location of that line, so these
var
variables areundefined
) - function declarations are hoisted (including their definition)
What is the output of the following code? Error or no output are possible. →
let x;
console.log(x);
undefined
let
variables are initialized to undefined
console.log(x);
let x;
ReferenceError
Temporal Dead Zone - can't access let
variable before actual declaration
console.log(x);
var x;
undefined
the var
declaration for x is hoisted
What's the Output?
What is the output of the following code? Error or no output are possible. →
f(5);
var f = function(x) {
console.log(x);
}
TypeError: undefined is not a function
variable declaration is hoisted, but the initialization of its value is not…. the value, undefined
is being used as a function!?
About var
A quick summary on using var →
- if you drop
var
(and don't have aconst
orlet
either), the declaration is not hoisted - if you're in a function then
var
will create a local variable … and the scope of it will be that function - within a function, but without
const
,let
orvar
, the interpreter will look up the scope chain until it finds that variable or hits the global scope (at which point it will create it)
Aaaand… Back to Using / Not Using const
, let
or var
What's the output of this code? →
let g = 7;
function f() {
g = 5;
}
f();
console.log(g);
// the variable, g, within the function, f...
// changes the global variable, g
5
A Tricky One…
What's the output of this code? →
let g = 7;
function f() {
g = 5;
function g() {}
}
f();
console.log(g);
7 (!?)
- function g is hoisted and is local to function f
- g = 5 reassigns the local variable g!
And From the Previous Slides
This illustrates going up the scope chain… what's the output? →
let x = 10;
function f() {
function g() {
x = 20;
}
g();
}
console.log(x);
f();
console.log(x);
10 // global
20 // nearest x is global, so global is changed by x = 20
Scope Chain Continued
A minor change in code…. (declaring a local in f). What's the output this time? →
let x = 10;
function f() {
let x = 30;
function g() {
x = 20;
}
g();
}
f();
console.log(x);
// nearest x is in f, so global x is not changed
10
Optional Arguments
You can pass as many or as few arguments to functions as you like!
Wait… what!? →
- if there are aren't enough arguments, the remaining parameters are
undefined
- if there are too many arguments passed in, they're ignored
- there's also an
arguments
variable added to the function's context (along with a this variable) … maybe we'll check these out later
Let's check this out →
Optional Arguments Continued
Given the following function…
function f(a, b) {
console.log(a, b);
}
What is output of this function if called with the following arguments? →
f(1, 2);
f(1);
f();
1 2
1 undefined
undefined undefined
Rest Parameters
Using the following syntax for rest parameters … you can create functions that have an indefinite number of arguments represented as a real Array mixed in with initial positional arguments →
function hiEveryone(greeting, ...names) {
console.log(greeting);
console.log(names);
}
hiEveryone('Hello', 'Alice', 'Bob', 'Carol')
Hello
[ 'Alice', 'Bob', 'Carol' ]
names
is an actual Array
, so you can use Array
methods on it (unlike the arguments
object, which is a fakeArray-like object)
Arguments Object
When a function is called, it gets an arguments object in its context, along with its defined parameters (and this, but we'll talk about that later). Let's see this in action. →
const f = function() {
console.log("number of args " + arguments.length);
for (let i = 0, j = arguments.length; i < j; i++) {
console.log(arguments[i]);
}
};
f(1, 2, 3);
Arguments Object Continued
The arguments object is array-like, but not an array. (Let's see. →)
- you can index into it
- you can get its length
- you can loop over it (with a regular
for
loop) - no methods, though (no
slice
,pop
,forEach
, etc.)
Using the Arguments Object
Create a function called mySum
that takes an arbitrary number of numbers as separate arguments and returns the sum of all of the arguments
console.log(mySum(1, 2, 3)); // --> 6
var mySum = function() {
var total = 0;
for(var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
Arguments vs Rest Parameters
Both arguments
and rest parameters allow for an arbitrary number of arguments. Which should be used? →
- prefer using rest parameters
- you get access to a real
Array
- you can name the resulting
Array
- however, rest parameters are only available as of ES6
Default Values
What operator could we use to give parameters default values if they're not passed in? →
For example, how would we create a function called greetTheWorld
- has one parameter,
greeting
- prints out the
greeting
, followed by"world"
greetTheWorld("Hi")
→Hi world!
- however, if an argument is not passed in, greeting should default to
"Hello"
greetTheWorld()
→Hello world!
Let's see an implementation in the next slide. →
Default Values Continued
We can use the ||
operator to give a default value if the value on the left is false-y:
function greetTheWorld(greeting) {
console.log((greeting || "Hello") + " world!");
}
Default Values in ES6
You can also add default values directly in the function header in ES6 →
function f(x=1) {
console.log(x);
}
f(); // no args, result is 1!
Default Parameter Value Details
These values are evaluated at call time, so a new object is created each time (that way, changes won't be persisted across function calls for default arguments that are mutable) →
function extraSauce(condiments = []) {
condiments.push('ketchup');
console.log(condiments);
}
extraSauce() // ['ketchup']
extraSauce() // still just ['ketchup']
You can even reference other parameters in your expression!
function foo(a = 1, b = (2 * a)) {
console.log(a, b);
}
foo(); // 1 2
foo(7); // 7 14
More Default Parameter Value Details!
Parameters with default values can be anywhere in parameter list →
function foo(a, b = 'it me!', c) {
console.log(a, b, c);
}
foo(1, 2, 3) // 1 2 3
foo(1) // 1 'it me!' undefined
The value, undefined
, is what actually triggers the default value
foo(1, undefined, 3) // 1 it me! 3
Closure
Functions retain access to their original scope, even when the outer function they were defined in has returned. What happens here? →
let gimmeFunction = function() {
let a = "I'm in here!";
return function() {
console.log(a);
}
}
let myFunction = gimmeFunction();
myFunction();
I'm in here!
Wait. What Happened?
(Via MDN)…
- normally, the local variables within a function only exist for the duration of that function's execution
- once the outer function
gimmeFunction
finishes executing, you'd expect that its local variable,a
, would no longer be accessible - however… a closure is created when it returns a function!
- a closure is a special kind of object that combines two things:
- a function
- the environment in which that function was created
- the environment consists of any local letiables that were in-scope at the time that the closure was created