Declarations.
Problems because of automatic conversions.
Automatic semi-colon insertion problems.
Source: reddit.
Variables can be declared using var
or the newer let
.
Note that declaration is simply var x = 1
or let x = 1
;
there is no type as js variables do not have types.
Constant variables (cannot be assigned after initialization)
are declared const
. Note that if a const
variable
contains an object, then the insides of that object can
still be changed (unless it is dynamically frozen using
Object.freeze()).
Every js system has an implicit global object: window
in a
browser, the current module in a nodejs script. If an undeclared
variable is assigned to, then it is created in this global object.
Can be avoided using "use strict"
.
Variables have lexical scope; i.e. their scope is limited to the syntactic construct within which they are declared.
All variables declared using var
are implicitly hoisted
as though they were declared at the start of the containing
function. (Note: logs edited for readability).
> var x = 3 undefined > function f(a) { var y = 5; if (a > 1) { var x = y; } return x + y; } undefined > f(2) 10 >
The var x
declaration in the previous function is hoisted to the
start of f()
. Hence the scope of all variables declared using
var
within a function is the entire function, irrespective of
the point where the variable was actually declared.
function f(a) { var x; var y = 5; if (a > 1) { x = y; } return x + y; }
This behavior can be prevented by using let
.
> var x = 3; undefined > function f_let(a) { let y = 5; if (a > 1) { let x = y; } return x + y; } undefined > f_let(2) 8
Behavior of let
has fewer surprises; prefer let
over var
in
new code.
> var x = 1 undefined > function f(a) { //var x effectively declared here x = 2; if (a) { var x = 3 } //declaration hoisted return x; } undefined > f(1) 3 > f(0) 2 > x 1 >
> let x = 1 > function f(a) { x = 2; if (a) { let x = 3 } //{...} block effectively a NOP return x; } > x 1 > f(1) 2 > x 2 > f(0) 2 > x 2 >
A let
declaration takes effect at the start of the block in
which it occurs. Leads to temporal dead zones.
> a = 1 1 > function f() { //let a at this point let b = a + 2; //not external a let a = 5; return b + a; } undefined > f() ReferenceError: a is not defined at f (repl:1:24) >
My order of descending preference: const
, let
, var
.
Convention is to use all uppercase names for manifest constants.
Many JS programs have multiple declarations using a single specifier:
let var1 = value1, var2 = value2, ... varN = valueN;
I consider that error prone and would prefer that each declaration
stand alone using its own let
specifier. Prefer destructuring
declaration using array literal notation on both sides as in
let [var1, var2, ..., varN] = [value1, value2, ..., valueN];
If you assign to an undeclared variable, then that variable will be
created as a property of the global object. Force error by always
specifying "use strict"
within scripts. Not an issue for modules
which are implicitly "use strict"
.
The scope of all JavaScript declarations are hoisted to start of a syntactic construct.
var
declarations are hoisted to start of containing function;
let
and const
declarations are hoisted to start of containing
block or loop.
Use of var
variable within function before initialization results
in undefined
.
Use of const
or let
variable within scope before
declaration results in ReferenceError
because of
temporal dead-zone.
Behavior of const
and let
less surprising because of
smaller scope.
Avoid var
in new code; use const
and let
.
Misfeature inherited from C.
Integer literal starting with 0
treated as octal number when it
does not contain any non-octal digits.
Integer literals with leading 0 cause error when 'use strict'
in
effect.
> 077 63 > 078 //non-octal digit results in silent base-10 78 > 0o77 //newer syntax 63 > 0o78 //non-octal digits cause error 0o78 ^^ Uncaught SyntaxError: Invalid or unexpected token
When used without new
, Number()
, String()
, Boolean()
can be
used to explicitly convert between primitives. Recommended.
> Number('3') 3 > Number(false) 0 > Number(undefined) NaN > Number(null) 0 > Number(true) 1 > String(true) 'true' > String(3+1) '4' > String(undefined) 'undefined'
$ perl -de1 #crude perl REPL Loading DB routines from perl5db.pl version 1.51 ... DB<1> print 1 + '2' 3 $ node #js REPL > 1 + '2' '12' $ python3 #python3 REPL Python 3.8.10 (default, Jun 22 2022, 20:18:18) ... >>> 1 + '2' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str' $ irb #ruby REPL > 1 + '2' ... TypeError (String can't be coerced into Integer) $
The fact that 0
, ""
and NaN
are treated as falsy values
within boolean contexts can often cause surprises.
Never use x === NaN
(in any language); use isNaN(x)
instead.
Very complex conversion rules; best to avoid in new code, but need to handle legacy code.
Operators where conversions occur include +
(both prefix and
infix), -
(both prefix and infix), other arith/relational ops.
+
is used for both strings (concatenation) and numbers
(addition). If either operand is a string then we are doing
concatenation.
> 1 + '2' '12' > '2' * 3 6 > false * 6 0 > null + 5 5 > undefined * 4 NaN > true * '5' 5 > + '123' //Number to String idiom; prefer Number('123') 123
> 1 + 2 + "3" + 4 //left-assoc +: ((1 + 2) + "3") + 4 '334' > a = '1' '1' > a = a + 3 + 6 //concat "3" + "6" to a. '136' > a += 3 + 6 //numeric add 3 + 6 '1369' >
Arithmetic and concatenation expressions are evaluated using primitive
operands. Specifically, if we are looking for a primitive operand as a
Number
:
If operand is primitive, then nothing needs to be done.
If operand is an object obj
and obj.valueOf()
returns a primitive object, then return that primitive
object.
If operand is an object obj
and obj.toString()
returns a primitive object, then return that primitive
object.
Otherwise throw a typeerror
.
If we are looking for a primitive operand as a String
,
then interchange steps 2 and 3.
> x = { toString: function() { return "5"; }, ... valueOf: function() { return 2; } } { [Number: 2] toString: [Function: toString], valueOf: [Function: valueOf] } > x + 3 5 > x + '3' //+ calls valueOf() first for both operands '23' > String(x) '5' >
js has both ==
and ===
operators along with corresponding
!=
and !==
operators.
Loose equality operator ==
tries to convert its operands to
the same type before comparison.
Strict equality operator ===
does not do type conversion; simply
returns false
if types are different.
Almost always use ===
and !==
; do not use ==
or !=
.
Do a google search on js wtf
.
> '1' == 1 true > '1' === 1 false > undefined == null true > undefined === null false > '' == 0 true > 0 == '0' true > '' == '0' false //breaks transitivity >
Braces have two purposes within JavaScript syntax:
Serve to delimit object literals. Braces are treated as object literal delimiters when they occur in an expression context.
Serve to delimit code blocks. Braces are treated as code block delimiters when they are in a non-expression context.
When braces occur in an ambiguous context, they are always treated as code block delimiters.
For example, an attempt to write an anonymous function x => {
value: x }
to wrap parameter x
in an object is wrong, since
the { }
are treated as code delimiters. The function should be
rewritten as x => ({ value: x })
instead.
Automatic Semicolon Insertion (ASI):
Insert semicolon at newline if that fixes syntax error.
Always insert semicolon after return
, break
, continue
when followed
by a newline.
Always insert semicolon if next line starts with ++
or --
.
> function f() { return 5 + 3 } undefined > f() 8
Can cause problems:
> function f() { //silently returns undefined return { //start of unreachable code block a: //label! false //expression statement } } undefined > f() undefined
The primitive types string, number, boolean can be wrapped as objects
using constructors new String()
, new Number()
, new Boolean()
.
Wrapping and unwrapping are done automatically as needed.
> x = new Number(3) //use wrapper constr [Number: 3] > typeof x //x is an object 'object' > typeof 3 'number' > x + 1 //automatically unwrapped 4 > x.a = 2 //object property assign 2 > x.a + x //property + unwrap 5
We can even define properties on primitive literals, but not of much use.
> 3.x = 22 3.x = 22 ^^ SyntaxError: Invalid or unexpected token > > 3.0.x = 22 22 > 3.0.x undefined >
Wrapper object automatically created, but since we do not retain
a reference to it, we cannot access it. This behavior turned off
in strict
mode.
eval(code)
compiles and executes String code
.
Can be a tremendous security risk if code
depends on an
untrusted source.
Normally, it runs in the local scope, but runs in global scope if called indirectly.
Note that the Function()
constructor provides a similar
capability which is somewhat more secure.
eval()
To Implement a Spreadsheetss-using-eval.html. Running App.
No error checking.
Cell values maintained in global window
variables.
eval()
used for evaluating spreadsheet formulas.
Since there is no validation on formula strings, it is clear that this can be a security problem if used within a sensitive context.
with
StatementA with
statement:
with (expression) statement
executes statement
with the object specified by expression
interposed into the scope chain.
Not recommended; forbidden when strict
which means it
cannot be used in ES6 modules (which are always strict
).
Can degrade performance.
Instead of with
assign result of expression
to
a temporary object and replace references to "variables"
in
statement
to property references on the temporary
object.
eval()
and with
To Implement a Spreadsheetss-using-eval-and-with.html; Running App.
No error checking.
Cell values are not maintained in global window
variables;
instead they are maintained in a values
object.
When eval()
is used for evaluating a spreadsheet formula,
the eval()
is done using with (values) eval(formula)
.