- Explain what a block is.
- Describe the difference between global and local scope in JavaScript.
- Identify which part(s) of JavaScript create new scope.
- Identify which variables are accessible in various scopes.
- Define a call back function
- Use pass a callback function to another function and invoke it
- Use an anonymous function with an array iteration method
- Fork and clone this repository.
- Create a new branch,
training
, for your work. - Checkout to the
training
branch.
A Block statement is used to group code together. To create a block, we use a pair of curly braces:
{
// Statements
}
Optionally, a block can be labeled as a visual identifier or as a target for break.
Blocks are also used with functions, conditionals and loops:
if ( /* true || false */ ) { /* within the block, body of conditional */ }
for ( /* let i = 0; ...*/ ) { /* within the block, body of loop */ }
while ( /* i < num ... */ ) { /* within the block, body of loop */ }
function ( /* arg1, arg2 */ ) { /* within the block, body of function */ }
In addition to grouping code together, blocks create a new scope for the variables defined within the block.
When we use blocks, we create a new scope for the variables defined within the block. Within a block, if we are using the ES6 let
and const
variables (which you should), these variables have block scope, meaning the variables defined within the block are limited in scope to the block in which it is defined:
const name = 'Danny'
{
const name = 'Caleb'
}
console.log(name) // prints 'Danny'
// name = 'Caleb' is limited in scope to the block in which it is defined
You can think of scope as a collection of nested boxes. Each scope acts as a container in which variables and functions can be declared. while JavaScript is executing code within a scope, it only has access to identifiers declared in the current scope and higher scopes, the parent and global scopes.
Let's see some more code examples of scopes.
Remember that block scope means our different scopes are separated by blocks { }
.
// I am not inside a block
if (true) {
// i am inside a block
}
// I am not inside a block
NOT objects but blocks.
if (true) {
// i am inside a block
}
const obj = {
prop1: 'I am not inside a block',
prop2: 'This is an object silly'
}
The outer most scope is the global scope and all inner scopes are considered local scopes:
// global scope
if (true) {
// local scope
}
// global scope
Variables are accessible within the scope they are declared:
// global scope
if (true) {
// local scope
const x = 1 // what would happen if `var` were used instead?
console.log(x) // 1
// When should we use `console` functions?
}
// global scope
console.log(x) // ReferenceError: x is not defined
Variables are accessible to any inner scopes (child scopes):
// global scope
let x = 1
if (true) {
// local scope
x = 2
console.log(x) // 2
}
// global scope
console.log(x) // 2
But not to the scopes above them (parent scopes):
// global scope
const x = 1
if (true) {
// local scope
const y = x
console.log(y) // 1
}
// global scope
console.log(x) // 1
console.log(y) // ReferenceError: y is not defined
Variables are not accessible from sibling scopes:
if (true) {
// local scope of 1st sibling
const a = 1
console.log(a) // 1
}
if (true) {
// local scope of 2nd sibling
console.log(a) // ReferenceError: a is not defined
}
Different scopes can have variables that are declared with the same name and they do not conflict or know about each other.
// global scope
const x = 1
console.log(x) // 1
if (true) {
// local scope
const x = 2
console.log(x) // 2
}
// global scope
console.log(x) // 1
So that means a variable declared in the global scope is accessible by all of the scopes we create and a variable declared in a local scope is only accessible to itself and its child scopes.
Within bin/scope-practice.js
, let's get some practice creating global and nested block scopes.
// global scope
const a = 1
if (true) {
// local scope of 1st nested if statement
const b = 2
if (true) {
// local scope of 2nd nested if statement
const c = 3
console.log(a) // 1
console.log(b) // 2
console.log(c) // 3
}
// local scope of 1st nested if statement
console.log(a) // 1
console.log(b) // 2
console.log(c) // ReferenceError: c is not defined
}
// global scope
console.log(a) // 1
console.log(b) // ReferenceError: b is not defined
console.log(c) // ReferenceError: c is not defined
Conditions are just 1 example of block scope. Loops are another example of block scope.
while (true) { // don't run this
const a = 1
console.log(a) // 1
}
console.log(a) // ReferenceError: a is not defined
// For Loops still have block scope even though the syntax is different.
for (let i = 1; i < 2; i++) {
console.log(i) // 1
}
console.log(i) // ReferenceError: i is not defined
// Functions are another example of block scope.
const anyFunction = function () {
const a = 1
console.log(a) // 1
}
console.log(a) // ReferenceError: a is not defined
// The scope of our parameters are within the function block as well
const print = function (a) {
console.log(a)
}
print(1) // 1
console.log(a) // ReferenceError: a is not defined
As we have seen, utilizing scope provides great utility. We get more control over who can access and manipulate our data. We can use scope to declare a variable without polluting the global namespace. Scoping provides a way to encapsulate data and prevent other parts of our applciation from accessing variables declared within a certain scope.
When you are not familiar with the rules of scope, it will be a common source of bugs and frustration. By being aware of how scope is created, and by using scope effectively, you will write code that is more efficient, organized and less error prone.
- Don't declare or depend on global state
- Apply the Principle of Least Privilege: Allow code to access the information and resources that are necessary for it to run, and nothing more.
- Encapsulate code as much as possible in scope using functions and blocks
- Never use
var
(preferconst
overlet
, but never usevar
) - Never depend on scope state
- Always pass in state as an argument when it is needed
Along with primitives and reference types, you can also pass in functions into other functions. A function at the end of the day is just another kind of object.
A function that is passed to another function is called a callback.
const add = function (num1, num2) {
return num1 + num2
}
const subtract = function (num1, num2) {
return num1 - num2
}
const doMath = function (num1, num2, operation) {
return operation(num1, num2)
}
doMath(2,1, add) // 3
doMath(2,1, subtract) // 1
This is a very important term: What is a callback?
A callback is a function that is passed to another function.
We'll explore the array methods that allow us to test and transform arrays more simply and consistently, Iteration methods, and optionally at the end, we'll model some of these JavaScript Array methods as functions. Being able to use these methods correctly is our main goal.
There are two main groups of array iteration methods:
- Those that must process all of the array elements
- Those that may only process a subset of the array elements
The
forEach
method iterates over all of the elements in an array. Unlike a for
loop, it
cannot be stopped (all elements are processed). forEach
returns undefined
.
From the MDN documentation:
There is no way to stop or break a forEach() loop other than by throwing an
exception. If you need such behavior, the forEach() method is the wrong
tool, use a plain loop instead. If you are testing the array elements for a
predicate and need a Boolean return value, you can use every() or some()
instead. If available, the new methods find() or findIndex() can be used for
early termination upon true predicates as well.
This means that forEach
is a poor choice for an array operation that may
terminate early.
The
map
method returns a new array the same size as the existing array. The
elements of the new array are set to the return value of the callback passed to
map
invoked with the corresponding element from the original array as its
argument (e.g. newArray[i] = callback(array[i])
). The array map
is called
upon is not mutated.
The
filter
method returns a new array containing elements from the original array for
which the callback returns true
. filter
uses a predicate callback method to
decide on which elements to add to the new array that it returns. The length of
the new array may be 0 if the callback returned false
for every element, or
equal to the length of the original array, if the callback returned true
for
every element in the original array.
Callbacks passed to filter
should be predicate functions.
The findIndex method returns the index of the first element in the array for which the callback returns true.
Why do we need findIndex
? Why not just use
indexOf?
The find method returns the first element in the array for which the callback returns true.
The
some
method return true if the callback returns true
for any element of the array.
Callbacks passed to some
should be predicate functions.
The every
method checks to see if all elements of the array meet some test. The function
used for this should only return true
or false
. This type of function is
often called a predicate.
Callbacks passed to every
should be predicate functions.
- All content is licensed under a CCBYNCSA 4.0 license.
- All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact [email protected].