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
and const
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 are undefined
)
- 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 a const
or let
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
or var
, 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);
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
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);
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));
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();
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()
You can even reference other parameters in your expression!
function foo(a = 1, b = (2 * a)) {
console.log(a, b);
}
foo();
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)
The value, undefined
, is what actually triggers the default value
foo(1, undefined, 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