Resource |
URL |
---|---|
MDN |
|
Run Snippets |
https://developers.google.com/web/tools/chrome-devtools/javascript/snippets |
The simplest way to perform an explicit type conversion is to use the Boolean(), Number()
,
and String()
functions.
Any value other than null
or undefined
has a toString()
method.
|
binary |
|
octal |
|
hex |
|
|
|
"123457" |
|
"123456.78900" |
|
"1.235e+5" |
|
"123456.8" |
|
"123456.7890" |
|
3 |
|
3.14 |
|
-12 |
|
255 |
|
255 |
|
-255 |
|
0 |
|
NaN: integers can't start with "." |
|
NaN: numbers can't start with "$" |
Supply Radix |
|
|
3 |
|
255 |
|
63 |
|
|
|
|
|
|
|
|
|
let x=1, y=2 |
|
x = x + 1, y = y + 1 |
|
Swap the value of the two variables |
Destructuring assignment makes it easy to work with functions that return arrays of values:
|
|
Variable destructuring in loops:
|
|
|
Note: The Object.entries()
method returns an array of a given object's own
enumerable string-keyed property [key, value]
pairs, in the same order as that provided
by a for...in
loop. (The only important difference is that
a for...in
loop enumerates properties in the prototype chain as well).
The list of variables on the left can include extra commas to skip certain values on the right
|
x == 2; y == 4 |
Note: the last comma does not stand for a value.
To collect all unused or remaining values into a single variable when destructuring an array, use three dots
(...)
before the last variable name on the left-hand side
|
y == [2,3,4] |
|
first == "H"; rest ==["e","l","l","o"] |
Destructuring assignment can also be performed when the righthand side is an object value.
|
|
|
r == 0.0; g == 0.0; b == 0.0 |
|
sin=Math.sin, cos=Math.cos, tan=Math.tan |
In JavaScript, the values null
and undefined
are the only two values that do not have
properties. In a regular property access expression using . or [], you get a TypeError
if the
expression on the left evaluates to null
or undefined
. You can
use ?.
and ?.[]
syntax to guard against errors of this type.
You can also invoke a function using ?.()
instead of ()
.
With the new ?.()
invocation syntax, if the expression to the left of the ?.
evaluates to null
or undefined
, then the entire invocation expression
evaluates to undefined
and no exception is thrown.
Write the function invocation using ?.(),
knowing that invocation will only happen if there is
actually a value to be invoked
|
Note that expression x++
is not always the same as x = x + 1
.The
++
operator never performs string concatenation: it always converts its operand to a number and
increments it. If x is the string “1”, ++x
is the number 2, but x + 1
is the string “11”.
JavaScript objects are compared by reference, not by value. An object is equal to itself, but not to any other object. If two distinct objects have the same number of properties, with the same names and values, they are still not equal. Similarly, two arrays that have the same elements in the same order are not equal to each other.
NaN
value is never equal to any other value, including itself! To check whether a value x
is NaN
, use x !==
, or the global isNaN()
function.
If both values refer to the same object, array, or function, they are equal. If they refer to different objects, they are not equal, even if both objects have identical properties.
JavaScript has the ability to interpret strings of JavaScript source code, evaluating them to produce a value.
|
Because of security issues, some web servers use the HTTP “Content-Security-Policy” header to
disable eval()
for an entire website.
The first-defined operator ??
evaluates to its first defined operand: if its left operand is
not null
and not undefined
, it returns that value.
a ?? b
is equivalent to (a !== null && a !== undefined) ? a : b
??
is a useful alternative to ||.
The problem with this idiomatic use is that zero,
the empty string, and false are all falsy
values that may be perfectly valid in some circumstances. In
this code example, if maxWidth
is zero, that value will be ignored. But if we change the
||
operator to ??
, we end up with an expression where zero is a valid value.
|
|
Deleting an array element leaves a “hole” in the array and does not change the array’s length. The resulting array is sparse.
Using the void
operator makes sense only if the operand has side effects.
|
|
|
undefined |
|
1 |
Expressions are evaluated to produce a value, but statements are executed to make something happen.
Expressions with side effects, such as assignments and function invocations, can stand alone as statements, and when used this way are known as expression statements.
A similar category of statements are the declaration statements that declare new variables and define new functions.
If a function does not have any side effects, there is no sense in calling it, unless it is part of a larger expression or an assignment statement.
The for/of
loop works with iterable objects. Arrays, strings, sets, and maps are iterable.
Array |
|
||
String |
|
||
Map |
|
Objects are not (by default) iterable. Attempting to use for/of
on a regular object throws
a TypeError
at runtime.
If you want to iterate through the properties of an object, you can use the for/in
loop.
Note: for/of
can be used on objects with Object.entries
property, but it will not pick
properties from object’s prototype.
for/in
loop works with any object after the in
.
|
Note: this will enumerate array indexes, not values.
|
The for/in
loop does not actually enumerate all properties of an object. It does not enumerate
properties whose names are symbols. And of the properties whose names are strings, it only loops over
the enumerable
properties.
The with statement runs a block of code as if the properties of a specified object were variables in scope for that code.
The with
statement is forbidden in strict mode and should be considered deprecated in non-strict
mode: avoid using it whenever possible.
|
|
If a debugger program is available and is running, then an implementation may (but is not required to) perform some kind of debugging action.
In practice, this statement acts like a breakpoint: execution of JavaScript code stops, and you can use the debugger to print variables’ values, examine the call stack, and so on.
Note that it is not enough to have a debugger available: the debugger statement won’t start the debugger for you. If you’re using a web browser and have the developer tools console open, however, this statement will cause a breakpoint.
Strict mode is a restricted subset of the language that fixes important language deficiencies and provides stronger error checking and increased security.
The differences between strict mode and non-strict mode are the following:
The with
statement is not allowed in strict mode.
In strict mode, all variables must be declared: a ReferenceError
is thrown if you assign a value
to an identifier that is not a declared variable, function, function parameter, catch clause parameter, or
property of the global object.
In non-strict mode, this implicitly declares a global variable by adding a new property to the global object.
In strict mode, functions invoked as functions (rather than as methods) have a this
value of
undefined. (In non-strict mode, functions invoked as functions are always passed the global object as their
this
value.)
A function is invoked with call()
or apply()
, the this
value is exactly
the value passed as the first argument to call()
or apply()
. (In non-strict mode,
null
and undefined
values are replaced with the global object and non-object values
are converted to objects.)
In strict mode, assignments to non-writable properties and attempts to create new properties on non-extensible
objects throw a TypeError
. (In non-strict mode, these attempts fail silently.)
In strict mode, code passed to eval()
cannot declare variables or define functions in the caller’s
scope as it can in non-strict mode. Instead, variable and function definitions live in a new scope created for
the eval()
. This scope is discarded when the eval()
returns.
In strict mode, the Arguments object in a function holds a static copy of the values passed to the function. In non-strict mode, the Arguments object has “magical” behavior in which elements of the array and named function parameters both refer to the same value.
In strict mode, a SyntaxError
is thrown if the delete
operator is followed by an
unqualified identifier such as a variable, function, or function parameter. (In non-strict mode, such a
delete
expression does nothing and evaluates to false.)
In strict mode, an attempt to delete a non-configurable property throws a TypeError
. (In
non-strict mode, the attempt fails and the delete expression evaluates to false.)
In strict mode, it is a syntax error for an object literal to define two or more properties by the same name. (In non-strict mode, no error occurs.)
...
In addition to its name and value, each property has three property attributes:
The writable
attribute specifies whether the value of the property can be set.
The enumerable
attribute specifies whether the property name is returned by a for/in
loop.
The configurable
attribute specifies whether the property can be deleted and whether its
attributes can be altered.
All objects created by object literals have the same prototype object, Object.prototype.
Objects created using the new
keyword and a constructor invocation use the value of the prototype
property of the constructor function as their prototype.
Object created by new Object()
inherits from Object.prototype
, just as the
object created by {}
does. Similarly, the object created
by new Array()
uses Array.prototype
as its prototype, and the
object created by new Date()
uses Date.prototype
as its prototype.
Almost all objects have a prototype, but only a relatively small number of objects have a prototype
property. It is these objects with prototype properties that define the prototypes for all the other objects.
Most built-in constructors (and most user-defined constructors) have a prototype that inherits
from Object.prototype
.
Date.prototype
inherits properties from Object.prototype
, so a Date object
created by new Date()
inherits properties from
both Date.prototype
and Object.prototype
. This linked series of prototype
objects is known as a prototype chain.
Objects can be created with object literals, with the new
keyword, and with
the Object.create()
function.
Literal |
|
|
|
|
|
|
|
Use Object.create
to guard against accidental modifications:
|
Note: the library function can modify the passed in object, but not the original o
object
|
|
|
|
|
|
Property
|
|
|
The delete
operator only deletes own properties, not inherited ones. (To delete an inherited
property, you must delete it from the prototype object in which it is defined. Doing this affects every object that
inherits from that prototype.)
delete
does not remove properties that have a configurable
attribute of false.
Certain properties of built-in objects are non-configurable, as are properties of the global object created by variable declaration and function declaration.
|
false: property is non-configurable |
|
false: can't delete |
|
false |
|
true |
To check whether an object has a property with a given name. You can do this with
the in
operator, with
the hasOwnProperty()
and propertyIsEnumerable()
methods, or simply by
querying the property
( != undefined
).
|
||
|
true |
|
|
false |
|
|
true: o inherits a |
|
Advantage of using in: in
can distinguish between properties that do not exist and properties
that exist but have been set to undefined
.
|
|
|
true |
|
false |
|
false: toString is an inherited property |
The propertyIsEnumerable()
returns true only if the named property is an own property and
its enumerable
attribute is true.
|
|
|
true |
|
false: not an own property |
|
false: not enumerable |
To guard against enumerating inherited properties with for/in
, you can add an explicit check inside
the loop body:
|
Functions you can use to get an array of property names
Object.keys()
returns an array of the names of the enumerable own properties of an object. It
does not include non-enumerable properties, inherited properties, or properties whose name is a Symbol.
Object.getOwnPropertyNames()
works like Object.keys()
but returns an array of
the names of nonenumerable own properties as well.
Object.getOwnPropertySymbols()
returns own properties whose names are Symbols, whether or not
they are enumerable.
Reflect.ownKeys()
returns all own property names, both enumerable and non-enumerable, and
both string and Symbol.
To copy the properties of one object to another object
|
One reason to assign properties from one object into another is when you have an object that defines default values
for many properties and you want to copy those default properties into another object if a property by
that name does not already exist in that object. Using Object.assign()
naively will not do
what you want:
|
overwrites everything in o with defaults |
Instead, use one of the following:,
|
|
The functions JSON.stringify()
and JSON.parse()
serialize and restore
JavaScript objects.
|
|
|
s == '{"x":1,"y":{"z":[false,null,""]}}' |
|
p == {x: 1, y: {z: [false,null, ""]}} |
toString(), valueOf(), loLocaleString(), toJSON()
|
s == "[object Object]" |
|
<--> |
|
|
|
<--> |
|
|
Two Symbols created with the same string argument are still different from one another.
The point of Symbols is not security, but to define a safe extension mechanism for JavaScript objects. If you get an object from third-party code that you do not control and need to add some of your own properties to that object but want to be sure that your properties will not conflict with any properties that may already exist on the object, you can safely use Symbols as your property names.
You can copy the properties of an existing object into a new object using the “spread operator” ... inside an object literal:
|
|
<--> |
|
When you write a method using this shorthand syntax, the property name can take any of the forms that are legal in an object literal: in addition to a regular JavaScript identifier like the name area above, you can also use string literals and computed property names, which can include Symbol property names:
|
||
|
2 |
|
|
3 |
|
|
4 |
|
Array literals
The ... spread operator on an iterable object
The Array()
constructor
The Array.of()
and Array.from()
factory methods
|
|
If an array literal contains multiple commas in a row, with no value between, the array is sparse
|
|
Array literal syntax allows an optional trailing comma, so [,,]
has a length of 2, not 3.
|
|
|
[0, 1, 2, 3, 4] |
create a copy of an array - modifying the copy does not change the original
|
|
["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] |
|
["h","e","l",”l”"o","","w",”o”"r",”l”,"d"] |
|
["h","e","l","o","","w","r","d"] |
When the Array()
constructor function is invoked with one numeric argument, it uses that
argument as an array length. But when invoked with more than one numeric argument, it treats those arguments as
elements for the array to be created. This means that the Array()
constructor cannot be used
to create an array with a single numeric element.
|
[] |
|
[10] |
|
[1, 2, 3] |
It is also a simple way to make a copy of an array:
|
Array.from()
is also important because it defines a way to make a true-array copy of an array-like
object. Array-like objects are non-array objects that have a numeric length property and have values stored with
properties whose names happen to be integers.
|
Array.from()
also accepts an optional second argument. If you pass a function as the second
argument, then as the new array is being built, each element from the source object will be passed to the function
you specify, and the return value of the function will be stored in the array instead of the original value.
What is special about arrays is that when you use property names that are non-negative integers , the array
automatically maintains the value of the length
property for you.
JavaScript converts the numeric array index you specify to a string—the index 1 becomes the string "1", then uses that string as a property name.
It is helpful to clearly distinguish an array index from an object property name. All indexes are property names,
but only property names that are integers between 0 and 231 are indexes. All arrays are objects, and you
can create properties of any name on them. If you use properties that are array indexes, however, arrays have the
special behavior of updating their length
property as needed.
Note that you can index an array using numbers that are negative or that are not integers. When you do this, the number is converted to a string, and that string is used as the property name. Since the name is not a non-negative integer, it is treated as a regular object property, not an array index.
|
This creates a property named "-1.23" |
|
This the 1001st element of the array |
|
Array index 1. Same as a[1] = 1; |
The fact that array indexes are simply a special type of object property name means that JavaScript arrays have no
notion of an “out of bounds” error. When you try to query a nonexistent property of any object, you don’t get an
error; you simply get undefined
.
Sparse arrays can be created with the Array()
constructor or simply by assigning to an array
index larger than the current array length.
|
Assignment adds one element but sets length to 1001. |
you can also make an array sparse with the delete
operator.
Note that when you omit a value in an array literal (using repeated commas as in [1,,3]
), the
resulting array is sparse, and the omitted elements simply do not exist
if you set the length property to a nonnegative integer n
smaller than its current value, any array
elements whose index is greater than or equal to n are deleted from the array.
|
|
|
a is now [1,2,3]. |
|
Delete all elements. a is []. |
|
Length is 5, but no elements, like |
You can also set the length property of an array to a value larger than its current value. Doing this does not actually add any new elements to the array; it simply creates a sparse area at the end of the array.
|
|
|
add elements to it. |
You can also use the push()
method to add one or more values to the end of an array.
You can use the unshift()
method to insert a value at the beginning of an array, shifting
the existing array elements to higher indexes.
The pop()
method is the opposite of push()
: it removes the last element of the
array and returns it, reducing the length of an array by 1.
Similarly, the shift()
method removes and returns the first element of the array, reducing
the length by 1 and shifting all elements down to an index one lower than their current index.
You can delete array elements with the delete operator
|
|
|
a now has no element at index 2 |
|
false |
|
3: delete does not affect array length |
The easiest way to loop through each of the elements of an array (or any iterable object) is with
the for/of
loop
|
It has no special behavior for sparse arrays and simply returns undefined
for any array
elements that do not exist.
If you want to use a for/of
loop for an array and need to know the index of each array
element, use the entries()
method of the array
|
Another good way to iterate arrays is with forEach()
. This is not a new form of the for loop, but
an array method that offers a functional approach to array iteration.
|
You can also loop through the elements of an array with a for
loop.
|
|
|
|
First, all of these methods accept a function as their first argument and invoke that function once for each element (or some elements) of the array. If the array is sparse, the function you pass is not invoked for nonexistent elements. In most cases, the function you supply is invoked with three arguments: the value of the array element, the index of the array element, and the array itself.
FOREACH() |
|
[2,3,4,5,6] |
||
MAP() |
|
[1, 4, 9] |
||
FILTER() |
|
[2, 1]; [5, 3, 1]; |
||
FIND() FINDINDEX() |
|
2 5 undefined |
||
EVERY() SOME() |
|
true true false |
||
REDUCE() ReduceRight() |
l
|
15 120 5 |
Note that map()
returns a new array: it does not modify the array it is invoked on. If that
array is sparse, your function will not be called for the missing elements, but the returned array will be sparse in
the same way as the original array: it will have the same length and the same missing elements.
To close the gaps in a sparse array, you can do this:
|
And to close gaps and remove undefined and null elements, you can use filter, like this:
|
Unlike filter()
,
however, find()
and findIndex()
stop iterating the first time the
predicate finds an element. When that happens, find()
returns the matching element,
and findIndex()
returns the index of the matching element. If no matching element is
found, find()
returns undefined
and findIndex()
returns
-1.
When you invoke reduce()
with no initial value, it uses the first element of the array as
the initial value.
reduceRight()
works just like reduce()
, except that it processes the array from
highest index to lowest (right-to-left), rather than from lowest to highest. You might want to do this if the
reduction operation has right-to-left associativity
flat()
and flatMap()
|
[1, 2, 3] |
|
[1, 2, [3]] |
|
[1, 2, 3, [4]] [1, 2, 3, 4] [1, 2, 3, 4] |
|
["hello", "world", "the", "definitive", "guide"]; |
Calling a.flatMap(f)
is the same as (but more efficient
than) a.map(f).flat()
:
|
|
|
[1,2,3,4,5] |
|
[1,2,3,4,5,6,7] |
The push()
and pop()
methods allow you to work with arrays as if they
were stacks. The push()
method appends one or more new elements to the end of an array and
returns the new length of the array.
The unshift()
and shift()
methods behave much
like push()
and pop()
, except that they insert and remove elements from the
beginning of an array rather than from the end.
You can implement a queue data structure by using push()
to add elements at the end of an
array and shift()
to remove them from the start of the array. Note differences in
unshift
with single and multiple values.
|
|
|
[1] |
|
[2, 1] |
|
|
|
[1, 2] |
slice(), splice(), fill()
, and copyWithin()
SLICE() |
|
[1,2,3] [4,5] [2,3,4] [3] |
SPLICE |
|
[5,6,7,8]; [2,3]; a is now [1,4] [4]; a is now [1] []; a is now [1,2,"a","b",3,4,5] ["a","b"]; a is now [1,2,[1,2],3,3,4,5] |
FILL() |
|
[0,0,0,0,0] [0,9,9,9,9] [0,9,8,8,9] |
COPYWITHIN() |
|
[1,1,2,3,4] [1,1,3,4,4] [4,4,3,4,4] |
splice()
is a general-purpose method for inserting or removing elements from an
array. splice()
can delete elements from an array, insert new elements into an array, or
perform both operations at the same time.
The first argument to splice()
specifies the array position at which the insertion and/or
deletion is to begin. The second argument specifies the number of elements that should be deleted from (spliced out
of) the array.
Unlike concat(), splice()
inserts arrays themselves, not the elements of those arrays.
copyWithin()
copies a slice of an array to a new position within the array. It modifies the array
in place and returns the modified array, but it will not change the length of the array.
INDEXOF() |
|
1 3 -1 |
|
SORT() |
Case-insensitive sort
|
|
|
REVERSE() |
|
[3,2,1] |
indexOf()
and lastIndexOf()
compare their argument to the array elements
using the equivalent of the === operator. If your array contains objects instead of primitive values, these methods
check to see if two references both refer to exactly the same object. If you want to actually look at the content of
an object, try using the find()
method with your own custom predicate function instead.
indexOf()
and lastIndexOf()
take an optional second argument that
specifies the array index at which to begin the search. Negative values are allowed for the second argument and are
treated as an offset from the end of the array.
indexOf()
will not detect the NaN value in an array, but includes()
will
When sort()
is called with no arguments, it sorts the array elements in alphabetical order.
To sort an array into some order other than alphabetical, you must pass a comparison function as an argument
to sort()
.
The join()
method converts all the elements of an array to strings and concatenates them,
returning the resulting string.
|
|
|
"1,2,3" "1 2 3" "123" |
|
|
|
"---------" |
Arrays, like all JavaScript objects, have a toString()
method. For an array, this method
works just like the join()
method with no arguments:
|
"1,2,3" |
|
"a,b,c" |
|
"1,2,c" |
|
true |
|
false |
It is often perfectly reasonable to treat any object with a numeric length
property and
corresponding non-negative integer properties as a kind of array.
|
Since array-like objects do not inherit from Array.prototype
, you cannot invoke array methods on
them directly. You can invoke them indirectly using the Function.call
method.
|
// An array-like object |
|
"a+b+c" |
|
"J a v a S c r i p t" |
|
["A","B","C"] |
|
["a","b","c"] |
|
|
|
t |
|
e |
In addition to the arguments, each invocation has another value - the invocation context - that is the value of
the this
keyword.
|
Function declaration statements are “hoisted” to the top of the enclosing script, function, or block so that functions defined in this way may be invoked from code that appears before the definition.
|
||
|
Function expressions can include names, which is useful for recursion |
|
|
Function expressions can also be used as arguments to other functions |
|
|
Function expressions are sometimes defined and immediately invoked |
|
|
|
no need for |
|
omit parens with single parameter |
|
usage for no params |
If the body of your arrow function is a single return statement but the expression to be returned is an object literal, then you have to put the object literal inside parentheses to avoid syntactic ambiguity between the curly braces of a function body and the curly braces of an object literal
|
good |
|
good |
|
returns nothing |
|
syntax error |
Arrow functions differ from functions defined in other ways in one critical way: they inherit the value of
the this
keyword from the environment in which they are defined rather than defining their
own invocation context as functions defined in other ways do.
|
For function invocation in non-strict mode, the invocation context (the this
value) is the
global object. In strict mode, however, the invocation context is undefined
.
|
Determine if we're in strict mode |
A constructor invocation creates a new, empty object that inherits from the object specified by
the prototype
property of the constructor.
JavaScript functions are objects, and like all JavaScript objects, they have methods. Two of these
methods, call()
and apply()
, invoke the function indirectly. Both methods
allow you to explicitly specify the this
value for the invocation, which means you can invoke any
function as a method of any object, even if it is not actually a method of that object.
When a function is invoked with fewer arguments than declared parameters, the additional parameters are set to
their default value, which is normally undefined
.
|
|
One interesting case is that, for functions with multiple parameters, you can use the value of a previous parameter to define the default value of the parameters that follow it
|
Rest parameters enable us to write functions that can be invoked with arbitrarily more arguments than parameters.
|
|
|
|
1000 |
within the body of a function, the value of a rest parameter will always be an array. The array may be empty, but a
rest parameter will never be undefined
.
This type of function is called variadic functions, variable arity functions, or vararg functions.
Within the body of any function, the identifier arguments
refers to the Arguments object for
that invocation.
|
|
|
|
1000 |
you should avoid using it in any new code you write.
|
|
|
|
-1 |
|
|
|
|
|
|
<--> |
|
||
|
<--> |
|
Adding code to check the types of arguments
|
||
|
6 |
|
|
|
|
|
|
|
|
|
|
16 |
|
16 |
Functions can also be assigned to object properties rather than variables.
|
|
|
256 |
Functions don’t even require names at all, as when they’re assigned to array elements:
|
|
|
400 |
a[0]
accesses first element of the array, which is "x =>
x*x
", (a[1])
passes parameter, which is 20.
|
||
|
||
|
|
or:
|
||
|
||
|
// "hello world" |
|
|
100 |
When a function needs a “static” variable whose value persists across invocations, it is often convenient to use a property of the function itself.
For example, suppose you want to write a function that returns a unique integer whenever it is invoked. The function must never return the same value twice. In order to manage this, the function needs to keep track of the values it has already returned, and this information must persist across function invocations.
|
||
|
0 |
|
|
1 |
Compute factorials and cache results as properties of the function itself.
|
||
|
Initialize the cache to hold this base case. |
|
|
720 |
|
|
120; the call above caches this value |
Variables declared within a function are not visible outside of the function. For this reason, it is sometimes useful to define a function simply to act as a temporary namespace in which you can define variables without cluttering the global namespace.
Variables that would have been global become local to the function. Following code defines only a single global
variable: the function name chunkNamespace
.
|
|
|
If defining even a single property is too much, you can define and invoke an anonymous function in a single expression - IIEF (immediately invoked function expression)
|
JavaScript uses lexical scoping. This means that functions are executed using the variable scope that was in effect when they were defined, not the variable scope that is in effect when they are invoked.
In order to implement lexical scoping, the internal state of a JavaScript function object must include not only the code of the function but also a reference to the scope in which the function definition appears.
This combination of a function object and a scope (a set of variable bindings) in which the function’s variables are resolved is called a closure.
Closures become interesting when they are invoked from a different scope than the one they were defined in. This happens most commonly when a nested function object is returned from the function within which it was defined.
|
||
|
"local scope" |
|
|
|
|
|
"local scope" |
Closures capture the local variables of a single function invocation and can use those variables as private state.
|
|
|
|
0 |
|
|
1 |
it is the return value of the function that is being assigned to uniqueInteger
.
Private variables like counter need not be exclusive to a single closure: it is perfectly possible for two or more nested functions to be defined within the same outer function and share the same scope.
|
||
|
||
|
0 |
|
|
0 |
|
|
|
|
|
0 |
|
|
1 |
You can combine this closure technique with property getters and setters
|
||
|
||
|
1000 |
|
|
1001 |
|
|
|
|
|
2000 |
|
|
Error: count can only be set to a larger value |
Define a private variable and two nested functions to get and set the value of that variable.
|
||
|
|
|
|
|
|
|
|
|
|
"Frank" |
|
|
TypeError: try to set a value ofthe wrong type |
Since functions are objects, they can have properties and methods, just like any other object.
The read-only length property of a function specifies the arity of the function—the number of parameters it declares in its parameter list, which is usually the number of arguments that the function expects.
This property is primarily useful when writing debugging or error messages.
When a function is used as a constructor, the newly created object inherits properties from the prototype object.
call()
and apply()
allow you to indirectly invoke a function as if it were
a method of some other object. The first argument to
both call()
and apply()
is the object on which the function is to be
invoked; this argument is the invocation context and becomes the value of the this
keyword
within the body of the function.
To invoke the function f()
as a method of the object o (passing no arguments),
|
|
To pass two numbers to the function f()
and invoke it as if it were a method of the object
o,
|
The apply()
method is like the call()
method, except that the
arguments to be passed to the function are specified as an array:
|
The trace()
function defined uses the apply()
method instead of a spread operator, and by
doing that, it is able to invoke the wrapped method with the same arguments and the same this value as the wrapper
method
|
The primary purpose of bind()
is to bind a function to an object.
|
|
|
|
|
|
|
3 |
|
|
|
3 // g is still bound to o, not p. |
The most common use case for calling bind()
is to make non-arrow functions behave like arrow
functions.
Partial application is a common technique in functional programming and is sometimes
called currying
.
|
|
|
|
|
3 |
Most (but not all) implementations of this toString()
method return the complete source code
for the function
The Function() constructor is best thought of as a globally scoped version of eval()
that
defines new variables and functions in its own private scope. You will probably never need to use this constructor
in your code.
Skipping
A higher-order function is a function that operates on functions, taking one or more functions as arguments and returning a new function.
|
||
|
A function to determine if a number is even |
|
|
||
|
true |
Returns a new function that maps one array to another
|
||
|
||
|
||
|
|
|
|
[2,3,4] |
Example that takes two functions, f and g, and returns a new function that computes f(g()):
|
||
|
||
|
||
|
25 |
We defined a factorial function that cached its previously computed results. In functional programming, this kind of caching is called memoization.
JavaScript’s classes and prototype-based inheritance mechanism are substantially different from the classes and class-based inheritance mechanism of Java.
If we define a prototype object and then use Object.create()
to create objects that inherit
from it, we have defined a JavaScript class.
Factory function that returns a new range object:
|
||
|
||
|
||
|
true |
|
|
"(1...3)" |
|
|
[1, 2, 3] |
A constructor is a function designed for the initialization of newly created objects.
The critical feature of constructor invocations is that the prototype
property of the
constructor is used as the prototype of the new object.
While almost all objects have a prototype, only a few objects have a prototype
property. It
is function objects that have a prototype
property.
This means that all objects created with the same constructor function inherit from the same object and are therefore members of the same class.
A Range class using a constructor
|
||
|
||
|
||
|
true |
|
|
"(1...3)" |
|
|
[1, 2, 3] |
Because the Range()
constructor is invoked with new
, it does not have to
call Object.create()
or take any action to create a new object.
In the first example, the prototype was range.methods
. This was a convenient and descriptive name, but
arbitrary. In the second example, the prototype is Range.prototype
, and this name is
mandatory.
An invocation of the Range()
constructor automatically uses Range.prototype
as
the prototype of the new Range
object.
Two objects are instances of the same class if and only if they inherit from the same prototype object.
The instanceof
operator is not checking whether r
was actually initialized by
the Range constructor. Instead, it is checking whether r
inherits
from Range.prototype
.
|
|
|
|
true |
If you want to test the prototype chain of an object for a specific prototype and do not want to use the
constructor function as an intermediary, you can use the isPrototypeOf()
method
|
Every regular JavaScript function automatically has a prototype
property. The value of this
property is an object that has a single, non-enumerable constructor
property.
The value of the constructor
property is the function object
|
|
|
|
|
|
|
|
|
|
true |
|
|
|
true |
Instances of the Range class, as defined, do not have a constructor property. We can remedy this problem by explicitly adding a constructor to the prototype:
|
Another common technique that you are likely to see in older JavaScript code is to use the predefined prototype object with its constructor property and add methods to it one at a time with code like this:
|
|
||
|
||
|
true |
|
|
(1...3) |
|
|
[1, 2, 3] |
Although class bodies are superficially similar to object literals, they are not the same thing. In particular, they do not support the definition of properties with name/value pairs.
If your class does not need to do any initialization, you can omit the constructor keyword and its body, and an empty constructor function will be implicitly created for you.
If you want to define a class that subclasses - or inherits from - another class, you can use
the extends
keyword with the class keyword:
|
class declarations have both statement and expression forms
|
|
|
9 |
You can define a static
method within a class body by prefixing the method declaration with
the static
keyword. Static methods are defined as properties of the constructor function
rather than properties of the prototype object.
|
The method defined by this code is Range.parse()
, not Range.prototype.parse()
,
and you must invoke it through the constructor, not through an instance:
|
Within a class body, you can define getter and setter methods just as you can in object literals. The only difference is that in class bodies, you don’t put a comma after the getter or setter.
The ES6 standard only allows the creation of methods (including getters, setters, and generators) and static methods; it does not include syntax for defining fields.
If you want to define a field on a class instance, you must do that in the constructor function or in one of the methods. And if you want to define a static field for a class, you must do that outside the class body, after the class has been defined.
Standardization is underway, however, for extended class syntax that allows the definition of instance and static fields, in both public and private forms.
|
<--> |
|
The same proposal that seeks to standardize these instance fields also defines private (with the # prefix) instance fields.
|
A related proposal seeks to standardize the use of the static
keyword for fields.
|
We can augment JavaScript classes simply by adding new methods to their prototype objects.
|
|
Span subclass of the Range class. This subclass will work just like a Range, but instead of initializing it with a start and an end, we’ll instead specify a start and a distance, or span.
|
Ensure that the Span
prototype inherits from the Range
|
We don't want to inherit Range.prototype.constructor
, so we define our own constructor property:
|
Span
overrides the toString()
method
|
A robust subclassing mechanism needs to allow classes to invoke the methods and constructor of their superclass, but prior to ES6, JavaScript did not have a simple way to do these things.
|
||
|
|
|
|
true |
|
|
true |
|
|
||
|
4 |
|
|
1 |
|
|
3 |
|
|
true |
|
|
true |
|
true |
|
true |
Example demonstrates the use of the super
keyword to invoke the constructor and methods of
the superclass
|
You may not use the this
keyword in your constructor until after you have invoked the
superclass constructor with super()
. This enforces a rule that superclasses get to initialize
themselves before subclasses do.
Once private fields are supported, we could change these properties
to #keyType
and #valueType
so that they could not be altered from the
outside.
Define abstract classes—classes that do not include a complete implementation—to serve as a common superclass for a group of related subclasses.
Imagine a tool that takes a set of files, wraps the content of each of those files within an immediately invoked function expression, keeps track of the return value of each function, and concatenates everything into one big file.
|
writing code like the following to make use of those modules
|
ES6 adds import and export keywords to JavaScript and finally supports real modularity as a core language feature.
ES6 modularity is conceptually the same as Node modularity: each file is its own module, and constants, variables, functions, and classes defined within a file are private to that module unless they are explicitly exported.
To export a constant, variable, function, or class from an ES6 module, simply add the keyword export before the declaration
|
or:
|
It is common to write modules that export only one value (typically a function or class), and in this case, we
usually use export default
instead of export
|
|
|
|
When importing from a module that defines many exports, however, you can easily import everything with an import statement like this:
|
With the wildcard import shown in the previous example, the importing module would use the
imported mean()
and stddev()
functions through the stats object,
invoking them as stats.mean()
and stats.stddev()
.
Note: not finished.
Sets are not ordered or indexed, and they do not allow duplicates.
|
|
|
|
|
The argument to the Set()
constructor need not be an array: any iterable object (including other Set
objects) is allowed.
The add()
method takes a single argument; if you pass an array, it adds the array itself to
the set, not the individual array elements. add()
always returns the set it is invoked on,
however, so if you want to add multiple values to a set, you can used chained method calls like.
it is very important to understand that set membership is based on strict equality checks, like the === operator performs.
The most important thing we do with sets is not to add and remove elements from them, but to check to see whether a specified value is a member of the set:
|
The Set class is iterable, which means that you can use a for/of
loop to enumerate all of
the elements of a set:
|
Because Set objects are iterable, you can convert them to arrays and argument lists with the ... spread operator
|
JavaScript Set class always remembers the order that elements were inserted in, and it always uses this order when you iterate a set: the first element inserted will be the first one iterated (assuming you haven’t deleted it first), and the most recently inserted element will be the last one iterated.
Set class also implements a forEach()
method
|
|
|
|
|
|
map is a set of keys, each of which has an associated value. This is not quite the same as a set of key/value pairs.
use has()
to check whether a map includes the specified key;
use delete()
to remove a key (and its associated value) from the map;
use clear()
to remove all key/value pairs from the map; and use the size property to find out
how many keys a map contains.
set()
method of Map can be chained.
Any JavaScript value can be used as a key or a value in a Map. This includes null, undefined
,
and NaN
, as well as reference types like objects and arrays.
Map compares keys by identity, not by equality.
|
|
|
|
|
Map a different empty object to the number 2. |
|
undefined: |
|
|
|
true |
|
undefined |
Iterate over map:
|
|
|
[["x", 1], ["y", 2]] |
|
Map class iterates in insertion order
If you want to iterate just the keys or just the associated values of a map, use
the keys()
and values()
methods: these return iterable objects that
iterate keys and values, in insertion order. (Theentries()
method returns an iterable object
that iterates key/value pairs, but this is exactly the same as iterating the map directly.)
|
Map objects can also be iterated using the forEach()
|
Note that the value parameter comes before the key parameter.
The WeakMap
class is a variant (but not an actual subclass) of the Map class that does not prevent its
key values from being garbage collected.
WeakMap
keys must be objects or arrays; primitive values are not subject to garbage collection and
cannot be used as keys.
WeakMap implements only the get(), set(),
has(),
and delete()
methods. In particular, WeakMap
is
not iterable and does not define keys(), values(),
or forEach()
. If WeakMap
was iterable, then its keys would be reachable and it wouldn’t be weak.
Similarly, WeakMap does not implement the size property because the size
of a WeakMap could
change at any time as objects are garbage collected
They differ from regular arrays in some very important ways
The elements of a typed array are all numbers. Unlike regular JavaScript numbers, however, typed arrays allow you to specify the type (signed and unsigned integers and IEEE-754 floating point) and size (8 bits to 64 bits) of the numbers to be stored in the array.
You must specify the length of a typed array when you create it, and that length can never change.
The elements of a typed array are always initialized to 0 when the array is created.
|
|
|
|
||||
|
|
|
|||||
|
|
|
|
|
|
|
|
Initialize with values
|
|
|
one more way to create typed arrays that involves the ArrayBuffer
type
|
||
|
1024*1024 |
Typed arrays are not true arrays, but they re-implement most array methods, so you can use them pretty much just like you’d use regular arrays:
|
10 short integers |
|
"9999999999" |
Remember that typed arrays have fixed lengths, so the length property is read-only, and methods that change the
length of the array (such as push(), pop(), unshift(),
shift(),
and splice()
) are not implemented for typed arrays. Methods that alter the
contents of an array without changing the length (such as sort(), reverse()
,
and fill()
) are implemented.
|
You can use the DataView
class, which defines methods for reading and writing values from
an ArrayBuffer
with explicitly specified byte ordering. Refer to book for more examples.
RegExp objects may be created with the RegExp()
constructor, of course, but they are more
often created using a special literal syntax.
|
<--> |
|
Regular expressions can also have one or more flag characters that affect how they work
|
i = case insensitive |
Punctuation characters have special meanings in regular expressions: ^ $ . * + ? = ! : | \ / ( ) [ ] {
}.
Other punctuation characters, such as quotation marks and @, do not have special meaning and simply
match themselves literally in a regular expression.
If you use the RegExp()
constructor, keep in mind that any backslashes in your regular
expression need to be doubled, since strings also use backslashes as an escape character.
Character |
Matches |
---|---|
|
Any one character between the brackets. |
|
Any one character not between the brackets |
|
Any character except newline or another Unicode line terminator. Or, if the |
|
Any ASCII word character. Equivalent to [a-zA-Z0-9_]. |
|
Equivalent to [^a-zA-Z0-9_] |
|
Any Unicode whitespace character. |
|
Any character that is not Unicode whitespace. |
|
Equivalent to [0-9]. |
|
Equivalent to [^0-9]. |
|
A literal backspace (special case). |
|
Any one whitespace character or digit |
REPETITIONS
Character |
Meaning |
---|---|
|
Match the previous item at least n times but no more than m times |
|
Match the previous item n or more times. |
|
Match exactly n occurrences of the previous item. |
|
Equivalent to {0,1}. |
|
Equivalent to {1,} |
* |
Equivalent to {0,}. |
Example |
Description |
|
---|---|---|
|
Match between two and four digits |
|
|
Match exactly three word characters and an optional digit |
|
|
Match "java" with one or more spaces before and after |
|
|
Match zero or more characters that are not open parens |
If you want to match repetitions of more complicated expressions, you’ll need to define a group with parentheses
Be careful when using the * and ? repetition characters. Since these characters may match zero instances of whatever precedes them, they are allowed to match nothing.
It is also possible to specify that repetition should be done in a non-greedy way. Simply follow the repetition
character or characters with a question mark: ??, +?, *?
, or even {1,5}?.
String |
Pattern |
Match |
|||
---|---|---|---|---|---|
|
|
|
|||
|
|
|
Note that using non-greedy repetition may not always produce the results you expect. This is because regular expression pattern matching is done by findingthe first position in the string at which a match is possible. Since a match is possible starting at the first character of the string, shorter matches starting at subsequent characters are never even considered.
Char |
Pattern |
Pattern |
---|---|---|
|
|
“ab” or the string “cd” or the string “ef”. |
|
either three digits or four lowercase letters. |
|
|
matches only the first letter "a" |
|
|
|
matches “java” followed by the optional “script” |
|
matches “java” followed by the optional “script” |
If the left alternative matches, the right alternative is ignored, even if it would have produced a “better” match
Another purpose of parentheses in regular expressions is to define subpatterns within the complete pattern. When a
regular expression is successfully matched against a target string, it is possible to extract the portions of the
target string that matched any particular parenthesized subpattern. For example, suppose you are looking for one or
more lowercase letters followed by one or more digits. You might use the pattern /[a-z]+\d+/
. But
suppose you only really care about the digits at the end of each match. If you put that part of the pattern in
parentheses (/[a-z]+(\d+)/), you can extract the digits from any matches you find,
A related use of parenthesized subexpressions is to allow you to refer back to a subexpression later in the same regular expression. This is done by following a \ character by a digit or digits. The digits refer to the position of the parenthesized subexpression within the regular expression. For example, \1 refers back to the first subexpression, and \3 refers to the third.
Match |
Pattern |
|
---|---|---|
zero or more characters within single or double quotes. However, it does not |
|
|
To require the quotes to match,use a reference |
|
Character |
Meaning |
---|---|
| |
match either the subexpression to the left or the subexpression to the right. |
(...) |
Grouping: group items into a single unit that can be used with *, +, ?, |, and so on. Also remember the characters that match this group for use with later references |
(?:...) |
group items into a single unit, but do not remember the characters that match this group. |
Note (?:...)
syntax:
In pattern "/([Jj]ava(?:[Ss]cript)?)\sis\s(fun\w*)/
" \2
refers to the text
matched by (fun\w*)
since (?:[Ss]cript)?)
in not remembered.
regular expression anchors because they anchor the pattern to a specific position in the search string. The most commonly used anchor elements are ^, which ties the pattern to the beginning of the string, and $, which anchors the pattern to the end of the string.
Example |
Pattern |
---|---|
match the word “JavaScript” on a line by itself |
|
To search for “Java” as a word by itself you can try the pattern /\sJava\s/
, which requires
a space before and after the word. But there are two problems with this solution. First, it does not match “Java” at
the beginning or the end of a string, but only if it appears with space on either side. Second, when this pattern
does find a match, the matched string it returns has leading and trailing spaces, which is not quite what’s needed.
So instead of matching actual space characters with \s, match (or anchor to) word boundaries with \b. The resulting
expression is /\bJava\b/
.
The element \B
anchors the match to a location that is not a word boundary. Thus, the
pattern /\B[Ss]cript/
matches “JavaScript” and “postscript”, but not “script” or “Scripting”.
You can also use arbitrary regular expressions as anchor conditions.
If you include an expression within (?= and )
characters, it is a lookahead assertion, and
it specifies that the enclosed characters must match, without actually matching them.
Example |
Pattern |
Matches |
---|---|---|
to match the name of a common programming language, but only if it is followed by a colon |
|
matches the word “JavaScript” in “JavaScript: The DefinitiveGuide” does not match “Java” in “Java in a Nutshell” |
If you instead introduce an assertion with (?!
, it is a negative lookahead assertion.
Flags are specified after the second / character of a regular expression literal or as a string passed as the second argument to the RegExp() constructor.
Flag |
Meaning |
---|---|
g |
“global”—that is,that we intend to use it to find all matches within a string rather than just finding the
first match.it does alter the behavior of the String |
i |
case-insensitive |
m |
“multiline” mode |
s |
useful when working with text that includes newlines.Normally, a “.” in a regular expression matches any character except a line terminator. When the s flag is used, however, “.” will match any character, including line terminators. |
u |
Unicode. Setting the u flag on a RegExp also allows you to use the new |
y |
“sticky”. should match at the beginning of a string or at the first character following the previous match |
Strings support four methods that use regular expressions.
|
4 |
|
-1 |
search()
does not support global searches; it ignores the g
flag of its
regular expression argument.
REPLACE()
|
No matter how it is capitalized, replace it with the correct capitalization |
parenthesized subexpressions of a regular expression are numbered from left to right and that the regular expression remembers the text that each subexpression matches.
to replace quotation marks in a string with other characters:
|
|
|
'He said «stop»' |
If your RegExp uses named capture groups, then you can refer to the matching text by name rather than by number:
|
|
|
'He said «stop»' |
Instead of passing a replacement string as the second argument to replace(), you can also pass a function that will be invoked to compute the replacement value.
Example to convert decimal integers in a string to hexadecimal:
|
|
|
"f times f is e1" |
|
["7", "8", "15"] |
If the regular expression does not have the g
flag set, match()
does
not do a global search; it simply searches for the first match. In this nonglobal
case, match()
still returns an array, but the array elements are completely different.
Thus, if match()
returns an array a, a[0] contains the complete match, a[1] contains the
substring that matched the first parenthesized expression, and so on.
|
||
|
||
|
"http" |
|
|
||
|
"~david" |
In this non-global case, the array returned by match() also has some object properties in addition to the numbered array elements.
input property refers to the string on which match() was called
The index property is the position within that string at which the match starts.
if the regular expression contains named capture groups, then the returned array also has a groups property whose value is an object.
|
|
|
|
|
|
|
|
|
text |
|
17 |
|
"http" |
|
|
|
"~david" |
There are also important but less dramatic differences in behavior when the y flag is set. Refer to book for examples.
Instead of returning an array of matching substrings like match()
does, however, it returns an
iterator that yields the kind of match objects that match() returns when used with a non-global RegExp.
|
["123", "456","789"] |
|
["1", "2", "3", "4","5"] |
Surprisingly, if you call split()
with a RegExp delimiter and the regular expression
includes capturing groups, then the text that matches the capturing groups will be included in the returned array.
|
|
|
["Testing", "br/","1,2,3"] |
The RegExp()
constructor is useful when a regular expression is being dynamically created
and thus cannot be represented with the regular expression literal syntax.
|
|
|
Returns true or false by calling exec()
.
|
The use of the lastIndex
property with the g and y flags is a particularly awkward part of
this API. When you use these flags, you need to be particularly careful when calling the match(),
exec()
, or test()
methods because the behavior of these methods depends
on lastIndex
, and the value of lastIndex
depends on what you have
previously done with the RegExp object.
To find the index of all <p> tags within a string of HTML text:
|
If the html string contains at least one <p> tag, then it will loop forever. For each iteration of the loop,
we’re creating a new RegExp object with lastIndex
set to 0,
so exec()
always begins at the start of the string, and if there is a match, it will keep
matching over and over. The solution, of course, is to define the RegExp once, and save it to a variable so
that we’re using the same RegExp object for each iteration of the loop.
On the other hand, sometimes reusing a RegExp object is the wrong thing to do. Suppose, for example, that we want to loop through all of the words in a dictionary to find words that contain pairs of double letters.
|
||
|
["apple", "coffee"]: "book" is missing! |
Because we set the g flag on the RegExp, the lastIndex
property is changed after successful
matches, and the test()
method (which is based on exec()
) starts searching
for a match at the position specified by lastIndex
. After matching the “pp” in
“apple”, lastIndex
is 3, and so we start searching the word “book” at position 3 and do not
see the “oo” that it contains.
|
The current time |
|
|
Midnight, January 1st, 1970, GMT |
|
|
Year 2100 January 1st 02:03:04.005, local |
|
|
Midnight in GMT, January 1, 2100 |
If you print a date (with console.log(century), for example), it will, by default, be printed in your local time
zone. If you want to display a date in UTC, you should explicitly convert it to a string
with toUTCString()
or toISOString()
.
if you pass a string to the Date() constructor, it will attempt to parse that string as a date and time specification
|
Once you have a Date object, various get and set methods allow you to query and modify the year, month, day-of-month, hour, minute, second, and millisecond fields of the Date. Each of these methods hastwo forms: one that gets or sets using local time and one that gets or sets using UTC time.
Note that the methods for querying the day-of-month
are getDate()
and getUTCDate()
. The more natural-sounding
functions getDay()
and getUTCDay()
return the day-of-week (0 for
Sunday through 6 for Saturday). The day-of-week is read-only, so there is not a
corresponding setDay()
method.
JavaScript represents dates internally as integers that specify the number of milliseconds since (or before) midnight on January 1, 1970, UTC time.
For any Date object, the getTime()
method returns this internal value, and
the setTime()
method sets it.
|
add 30 secs |
The static Date.now() method returns the current time as a timestamp and is helpful when you want to measure how long your code takes to run
|
adds three months and two weeks to the current date:
|
|
||
|
"Wed Jan 01 2020 17:10:30 GMT-0800 (Pacific Standard Time)" |
|
|
"Thu, 02 Jan 2020 01:10:30 GMT" |
|
|
"1/1/2020": 'en-US' locale |
|
|
"5:10:30 PM": 'en-US' locale |
|
|
"2020-01-02T01:10:30.000Z" |
there is also a static Date.parse() method that takes a string as its argument, attempts to parse it as a date
and time, and returns a timestamp representing that date. Date.parse()
is able to parse the
same strings that the Date()
constructor can and is guaranteed to be able to parse the output
of toISOString(), toUTCString()
, and toString()
.
One good reason to use an Error object is that, when you create an Error, it captures the state of the JavaScript stack, and if the exception is uncaught, the stack trace will be displayed with the error message, which will help you debug the issue.
Error objects have two properties: message
and name
, and
a toString()
method. Node and all modern browsers also define
a stack
property on Error objects.
Subclasses are EvalError, RangeError, ReferenceError, SyntaxError,
TypeError,
and URIError
.
You should feel free to define your own Error subclasses that best encapsulate the error conditions of your own program.
|
||
|
||
|
404 |
|
|
"404 Not Found:http://example.com/" |
|
|
HTTPError |
JavaScript supports JSON serialization and deserialization with the two
functions JSON.stringify()
and JSON.parse().
|
||
|
s == '{"s":"","n":0,"a":[true,false,null]}' |
|
|
copy == {s: "", n: 0, a:[true, false, null]} |
Inefficient way of creating a deep copy of an object
|
Typically, you pass only a single argument
to JSON.stringify()
and JSON.parse()
. Both functions accept an optional
second argument that allows us to extend the JSON format.
JSON.stringify()
also takes an optional third argument. If you would like your JSONformatted
string to be human-readable (if it is being used as a configuration file, for example), then you should pass null as
the second argument and pass a number or string as the third argument. If the third argument is a number, then it
will use that number of spaces for each indentation level. If the third argument is a string of whitespace (such as
'\t'), it will use that string for each level of indent.
If JSON.stringify()
is asked to serialize a value that is not natively supported by the JSON
format, it looks to see if that value has a toJSON()
method, and if so, it calls that method
and then stringifies the return value in place of the original value. Date objects
implement toJSON()
: it returns the same string that toISOString()
method
does.
If you need to re-create Date objects (or modify the parsed object inany other way), you can pass a “reviver”
function as the second argument to JSON.parse()
.
|
Console functions that print their arguments like console.log() have a little-known feature: if the first argument
is a string that includes %s, %i, %d, %f, %o, %O, or %c
, then this first argument is treated as
format string, and the values of subsequent arguments are substituted into the string in place of the two-character
% sequences.
|
||
|
"https://example.com:8000/path/name?q=term#fragment" |
|
|
||
|
"https:" |
|
|
"example.com:8000" |
|
|
||
|
"8000" |
|
|
"/path/name" |
|
|
"?q=term" |
|
|
"#fragment" |
|
|
|
|
Add a path to an API endpoint |
|
|
Add a query parameter |
|
|
One of the important features of the URL class is that it correctly adds punctuation and escapes special characters in URLs when that is needed
|
|
|
|
|
|
|
"/path%20with%20spaces" |
|
|
|
|
|
"?q=foo%23bar" |
|
|
Often, however, HTTP requests encode the values of multiple form fields or multiple API parameters into the query portion of a URL. In this format, the query portion of the URL is a question mark followed by one or more name/value pairs, which are separated from one another by ampersands.
If you want to encode these kinds of name/value pairs into the query portion of a URL, then the searchParams property will be more useful than the search property.
|
|
|
"" |
|
|
|
"?q=term" |
|
|
|
"?q=x" |
|
|
|
"?q=x&opts=1" |
The value of the searchParams property is a URLSearchParams object.
|
|
|
|
|
|
|
|
|
"q=term&opts=exact" |
|
|
|
"http://example.com/?q=term&opts=exact" |
setTimeout()
and setInterval()
—that allow programs to ask the browser to invoke
a function after a specified amount of time has elapsed or to invoke the function repeatedly at a specified
interval.
|
If you want to invoke a function repeatedly, use setInterval()
Both setTimeout()
and setInterval()
return a value. If you save this
value in a variable, you can then use it later to cancel the execution of the function by passing it
to clearTimeout()
or clearInterval()
.
|
||
|
After 10 seconds: stop the repeating code above |
The iterator method of an iterable object does not have a conventional name but uses the Symbol, Symbol.iterator as its name. So a simple for/of loop over an iterable object iterable could also be written the hard way, like this:
|
When you want to iterate though a “partially used” iterator:
|
|
|
head == 1 |
|
tail == [2,3,4,5] |
we will implement the Range class one more time, making it iterable without relying on a generator.
In order to make a class iterable, you must implement a method whose name is the
Symbol Symbol.iterator
|
||
|
Logs numbers 1 to 10 |
|
|
[-2, -1, 0,1, 2] |
In addition to making your classes iterable, it can be quite useful to define functions that return iterable values.
Return an iterable object that iterates the result of applying f()
to each value from
the source iterable
|
||
|
[1, 4, 9, 16] |
Return an iterable object that filters the specified iterable, iterating only those elements for which the predicate returns true
|
||
|
[2,4,6,8,10] |
Particularly useful when the values to be iterated are not the elements of a data structure, but the result of a computation.
To create a generator, you must first define a generator function - defined with the
keyword function*
rather than function
When you invoke a generator function, it does not actually execute the function body, but instead returns a generator object. This generator object is an iterator.
Calling its next()
method causes the body of the generator function to run from the start
(or whatever its current position is) until it reaches a yield
statement.
The value of the yield
statement becomes the value returned by
the next()
call on the iterator.
|
||
|
we get a generator |
|
|
2 |
|
|
3 |
|
|
5 |
|
|
7 |
|
|
true |
Generators have a Symbol.iterator
method to make them iterable
|
|
[2,3,5,7] |
|
|
||
|
17 |
Like regular functions, however, we can also define generators in expression form.
|
||
|
[3, 4, 5] |
In classes and object literals, we can use shorthand notation to omit the function keyword entirely when we define methods.
|
||
|
["x", "y", "z", "g"] |
Generators often make it particularly easy to define iterable classes.
|
Generators are more interesting if they actually generate the values they yield by doing some kind of computation.
generator function that yields Fibonacci numbers
|
If this generator is used with the ... spread operator, it will loop until memory is exhausted and the program crashes.
Use it in a for/of
loop, however
|
||
|
10946 |
This kind of infinite generator becomes more useful with a take()
generator like this
|
||
|
[1, 1, 2, 3, 5] |
Promises
, new in ES6, are objects that represent the not-yet-available result of an asynchronous
operation.
The keywords async
and await
were introduced in ES2017 and provide
new syntax that simplifies asynchronous programming by allowing you to structure your Promise based code as if it
was synchronous.
Asynchronous iterators and the for/await
loop were introduced in ES2018 and allow you to
work with streams of asynchronous events using simple loops that appear synchronous.
|
|
|
|
|
Event-driven JavaScript programs register callback functions for specified types of events in specified contexts, and the web browser invokes those functions whenever the specified events occur.
These callback functions are called event handlers or event listeners, and they are registered
with addEventListener()
Ask the web browser to return an object representing the HTML <button> element that matches this CSS selector:
|
Now register a callback function to be invoked when the user clicks on that button
|
JavaScript running in the browser can fetch data from a web server with code like this:
|
Promises, a core language feature designed to simplify asynchronous programming.
A Promise is an object that represents the result of an asynchronous computation. That result may or may not be ready yet, and the Promise API is intentionally vague about this: there is no way to synchronously get the value of a Promise; you can only ask the Promise to call a callback function when the value is ready.
One real problem with callback-based asynchronous programming is that it is common to end up with callbacks inside callbacks inside callbacks, with lines of code so highly indented that it is difficult to read.
Promises allow this kind of nested callback to be re-expressed as a more linear Promise chain that tends to be easier to read and easier to reason about.
Another problem with callbacks is that they can make handling errors difficult. If an asynchronous function (or an asynchronously invoked callback) throws an exception, there is no way for that exception to propagate back to the initiator of the asynchronous operation. This is a fundamental fact about asynchronous programming: it breaks exception handling. Promises help here by standardizing a way to handle errors and providing a way for errors to propagate correctly through a chain of promises.
Note that Promises represent the future results of single asynchronous computations. They cannot be used to represent repeated asynchronous computations, however.
We can’t use Promises to replace setInterval()
because that function invokes a callback
function repeatedly, which is something that Promises are just not designed to do.
How we would use this Promise returning utility function
|
The Promise object defines a then() instance method. Instead of passing our callback function directly
to getJSON()
, we instead pass it to the then() method. When the HTTP response arrives, the body of
that response is parsed as JSON, and the resulting parsed value is passed to the function that we passed
to then()
.
If you call the then()
method of a Promise object multiple times, each of the functions you
specify will be called when the promised computation is complete.
Unlike many event listeners, though, a Promise represents a single computation, and each function registered with then() will be invoked only once.
|
|
Asynchronous operations, particularly those that involve networking, can typically fail in a number of ways, and robust code has to be written to handle the errors that will inevitably occur.
|
if getJSON()
runs normally, it passes its result to displayUserProfile()
.
If there is an error (the user is not logged in, the server is down, the user’s internet connection dropped, the
request timed out, etc.), then getJSON()
passes an Error object
to handleProfileError()
.
In practice, it is rare to see two functions passed to then(). There is a better and more idiomatic way of handling errors when working with Promises.
To understand it, first consider what happens if getJSON()
completes normally but an error
occurs in displayUserProfile()
. That callback function is invoked asynchronously
when getJSON()
returns, so it is also asynchronous and cannot meaningfully throw an exception
(because there is no code on the call stack to handle it).
|
With this code, a normal result from getJSON()
is still passed
to displayUserProfile()
, but any error in getJSON()
or
in displayUserProfile()
(including any exceptions thrown
by displayUserProfile
) get passed to handleProfileError()
.
One of the most important benefits of Promises is that they provide a natural way to express a sequence of
asynchronous operations as a linear chain of then()
method invocations, without having to
nest each operation within the callback of the previous one.
|
has largely been replaced by the newer, Promise-based Fetch API. In its simplest form, this new HTTP API is just the function fetch(). That promise is fulfilled when the HTTP response begins to arrive and the HTTP status and headers are available.
|
But although the initial Promise is fulfilled, the body of the response may not yet have arrived. So
these text()
and json()
methods for accessing the body of the response
themselves return Promises.
|
There is a second then()
in the chain, which means that the first invocation of
the then()
method must itself return a Promise. That is not how Promises work, however.
When we write a chain of .then()
invocations, we are not registering multiple callbacks on a
single Promise object. Instead, each invocation of the then()
method returns a new Promise
object. That new Promise object is not fulfilled until the function passed to then()
is
complete.
|
There is actually a fourth Promise object involved as which brings up the point of what it means for a Promise to be “resolved.”
fetch() returns a Promise object which, when fulfilled, passes a Response object to the callback function we
register. This Response object has .text(), .json(),
and other methods to request the body of
the HTTP response in various forms. But since the body may not yet have arrived, these methods must return Promise
objects.
“task 2” calls the .json()
method and returns its value. This is the fourth Promise object,
and it is the return value of the callback1()
function.
Let's consider:
|
|
||
|
|
||
|
|
||
|
|
||
|
|
In order for Promise chains to work usefully, the output of task 2 must become the input to task 3. The input to task 3 is the body of the URL that was fetched, parsed as a JSON object. But the return value of callback c1 is not a JSON object, but Promise p4 for that JSON object.
When p1 is fulfilled, c1 is invoked, and task 2 begins. And when p2 is fulfilled, c2 is invoked, and task 3 begins.
And when p2 is fulfilled, c2 is invoked, and task 3 begins. But just because task 2 begins when c1 is invoked,it does not mean that task 2 must end when c1 returns.
Promises are about managing asynchronous tasks, and if task 2 is asynchronous, then that task will not be complete by the time the callback returns.
When you pass a callback c to the then()
method, then()
returns a
Promise p and arranges to asynchronously invoke c at some later time. The callback performs some computation and
returns a value v. When the callback returns, p is resolved with the value v. When a Promise is resolved with a
value that is not itself a Promise, it is immediately fulfilled with that value.
So if c returns a non-Promise, that return value becomes the value of p, p is fulfilled and we are done. But if the return value v is itself a Promise, then p is resolved but not yet fulfilled.
At this stage, p cannot settle until the Promise v settles. If v is fulfilled, then p will be fulfilled to the same value. If v is rejected, then p will be rejected for the same reason. This is what the “resolved” state of a Promise means
the Promise has become associated with, or “locked onto,” another Promise. We don’t know yet whether p will be fulfilled or rejected, but our callback c no longer has any control over that. p is “resolved” in the sense that its fate now depends entirely on what happens to Promise v.
Let’s bring this back to our URL-fetching example. When c1 returns p4, p2 is resolved. But being resolved is not
the same as being fulfilled, so task 3 does not begin yet. When the full body of the HTTP response becomes
available, then the .json()
method can parse it and use that parsed value to fulfill p4. When p4
is fulfilled, p2 is automatically fulfilled as well, with the same parsed JSON value. At this point, the parsed JSON
object is passed to c2, and task 3 begins.
With synchronous code, if you leave out error-handling code, you’ll at least get an exception and a stack trace
that you can use to figure out what is going wrong. With asynchronous code, unhandled exceptions will often go
unreported, and errors can occur silently, making them much harder to debug. The good news is that
the .catch()
method makes it easy to handle errors when working with Promises.
THE CATCH AND FINALLY METHODS
The .catch()
method of a Promise is simply a shorthand way to
call .then()
with null as the first argument and an error-handling callback as the second
argument.
Normal exceptions don’t work with asynchronous code. The .catch()
method of Promises is an
alternative that does work for asynchronous code.
|
p1 is the Promise returned by the p2 is the Promise returned by the first c1 is the callback that we pass to that . p3 is the Promise returned by the second c2 is the callback we pass to that call c3 is the callback that we pass to the |
The first thing that could fail is the fetch() request itself. Let's say p1 was rejected with a NetworkError object.
We didn’t pass an error-handling callback function as the second argument to
the .then()
call, so p2 rejects as well with the same NetworkError object.
Without a handler, though, p2 is rejected, and then p3 is rejected for the same reason.
At this point, the c3 error-handling callback is called, and the NetworkError-specific code within it runs.
There are a couple of things worth noting about this code. First, notice that the error object thrown with a
regular, synchronous throw statement ends up being handled asynchronously with
a .catch()
method invocation in a Promise chain. This should make it clear why this shorthand
method is preferred over passing a second argument to .then()
, and also why it is so idiomatic to end
Promise chains with a .catch()
call.
it is also perfectly valid to use .catch()
elsewhere in a Promise chain. If one of the
stages in your Promise chain can fail with an error, and if the error is some kind of recoverable error that should
not stop the rest of the chain from running, then you can insert a .catch()
call in the
chain, resulting in code that might look like this:
|
If the callback returns normally, then the .catch()
callback will be skipped, and the return
value of the previous callback will become the input to the next .then()
callback.
Once an error has been passed to a .catch()
callback, it stops propagating down the Promise
chain. A .catch()
callback can throw a new error, but if it returns normally, than that
return value is used to resolve and/or fulfill the associated Promise, and
the error stops propagating.
Sometimes, in complex network environments, errors can occur more or less at random, and it can be appropriate to handle those errors by simply retrying the asynchronous request.
|
Sometimes,we want to execute a number of asynchronous operations in parallel. The
function Promise.all()
can do this. Promise.all()
takes an array of
Promise objects as its input and returns a Promise.
The returned Promise will be rejected if any of the input Promises are rejected. Otherwise, it will be fulfilled with an array of the fulfillment values of each of the input Promises.
|
The Promise returned by Promise.all()
rejects when any of the input Promises is rejected. This happens
immediately upon the first rejection and can happen while other input Promises are still pending. In ES2020,
Promise.allSettled()
takes an array of input
Promises and returns a Promise, just like Promise.all()
does. But Promise.allSettled()
never rejects the returned Promise, and it does not fulfill that Promise
until all of the input Promises have settled. The Promise resolves to an array of objects, with one object for each
input Promise. Each of these returned objects has a status property set to “fulfilled” or “rejected.” If the status
is “fulfilled”, then the object will also have a value property that gives the fulfillment value. And if the status
is “rejected”, then the object will also have a reason property that gives the error or rejection value of the
corresponding Promise.
|
Occasionally, you may want to run a number of Promises at once but may only care about the value of the first one
to fulfill. In that case, you can use Promise.race()
instead
of Promise.all()
. It returns a Promise that is fulfilled or rejected when the first of the
Promises in the input array is fulfilled or rejected.
These new keywords dramatically simplify the use of Promises and allow us to write Promise-based, asynchronous code that looks like synchronous code that blocks while waiting for network responses or other asynchronous events.
Asynchronous code can’t return a value or throw an exception the way that regular synchronous code can. And this is why Promises are designed the way the are. The value of a fulfilled Promise is like the return value of a synchronous function. And the value of a rejected Promise is like a value thrown by a synchronous function.
async
and await
take efficient, Promise-based code and hide the Promises
so that your asynchronous code can be as easy to read and as easy to reason about as inefficient, blocking,
synchronous code.
Given a Promise object p, the expression await p waits until p settles. If p fulfills, then the value of await p is the fulfillment value of p. On the other hand, if p is rejected, then the await p expression throws the rejection value of p.
|
It is critical to understand right away that the await
keyword does not cause your program
to block and literally do nothing until the specified Promise settles. The code remains asynchronous, and
the await
simply disguises this fact. This means that any code that uses await is itself
asynchronous.
Because any code that uses await is asynchronous, there is one critical rule: you can only use the await keyword
within functions that have been declared with the async
keyword.
|
Declaring a function async
means that the return value of the function will be a Promise
even if no Promise-related code appears in the body of the function.
The getHighScore()
function is declared async
, so it returns a Promise. And because it
returns a Promise, we can use the await
keyword with it:
|
Suppose that we’ve written our getJSON()
function using async:
|
And now suppose that we want to fetch two JSON values with this function
|
|
|
The problem with this code is that it is unnecessarily sequential: the fetch of the second URL will not begin until the first fetch is complete. If the second URL does not depend on the value obtained from the firstURL, then we should probably try to fetch the two values at the same time.
|
Suppose you have an array of URLs:
|
You can call fetch() on each URL to get an array of Promises:
|
We could now use Promise.all()
to wait for all the Promises in the array to be fulfilled.
But suppose we want the results of the first fetch as soon as they become available and don’t want to wait for all
the URLs to be fetched.
|
<--> |
|
both examples will only work if they are within functions declared async; a for/await
loop
is no different than a regular await expression in that way