Bindings declared with let
and const
are local to the block that they are declared in, so if you create one of those inside of a loop, the code before and after the loop cannot “see” it. In pre-2015 JavaScript, only functions created new scopes, so old-style bindings, created with the var
keyword, are visible throughout the whole function that they appear in—or throughout the global scope, if they are not in a function.
let x = 10;
if (true) {
let y = 20;
var z = 30;
console.log(x + y + z);
// → 60
}
// y is not visible here
console.log(x + z);
// → 40
const foo = () => {
console.log("Hello World");
}
const bar = function(){
return 1;
}
function foobar(){
return "Hello World";
}
let h = a => a % 3;
JavaScript is extremely broad-minded about the number of arguments you pass to a function. If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined.
function wrapValue(n) {
let local = n;
return () => local;
}
let wrap1 = wrapValue(1);
let wrap2 = wrapValue(2);
console.log(wrap1());
// → 1
console.log(wrap2());
// → 2
This feature—being able to reference a specific instance of a local binding in an enclosing scope—is called closure. A function that references bindings from local scopes around it is called a closure.
A good mental model is to think of function values as containing both the code in their body and the environment in which they are created. When called, the function body sees the environment in which it was created, not the environment in which it is called.
The two main ways to access properties in JavaScript are with a dot and with square brackets. Both value.x
and value[x]
access a property on value—but not necessarily the same property. The difference is in how x
is interpreted. When using a dot, the word after the dot is the literal name of the property. When using square brackets, the expression between the brackets is evaluated to get the property name. Whereas value.x
fetches the property of value named “x
”, value[x]
tries to evaluate the expression x
and uses the result, converted to a string, as the property name.
Property names are strings. They can be any string, but the dot notation works only with names that look like valid binding names. So if you want to access a property named 2
or John Doe
, you must use square brackets: value[2]
or value["John Doe"]
.
The elements in an array are stored as the array’s properties, using numbers as property names. Because you can’t use the dot notation with numbers and usually want to use a binding that holds the index anyway, you have to use the bracket notation to get at them.
Braces have two meanings in JavaScript. At the start of a statement, they start a block of statements. In any other position, they describe an object
. Reading a property that doesn’t exist will give you the value undefined.
let day1 = {
squirrel: false,
events: ["work", "touched tree", "pizza", "running"]
};
console.log(day1.squirrel);
// → false
console.log(day1.wolf);
// → undefined
It is possible to assign a value to a property expression with the =
operator. This will replace the property’s value if it already existed or create a new property on the object if it didn’t.
When you compare objects with JavaScript’s ==
operator, it compares by identity: it will produce true only if both objects are precisely the same value. Comparing different objects will return false, even if they have identical properties. There is no “deep” comparison operation built into JavaScript.
const score = {visitors: 0, home: 0};
// This is okay
score.visitors = 1;
// This isn't allowed
score = {visitors: 1, home: 1};
In addition to their set of properties, most objects also have a prototype. A prototype is another object that is used as a fallback source of properties. When an object gets a request for a property that it does not have, its prototype will be searched for the property, then the prototype’s prototype, and so on.
The prototype relations of JavaScript objects form a tree-shaped structure, and at the root of this structure sits Object.prototype
.
console.log(Object.getPrototypeOf({}) ==
Object.prototype);
// → true
Many objects don’t directly have Object.prototype
as their prototype but instead have another object that provides a different set of default properties. Functions derive from Function.prototype
, and arrays derive from Array.prototype
.
for (let entry of JOURNAL) {
console.log(`${entry.events.length} events.`);
}
When a for loop looks like this, with the word of
after a variable definition, it will loop over the elements of the value given after of
. This works not only for arrays but also for strings and some other data structures.
Receive rest parameter that is bound to an array.
function max(...numbers) {
let result = -Infinity;
for (let number of numbers) {
if (number > result) result = number;
}
return result;
}
console.log(max(4, 1, 9, -2));
// → 9
Unpack an array
let numbers = [5, 1, 7];
console.log(max(...numbers));
// → 7
let words = ["never", "fully"];
console.log(["will", ...words, "understand"]);
// → ["will", "never", "fully", "understand"]
function phi([n00, n01, n10, n11]) {
return (n11 * n00 - n10 * n01) /
Math.sqrt((n10 + n11) * (n00 + n01) *
(n01 + n11) * (n00 + n10));
}
If you know the value you are binding is an array, you can use square brackets to “look inside” of the value, binding its contents.
let {name} = {name: "Faraji", age: 23};
console.log(name);
// → Faraji
A similar trick works for objects, using braces instead of square brackets.
Functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions.
Higher-order functions allow us to abstract over actions, not just values. They come in several forms.
forEach
provides something like a for/of
loop as a higher-order function.
["A", "B"].forEach(l => console.log(l));
The filter
function, rather than deleting elements from the existing array, builds up a new array with only the elements that pass the test. This function is pure. It does not modify the array it is given.
console.log(SCRIPTS.filter(s => s.direction == "ttb"));
The map
method transforms an array by applying a function to all of its elements and building a new array from the returned values. The new array will have the same length as the input array, but its content will have been mapped to a new form by the function.
let rtlScripts = SCRIPTS.filter(s => s.direction == "rtl");
The parameters to reduce
are, apart from the array, a combining function and a start value. It builds a value by repeatedly taking a single element from the array and combining it with the current value.
let a = script.ranges.reduce((count, [from, to]) => {
return count + (to - from);
}, 0);
some
takes a test function and tells you whether that function returns true
for any of the elements in the array.
script.ranges.some(([from, to]) => {return code >= from && code < to;})
JavaScript gives us the functions JSON.stringify
and JSON.parse
to convert data to and from this format. The first takes a JavaScript value and returns a JSON-encoded string. The second takes such a string and converts it to the value it encodes.