Identifiers and Reserved Words
- An identifier is simply a name.
- In JS, identifiers are used to name constants, variables, properties, functions, and classes and to provide labels for certain loops in JS code.
- A JS identifier must begin with a letter, an underscore, or a dollar sign. Digits are not allowed to be the first character.
- Certain reserved words like if, while, for
cannot be used as the name of constants, variables, functions, or classes. But they can be used as names of
properties within an object.
- Others such as from, of, get, set
are used in limited contexts with no syntactic ambiguity and are perfectly legal as identifiers.
- Others like let
can't be fully reserved in order to retain backward compatibility with older programs. let
can be used as a variable name if
declared with var
outside of a class, for example, but not if declared inside a class or with const
.
Some background about Unicode
-
The
Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!).
- The only characters that mattered were good old unaccented English letters, and we had a code for them called ASCII which was able to represent every character using a
number between 32 and 127. Space was 32, the letter "A" was 65, etc. This could conveniently be stored in 7 bits. Most computers in those days were using 8-bit bytes, so not
only could you store every possible ASCII character, but you had a whole bit to spare.
- In the ANSI standard, everybody agreed on what to do below 128, which was pretty much the same as ASCII, but there were lots of different ways to handle the characters from
128 and on up, depending on where you lived. These different systems were called code pages. But still, most people just pretended that a byte was a character and a character
was 8 bits and as long as you never moved a string from one computer to another, or spoke more than one language, it would sort of always work. Luckily, Unicode had been
invented.
- Some people are under the misconception that Unicode is simply a 16-bit code where each character takes 16 bits and therefore there are 65,536 possible characters. This
is not, actually, correct. It is the single most common myth about Unicode. In fact, Unicode has a different way of thinking about characters, and you have to understand the
Unicode way of thinking of things or nothing will make sense.
- Until now, we've assumed that a letter maps to some bits which you can store on disk or in memory: A -> 0100 0001.
- In Unicode, a letter maps to something called a
code point which is still just a theoretical concept. How that code point is represented in memory or on disk is a
whole another story.
- Consider the letter A. Every (platonic, as in unique) letter in every alphabet is assigned a magic number by the Unicode consortium which is written like this: U+0041. You
can find them all by visiting the
Unicode website. There is no real limit on the number of letters that Unicode can define and in fact
they have gone beyond 65,536 so not every unicode letter can really be squeezed into two bytes, but that was a myth anyway.
- OK, so say we have a string:
Hello
which, in Unicode, corresponds to these five code points:
U+0048 U+0065 U+006C U+006C U+006F
. Just a bunch of
code points. Numbers, really. We haven't yet said anything about how to store this in memory or represent it in an email message. That's where
encodings come in.
- Unicode Transformation Format 8-bit (UTF-8) is a
variable-width encoding that can represent every character in the Unicode character set. It was designed for
backward compatibility with ASCII and to avoid the complications of endianness and byte order marks in UTF-16 and UTF-32. UTF-8 is capable of encoding all 1,112,064 valid
Unicode code points using one to four one-byte (8-bit) code units. What the
variable-width means is that characters with code points with small values (like
A
) can be represented with just one byte. In fact, in UTF-8, every code point from 0-127 is stored in a single byte. Only code points 128 and above are stored
using 2, 3, up to 6 bytes (Note: using five- and six-octet sequences are now illegal UTF-8). This enables UTF-8 to be backwards compatible with ASCII.
- For example,
Hello
, which was
U+0048 U+0065 U+006C U+006C U+006F
, will be stored as
48 65 6C 6C 6F
, which, behold! is the same as it
was stored in ASCII, and ANSI, and every OEM character set on the planet. Now, if you are so bold as to use accented letters or Greek letters or Klingon letters, you'll have
to use several bytes to store a single code point.
-
UTF-8 on wikipedia explains with an example how the encoding is actually done. Point 3 has the sentence:
"The two leading zeros are added because a three-byte encoding needs exactly sixteen bits from the code point." What this means is that the number of x's that we need to fill
in the U+0800 to U+FFFF encoding is 16.
- Note: A "character" can take more than 4 bytes because it is made of more than one code point. For instance a national flag character takes 8 bytes since it is "constructed
from a pair of Unicode scalar values" both from outside the
Base Multilingual Plane. (I
think what this means is that each of the two code points that would combine to form a flag representation won't individually be the same as the code point that maps to an
already existing platonic letter. This way when the parser sees this code point, it knows that it has to treat the two code points as a "single character" and thus render it
as expected as a flag. And not as two disjoint platonic letters.) I think this understanding is correct as
this text on Wikipedia explains. Since the ranges for the high surrogates
(0xD800β0xDBFF), low surrogates (0xDC00β0xDFFF), and valid BMP characters (0x0000β0xD7FF, 0xE000β0xFFFF) are disjoint, it is not possible for a surrogate to match a BMP
character, or for two adjacent code units to look like a legal surrogate pair. This simplifies searches a great deal. (But note that surrogate pair is a UTF-16 concept, and
does not apply to UTF-8 (?)).
- Note: Some complex emoji characters can take even more than this; the transgender flag emoji (π³οΈββ§οΈ), which consists of the five-codepoint sequence U+1F3F3 U+FE0F U+200D
U+26A7 U+FE0F, requires sixteen bytes to encode, while that for the flag of Scotland (π΄σ §σ ’σ ³σ £σ ΄σ Ώ) requires a total of twenty-eight bytes for the seven-codepoint sequence U+1F3F4
U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F.
Then there's Zalgo text.
- Uh oh:
JavaScript has a Unicode problem.
-
What's the difference between a
character, a code point, a glyph and a grapheme?
- From the wikipedia definitions:
a)
Code Units: The code unit size is equivalent to the bit measurement for the particular encoding:
- A code unit in US-ASCII consists of 7 bits
- A code unit in UTF-8, EBCDIC and GB 18030 consists of 8 bits
- A code unit in UTF-16 consists of 16 bits
- A code unit in UTF-32 consists of 32 bits
b)
Code Points: A code point is represented by a sequence of code units. The mapping is defined by
the encoding. Thus, the number of code units required to represent a code point depends on the encoding:
- UTF-8: code points map to a sequence of one, two, three or four code units.
- UTF-16: code units are twice as long as 8-bit code units. Therefore, any code point with a scalar value less than U+10000 is encoded with a single code unit. Code points
with a value U+10000 or higher require two code units each. These pairs of code units have a unique term in UTF-16: "
Unicode surrogate pairs". (What this means is that Surrogate Pairs are a term that is
limited to UTF-16)
UTF-32: the 32-bit code unit is large enough that every code point is represented as a single code unit.
-
The Single Most Important Fact About Encodings is
"It does not make sense to have a string without knowing what encoding it uses." How do we
preserve this information about what encoding a string uses? When it comes to html, use the following
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
. But that meta
tag really has to be the very first thing in the <head> section because as soon as the web browser sees this tag itβs going to stop parsing the page and start over
after reinterpreting the whole page using the encoding you specified.
-
codepoints.net helps you find the character corresponding to a particular code point.
Unicode escapes sequences when it comes to JS
- JS defines escapes sequences that allows us to write Unicode characters using only ASCII characters.
- These Unicode escapes begin with the characters
\u
and are either followed by exactly four hexadecimal digits (uisng uppercase or lowercase letters A-F) or by
one to six hexadecimal digits enclosed within curly braces.
Expand Gist
- Note that Unicode allows more than one way of encoding the same character. The string 'Γ©' for example, can be encoded as the single Unicode character
\u00E9
or as the regular ASCII 'e' followed by the acute accent combining mark
\u0301
.
- The Unicode standard defines the preferred encoding for all characters and specifies a normalization procedure to convert text to a canonical form suitable for comparisons.
JavaScript assumes that the source code it is interpreting has already been normalized and does not do any normalization on its own. If you plan to use Unicode characters in
your JavaScript programs, you should ensure that your editor or some other tool performs Unicode normalization of your source code to prevent you from ending up with
different but visually indistinguishable identifiers.
Expand Gist
Overview and Definitions
- JS types can be divided into two categories: primitive tyeps and object types. Primitive values include numbers, strings, and booleans.
- The special JS values null
and undefined
are primitive values but they are not numbers, strings, or booleans.
- ES6 adds a new special-purpose type known as Symbol.
- Any JS value that is not a number, string, boolean, symbol, null
, or undefined
is an object. An object is a collection of properties where
each property has a name and a value (the value can be a primitive value or another object).
- One very special object is the global object.
- JS supports object-oriented programming style. Loosely, this means that rather than having globally defined functions to operate on values of various types, the types
themselves define methods for working with values. For instance, to sort the elements of an array a, we don't pass a to a sort() function, instead we
invoke the sort method on a as a.sort()
.
- Technically it is only JS objects that have methods. But numbers, strings, boolean, and symbol values behave as if they have methods. In JS, null
and
undefined
are the only values that methods cannot be invoked on.
- JS's object types are mutable and its primitive types are immutable.
- JS liberally converts values from one type to another based on some rules. These rules affect its definition of equality, and the ==
operator performs type
conversion along with comparison. In practice, however, the ==
operator is deprecated in favor of the strict equality operator ===
which does no
type conversions.
- JS constants and variables are untyped: declarations do not specify what kind of values will be assigned.
Numbers
- JavaScript represents numbers using the 64-bit floating-point format defined by the IEEE 754 standard, which means it can represent numbers as large as Β±1.7976931348623157
Γ 10
308 and as small as Β±5 Γ 10
-324. IEEE 754 standard is the format for numbers of type
double
in Java, C++, and most modern programming
languages. So the point to note is that the
same datatype is being used to represent both integers as well as floating-point numbers. The mental picture of having
separate representation for
int
and
double
/
float
like in Java is incorrect.
- The JavaScript number format allows you to
exactly represent all integers between β9,007,199,254,740,992 (β2
53) and 9,007,199,254,740,992 (2
53),
inclusive. If you use integer values larger than this, you may lose precision in the trailing digits.
- Note however that certain operations in JS such as array indexing and the bitwise operators are performed with 32-bit integers. What this means is that the maximum number
of elements that you can store in an array is 2
32 - 1 = 4,294,967,295.
Expand Gist
- ES2016 adds
**
for exponentiation. There are functions defined on the
Math Object. The
Math
namespace object contains static
properties and methods for mathematical constants and functions.
Math
works with the
Number
type. It doesn't work with
BigInt
.
- Arithmetic in JS does not raise errors in cases of overflow, underflow, or division by zero.
-
Overflow occurs when the result of a numeric operation is larger than the largest representable number. In this case, the result is a special infinity value,
Infinity
. Similarly, when the absolute value of a negative value becomes larger than the absolute value of the largest representable negative number, the result
is negative infinity,
-Infinity
.
-
Underflow occurs when the result of a numeric operation is closer to zero than the smallest representable number. In this case, JS returns 0. If underflow occurs
from a negative number, JS returns a special value known as "negative zero." This value is almost completely indistinguishable from regular zero and JavaScript programmers
rarely need to detect it.
- Any ways,
here is a SO post answering if +0 and -0 are the same? Note that
+0 === -0
evaluates to true but
Object.is(-0, +0)
evaluates to false.
- Division by zero is not an error in JS, it returns infinity or negative infinity.
- There is one exception, however: zero divided by zero does not have a well-defined value, and the result of this operation is the special not-a-number value
NaN
.
NaN
also arises if you attempt to divide infinity by infinity, take the square root of a negative number, or use arithmetic operators with
non-numeric operands that cannot be converted to numbers.
- The not-a-number value has one unusual feature in JavaScript: it does not compare equal to any other value, including itself. This means that you can't write
x === NaN
to determine whether the value of a variable x is NaN. Instead, you must write
x != x
or
Number.isNaN(x)
. Those expressions
will be true if, and only if, x has the same value as the global constant
NaN
. Note that there is a similar method
isNan() defined on the global object.
You can use either of these.
- A related function
Number.isFinite()
returns true if its argument is a number other than
NaN
,
Infinity
, or
-Infinity
.
- BigInt is a numeric type whose values are integers. The type was added to JavaScript mainly to allow the representation of 64-bit integers, which are required for
compatibility with many other programming languages. BigInt literals are written as a string of digits followed by a lowercase letter
n
. You can use
BigInt()
as a function for converting regular JavaScript numbers or strings to BigInt values. You cannot mix operands of type
BigInt
with regular
numbers. Bitwise operators "generally" work with BigInt operands. None of the functions of the
Math
object accept BigInt operands however.
Dates
- JS defines a
Date object that encapsulates an integral number that
represents milliseconds since the midnight at the beginning of January 1, 1970, UTC (the epoch).
Expand Gist
Text
- A string is an immutable ordered sequence of 16-bit values, each of which typically represents a Unicode character.
- The length of a string is the number of 16-bit values it contains.
- JS uses UTF-16 encoding of the Unicode character set. The most commonly used Unicode characters (those from the "Basic Multilingual Plane") have codepoints that fit in 16
bits and can be represented by one element of a string. Unicode characters whose codepoints do not fit in 16 bits are encoded using the rules of UTF-16 as a sequence (known
as a "surrogate-pair") of two 16-bit values. This means that a JS string of length 2 (two 16-bit values) might represent only a single Unicode character.
- Most string-manipulation methods defined by JavaScript operate on 16-bit values, not characters. They do not treat surrogate pairs specially, they perform no normalization
of the string, and don't even ensure that a string is well-formed UTF-16. In ES6, however, strings are iterable, and if you use the for/of loop or ... operator with a string,
it will iterate the actual characters of the string, not the 16-bit values.
Expand Gist
- Some operations that can be performed with a String:
Expand Gist
Template Literals
- Template literals can include arbitrary JS expressions.
- Everything between the
${...}
is interpreted as a JS expression.
- Another feature of template literals is that if a function name (or "tag") comes right before the opening backtick, then the text and the values of the expressions within
the template literal are passed to the function.
- TODO: Section also goes over Regular Expressions Regex.
Expand Gist
Boolean Values
- Any JS value can be converted to a boolean value.
- The following values convert to, and therefore work like, false:
a)
undefined
b)
null
c) 0
d) -0
f)
""
(the empty string)
- All other values including all objects and arrays convert to, and work like, true.
- For example, you can test explicitly to see if an object
obj is not null by doing
if (obj !== null) {...}
Expand Gist
null
and undefined
- Using the typeof
operator on null
returns the string "object", indicating that null
can be thought of as a special object value that
indicates "no object".
- JS also has a second value that indicates absence of value. The undefined
value represents a deeper kind of absence. It is the value of variables that have not
been initialized and the value you get when you query the vaalue of an object property or array element that does not exist.
- undefined
is also the return value of functions that do not explicitly return a value and the value of function parameters for which no value has been passed.
Symbols
- JS's fundamental
Object
type is an unordered collection of properties, where each property has a name and a value. Property names, until ES6, were exclusively
strings. But in ES6 and later, Symbols can also serve this purpose.
- To obtain a Symbol value, you call the
Symbol()
function. This function never returns the same value twice, even when called with the same argument.
- If you supply a string argument, that string will be included in the output of the Symbol's
toString()
method. Note in the below example that calling
Symbol()
twice with the same stirng produces two completely different Symbol values.
-
Symbol()
never returns the same value twice, but
Symbol.for()
always returns the same value when called with the same string.
Expand Gist
The Global Object
- The
global object is a regular JS object that serves a very important purpose: the properties of this object are the globally defined identifiers that are available
to a JS program. When the JS interpreter starts or whenever a web browser loads a new page, it creates a new global object and gives it an initial set of properties that
define:
a) Global constants like
undefined
,
Infinity
, and
NaN
b) Global functions like
isName()
,
parseInt()
, and
eval()
c) Constructor functions llke
Date()
,
RegExp()
,
String()
,
Object()
,
Array()
d) Global objects like
Math
and
JSON
- In browsers, the
Window
object serves as the global object for all JS code contained in the browser window it represents. This global
Window
object has a self-referential
window
property that can be used to refer to the global object. The
Window
object defines the core global properties,
but it also defines quite a few other globals that are specific to web browsers and client-side JS.
- ES2020 defines
globalThis
as the standard way to refer to the global object in any context (like Node / Web browser / Web Worker Thread).
-
From window docs on MDN.
-
Note in the below example that global variables declared with var
become the properties of the window object. This is not the case
when you declare variables with let
or const
.
We will see more about global variables and how defining them means that they can be accessed
across all
<script>
tags that are associated with that HTML file in "Variable and Constant Scope" section somewhere below.
Expand Gist
Type Conversions
- The below table summarizes how values convert from one type to another in JS. Bold entries in the table highlight conversions that you may find surprising. Empty cells
indicate that no conversion is necessary and none is performed.
Show Image
- JS has two operators that test whether two values are equal. The "strict equality operator"
===
does not consider its operands to be equal if they are not of
the same type.
- Because JS is flexible with type conversions, it also defines the
==
operator with a flexible definition of equality.
- Keep in mind that convertibility of one value to another does not imply equality of those two values.
Expand Gist
- Of course we can always perform explicit type conversions when required.
- The simplest way to perform a type conversion is to use the
Boolean()
,
Number()
, and
String()
functions.
- Certain JS operators perform implicit type conversions and are sometimes used explicitly for the purpose of type conversion. If one operand of the
+
operator
is a string, it converts the other one to a string. The unary
+
operator converts its operand to a number. And the unary
!
operator converts its
operand to a boolean and negates it.
- When it comes to converting strings to numbers, the
parseInt()
and
parseFloat()
offer more flexibility.
Expand Gist
- Sometimes you might have to convert a number to string and you might want to control the number of decimal points that are present in the formatted string. This, and other
such operations, can be done using the following methods.
Expand Gist
Object to Primitive Conversions
- // TODO: If this is your first reading of this chapter, you should feel free to skip ahead to Β§3.10.
Variable declaration and assignment
- In modern JS (ES6 and later), variables are declared with the
let
keyword. It is good programming practice to assign an initial value to your variables when
you declare them when possible.
- If you don't specify an initial value for a variable with the
let
statement, the variable is declared, but its value is
undefined
until your code
assigns a value to it.
- To declare a constant, use
const
.
const
works just like
let
except that you must initialize the constant when you declare it.
Expand Gist
Variable and Constant Scope
- The
scope of a variable is the region of your program source code in which it is defined.
- Variables and constants declared with
let
and
const
are
block scoped. This means that they are only defined within the block of code in
which the
let
or
const
statement appears.
- When a declaration (with any of
let
,
const
, or
var
) appears at the top level, outside of any code blocks, we say it is a
global
variable or constant and has global scope.
- In Node and in client-side JS modules (Chapter 10), the scope of a global variable is the file that it is defined in.
- In traditional client-side JS, however, the scope of a global variable is the HTML document in which it is defined.
-
What this is means is that if one <script>
declares a global variable or constant, that variable or constant is defined (and
can be accessed ) in all of the <script>
elements in that HTML document (or at least all of the scripts that execute after the
let
or const
statement executes).
Show Image
Difference between var
and let
- These differences are more for reference. Pretty much a copy-paste of the section in the book.
- Variables declared with var
do not have block scope. Instead, they are scoped to the body of the containing function no matter how deeply nested they are
inside that function.
- If you use var
outside of a function body, it declares a global variable. But global variables declared with var
differ from globals declared with
let
in an important way. Globals declared with var
are implemented as properties of the global object. The global object can be referenced as
globalThis
. So if you write var x = 2;
outside of a function, it is like you wrote globalThis.x = 2;
. Note however, that the analogy is
not perfect: the properties created with global var
declarations cannot be deleted with the delete
operator. Global variables and
constants declared with let
and const
are not properties of the global object.
- Unlike variables declared with let
, it is legal to declare the same variable multiple times with var
.
- When a variable is declared with var, the declaration is lifted up (or "hoisted") to the top of the enclosing function. The initialization of the variable remains
where you wrote it, but the definition of the variable moves to the top of the function. So variables declared with var can be used, without error, anywhere in the enclosing
function. If the initialization code has not run yet, then the value of the variable may be undefined, but you won't get an error if you use the variable before it is
initialized. This can be a source of bugs and is one of the important misfeatures that let
corrects: if you declare a variable with let
but attempt
to use it before the let statement runs, you will get an actual error instead of just seeing an undefined
value.
- In strict mode, if you attempt to use an undeclared variable, you'll get a reference error when you run your code. Outside of strict mode, however, if you assign a value to
a name that has not been declared with let, const, or var, you'll end up creating a new global variable. It will be a global no matter how deeply nested within functions and
blocks your code is, which is almost certainly not what you want, is bug-prone, and is one of the best reasons for using strict mode! Global variables created in this
accidental way are like global variables declared with var: they define properties of the global object. But unlike the properties defined by proper var declarations, these
properties can be deleted with the delete operator.
Destructuring Assignment
- In a destructuring assignment, the value on the right hand side of the equals sign is an array or object (a "structured" value) and the left hand side specifies one or more
variable names using a syntax that mimics array and object literal syntax.
Expand Gist
Object and Array Initializers
- Object and array initializers are expressions whose value is a newly created object or array.
- An array initializer is a comma-separated list of expressions contained within square brackets.
Expand Gist
- An object initializer expression is like an array initializer expression, but the square brackets are replaced by curly brackets, and each subexpression is prefixed with a
property name and a colon.
Expand Gist
Property Access Expressions
- A
property access expression evaluates to the value of an object property or an array element.
- You can access properties either using
expression . identifier
or
expression [expression]
.
- The difference between the two is that: when using a dot and an identifier, the value of the property name by that identifier is looked up. On the other hand, if [] is
used, the
expression inside the [] is evaluated and converted into a string, and then the value of the property named by that string is looked up. Using the dot
notation is the simpler way, but you can use it only when you already know the name of the property (while writing the program) that you want to access. If the property
contains spaces, or punctuations, or itself is the result of some computation, you will need to use the [] syntax instead.
- As we have seen before, two JS values cannot have properties:
null
and
undefined
. Trying to access properties on either of these, either through
.
or
[]
syntax, causes a
TypeError
to be thrown.
Expand Gist
Conditional Property Access
- ES2020 adds two new kinds of property access expressions:
expression ?. identifier
and
expression ?. [expression]
.
- These are used to guard against the
TypeError
that we get when trying to access properties on
null
or
undefined
.
- Consider the expression
a?.b
.
If a is null
or undefined
, then the expression evaluates to
undefined
without any attempt to access the property
b. If
a is some other valid value, then
a?.b
evaluates to whatever
a.b
would evaluate to. And if
a does not have a property named
b, then the value will again be
undefined
.
- Consider the below example.
a is an object, so
a.b is a valid property access expression. But the value of
a.b is
null
,
so
a.b.c would throw a
TypeError
. By using
?.
instead of
.
we avoid the
TypeError
, and
a.b?.c
evaluates to
undefined
.
- This means that
(a.b?.c).d
will throw a
TypeError
, because the expression attempts to access a property of the value
undefined
.
-
But a.b?.c.d
(without the parentheses) simply evaluates to undefined
and does not throw an error. This is because
property access with ?.
is "short-circuiting": if the subexpression to the left of ?.
evaluates to null
or undefined
, then
the entire expression immediately evaluates to undefined
without any further property access attempts.
- Of course, if
a.b
is an object, and if that object has no property named
c, then
a.b?.c.d
will again throw a
TypeError
which
we can then prevent by using another conditional operator:
a.b?.c?.d
.
Expand Gist
- Conditional property access is also possible using
?.[]
instead of
[]
. For instance, in the expression
a?.[b][c]
, if the value of
a is
null
or
undefined
, then the entire expression immediately evaluates to
undefined
, and the subexpressions
b and
c are never evaluated.
- If either of those expressions have side effects, the side effect will not occur if
a is not defined.
Expand Gist
Conditional Invocations of Functions
- Similar to property access, you can also call a function conditionally.
- For instance, consider
myFunction()
. Here if
myFunction was
null
or
undefined
, a
TypeError
would be thrown.
But if you did
myFunction?.()
, this would evaluate to
undefined
and no exception would be thrown.
- The same short-circuiting rules regarding increment of passed arguments apply as well.
- Consider the following three different cases:
Expand Gist
Some JS Operators
-
delete: remove a property
-
typeof: determine type of operand
-
instanceof: test object class
-
in: test whether property exists
- The / operator divides its first operand by its second. If you are used to programming languages that distinguish between integer and floating point numbers, you might
expect to get an integer result when you divide one integer by another. In JavaScript, however, all numbers are floating-point, so all division operations have floating-point
results: 5/2 evaluates to 2.5, not 2.
- You should almost always be using the strict-equality operators:
===
and
!==
.
When are two values equal when using ===
?
- Two value are equal when comparing with ===
as per the following rules:
a) If the two values have different types, they are not equal
b) If both values are null
or both values are undefined
, they are equal
c) If both values are the boolean value true or both are the boolean value false, they are equal
d) If one or both values is NaN
, they are not equal. (This is surprising, but the NaN value is never equal to any other value, including itself! To check whether
a value x is NaN, use x !== x, or the global isNaN()
function.)
e) If both values are numbers and have the same value, they are equal. If one value is 0 and the other is -0, they are also equal.
f) If both values are strings and contain exactly the same 16-bit values in the same positions, they are equal. Two strings may have the same meaning and the same
visual appearance, but still be encoded using different sequences of 16-bit values. JavaScript performs no Unicode normalization, and a pair of strings like this is not
considered equal to the === or == operators.
g) 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.
- Note that the <= (less than or equal) and >= (greater than or equal) operators do not rely on the equality or strict equality operators for determining whether two values
are "equal." Instead, the less-than-or-equal operator is simply defined as "not greater than," and the greater-than-or-equal operator is defined as "not less than."
The in
operator
- The
in
operator expects a left-side operand that is a string, symbol, or value that can be converted to a string.
- It expects a right-side operand that is an object.
- It evaluates to true if the left-side value is the name of a property of the right-side object.
Expand Gist
The instanceof
operator
- // TODO: In order to understand how the instanceof
operator works, you must understand the "prototype chain."
Logical &&
operator
- The
&&
operator can be understood at three different levels:
a) At the simplest level, when used with boolean operands, && performs the Boolean AND operation on the two values: it returns true if and only if both its first operand and
its second operand are true. If one or both of these operands is false, it returns false.
b) The second level at which && can be understood is as a Boolean AND operator for truthy and falsy values. If both operands are truthy, the operator returns a truthy value.
Otherwise, one or both operands must be falsy, and the operator returns a falsy value. In JavaScript, any expression or statement that expects a boolean value will work with
a truthy or falsy value,
so the fact that && does not always return true or false does not cause practical problems
.
c) Notice that this description says that the operator returns "a truthy value" or "a falsy value" but does not specify what that value is. For that, we need to describe &&
at the third and final level. This operator starts by evaluating its first operand, the expression on its left. If the value on the left is falsy, the value of the entire
expression must also be falsy, so && simply returns the value on the left and does not even evaluate the expression on the right. On the other hand, if the value on the left
is truthy, then the overall value of the expression depends on the value on the right hand side. If the value on the right is truthy, then the overall value must be truthy,
and if the value on the right is falsy, then the overall value must be falsy. So when the value on the left is truthy, the && operator evaluates and returns the value on the
right.
Expand Gist
Logical ||
operator
- Like the
&&
operator discussed above,
||
starts by evaluating its first operand, the expression on its left. If the value of this first operand is
truthy, it short-circuits and returns that truthy value without ever evaluating the expression on the right. If, on the other hand, the value of the first operand is falsy,
then || evaluates its second operand and returns the value of that expression.
Expand Gist
?:
and ??
operators
- Ternary operator:
? :
- First-Defined operator:
??
- Writing
a ?? b
is equivalent to writing
(a !== null && a !== undefined) ? a : b
.
Expand Gist
The delete
operator
-
delete
is a unary operator that attempts to delete the object property or array element specified as its operand.
Expand Gist
Random stuff
- The rule in JS (as in most programming languages) is that by default an else
clause is part of the nearest if
statement.
- Note the difference in behavior of the continue
statement in the while
and for
loops: a while loop returns directly to its condition,
but a for loop first evaluates its increment expression and then returns to its condition. Earlier, we considered the behavior of the for loop in terms of an "equivalent"
while loop. Because the continue statement behaves differently for these two loops, however, it is not actually possible to perfectly simulate a for loop with a while loop
alone.
switch
statement
- The general format of a switch statement looks something like this:
example on
MDN.
- When a switch executes, it computes the value of expression and then looks for a case label whose expression evaluates to the same value (where sameness is determined by
the
===
operator). If it finds one, it starts executing the block of code at the statement labeled by the
case
. If it does not find a
case
with a matching value, it looks for a statement labeled
default:
If there is no
default:
label, the switch statement skips the
block of code altogether.
for/of loop
- This loop works with iterable objects. What constitutes an iterable object will be discussed in Chapter 12. For now, just know that arrays, strings, sets, and maps are
iterable.
- Note that arrays are iterated "live" - changes made during the iteration may affect the outcome of the iteration. If we modified the preceding code by adding the line
data.push(sum);
inside the loop body, then we create an infinite loop because the iteration can never reach the last element of the array.
Expand Gist
- Objects are not by-default iterable. Attempting to use for/of on a regular object throws a TypeError at runtime.
-
Object.keys()
: If you want to iterate through the properties of an object, you can use the for/in loop, or use the for/of with the
Object.keys()
method. This works because
Object.keys()
returns an array of property names for an object, and arrays are iterable with for/of. Note also that this iteration of
the keys of an object is not live as the array example above was - changes to the object
obj made in the loop body will have no effect on the iteration.
-
Object.values()
: Similarly, if you are interested only in the values of the object, you can iterate through the object using this.
-
Object.entries()
: And use this if you are interested in both keys and values of an object's properties.
Object.entries()
returns an array of
arrays, where each inner array represents a key/value pair for one property of the object. We use destructuring assignment in this code example to unpack those inner
arrays into two individual variables.
Expand Gist
for/of with strings
- Strings are iterable character-by-character.
- Note that strings are iterated by Unicode codepoint, not by UTF-16 character. The string "Iπ€π" has a length of 5 because the two emoji characters each require two UTF-16
characters to represent. But if you iterate that string with for/of, the loop body will run 3 times, once for each of the three code points "I", "π€", and "π".
for/of with sets and maps
- The built-in ES6 Set and Map classes are iterable. This is how you would iterate over them
Expand Gist
for/in
- While a for/of loop requires an iterable object after the
of
, a for/in loop works with any object after the
in
. The for/in statement loops through
the property names of a specified object.
- // Something ...
- 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, as in, the various built-in methods and properties defined by core JS are not enumerable (for eg.
toString()
). The bottom line here is
prefer to use a for/of loop with Object.keys()
instead of a for/in loop. When
working with arrays, you almost always want to use for/of instead of for/in.
throw
and try/catch/finally
- An
exception is a signal that indicates that some sort of exceptional condition or error has occurred. To
throw an exception is to signal such an error or
exceptional condition. To
catch an exception is to handle it using a
try/catch/finally
statement.
- The
throw
statement has the following syntax:
throw expresion
where
expression may evaluate to a value of any type. You might decide
to throw a number, or a string, or something else.
- When the JS interpreter itself throws an error, it uses the
Error
class and its subclasses.
- When a exception is thrown, the JS interpreter immediately stops normal program execution and jumps to the nearest exception handler. An exception handler is the
catch
clause of a try/catch/finally block. If the block of code in which the exception was thrown does not have an associated catch clause, the interpreter
checks the next highest enclosing block of code to see if it has an exception handler associated with it. This continues until a handler is found. In this way, exceptions
propagate up through the lexical structure of JS methods and up the call stack. If no exception handler is ever found, the exception is treated as an error and is reported to
the user.
- The following code illustrates the syntax and purpose of the
try/catch/finally
statement:
Expand Gist