This is a JavaScript guide for developers who already knew other programming languages and want to know JavaScript systematically and quickly.
What it is
- Untyped, prototype-based, single-threaded scripting language;
- Native Unicode (UTF-16);
- Case-sensitive;
- First-class functions (support functional programming styles);
- A minimal API for working with text, arrays, dates, and regular expressions. But no Input/Output, networking, storage, and graphics, these are supported by web browsers;
Identifiers
Begin with letter, _
, $
, then followed by letters, digits, _
, $
.
Comments
//
, for single line;/* ... */
, for single line or multiple lines;
Statements
The semicolon (;
) is used to separate statements. It is optional, but no surprising cases when using it.
Data types
Primitive types
- number
- string
- boolean
- null
- undefined
- Symbol
- BigInt
They are immutable and compared by value.
Object types
Arrays and functions are special objects.
They are mutable and compared by reference.
number
Array indexes and the bitwise operators’ operands are 32-bit integers, all other numbers are represented as 64-bit binary floating-point values(IEEE-754).
These floating-point values can exactly represent 1/2
, 1/8
, …, 1/2^n
, but can’t exactly represent 1/10
, 1/100
, they are decimal fractions.
x = .3 -.2 // => 0.09999999999999998
y = .2 -.1 // => 0.1
x == y // => false
100 // base-10 integer
0xff/0Xff // hexadecimal integer
Arithmetic operators:
+
,-
,*
,/
,%
,**
,++
,--
;- Math object:
Math.log(100)
;
Doesn’t raise overflow, underflow, division by zero errors, but the arithmetic results are special values:
- Overflow: Infinity / -Infinity;
- Underflow: 0 / -0;
- Division by zero: Infinity / -Infinity, but 0 / 0 is NaN(not a number);
Infinity
, NaN
are global variables. Infinity
represents infinity. NaN
represents not-a-number.
To check x
is NaN
: x != x
, it is true if only if x
is NaN
, don’t use x == NaN
.
string
"abcde"
'abcde'
"ab'cd'e"
'ab"cd"e'
'can\'t'
'one \
very \
long \
line'
Enclose a string with single or double quotes ('
or "
). When combining JavaScript and HTML, one style of quotes for JavaScript, the other for HTML.
A string likes a read-only array, could use indexes to access.
Template strings
A template string is enclosed by the back-tick (``). The dollar sign and curly braces (${expression}
) indicate a placeholder.
const name = 'JavaScript';
const msg = `Hi, ${name}`; // a template string
console.log(msg); // => Hi, JavaScript
Tagged templates
function tag(strings, place) {
// => 2 Hey, where are you?\n <empty string>
console.log(strings.length, strings.raw[0], strings.raw[1]);
/* =>
* 2 Hey, where are you?
* <empty string>
*/
console.log(strings.length, strings[0], strings[1]);
/* =>
* Hey, where are you?
* moon
*/
console.log(`${strings[0]}${place}`);
}
const place = 'moon';
tag`Hey, where are you?\n${place}`;
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
boolean
Any values can be converted to a boolean value: undefined
, null
, 0
, -0
, NaN
, ""
are converted to false
, others are true
.
null
null
is an object, indicates “no value”.
typeof(null) // => "object"
undefined
typeof(undefined) // => "undefined"
null == undefined // => true
Get an undefined
value when accessing an uninitialized variable, a non-existed object property or array element, a function argument that you don’t supply a value, the return value of a function that returns nothing.
undefined
is an error-like absence of value. If you want a variable that hasn’t any value, assign null
to it.
Wrapper objects
Numbers, strings, booleans are not objects, but they have properties and methods, because of JavaScript converts them to an object when to refer a property value or invoke a method. JavaScript will discard the wrapper object soon.
String()
, Number()
, Boolean()
return wrapper objects.
const x = new Number(123)
x == 123 // => true, only compare the value
x === 123 // => false, compare the value and the type
Variables
Variable declaration
var
var
declares a local or global variable, optionally initializing it to a value.
var i;
i = 1;
var j, k;
var m = 0;
var n = 'a', p = '1';
for ( var x = 0; x < 100; x++) {};
y = 1; // automatically create a global variable y
Actually there is a global object, we can refer to it outside functions using this
keyword. We define a property of the global object when declaring a global variable with var
(outside functions).
var globalVar = 1;
this.globalVar; // => 1
// JavaScript's built-in global variables
this.NaN; // => NaN
this.undefined; // => undefined
let
let
declares a block-scoped variable, optionally initializing it to a value. Likes var
, the most important difference is the scope.
const
const
declares a block-scoped constant. The constant value can’t be changed via reassignment.
const name1 = value1 [, name2 = value2 [, ... [, nameN = valueN]]];
nameN
can be any legal identifier.
valueN
can be any legal expression, including a function expression.
const outsideConstant = 1; // outside functions or blocks
// not a property of the global object, unlike the global variables declared by var.
console.log(this.outsideConstant); // => undefined
{
/* => ReferenceError: can't access lexical declaration `i' before initialization
* Unlike var, the variables declared by var don't raise a ReferenceError, they are undefined.
*/
// console.log(i);
/* => SyntaxError: missing = in const declaration
* Constants must be initialized.
*/
// const i;
const i = 1;
/* => SyntaxError: redeclaration of const i
* Can't be redeclared, unlike the variables declared by var.
*/
// const i = '1';
/* => TypeError: invalid assignment to const `i'
* Constants can't be reassigned to a new value.
*/
// i = 2;
// obj has a read-only reference to {x: 1, y: 2}.
const obj = {x: 1, y: 2};
// but can change its properties.
obj.x = 2;
console.log(obj); // => Object { x: 2, y: 2 }
const arr = [];
arr[0] = 1;
arr[1] = 2;
console.log(arr); // => Array [ 1, 2 ]
}
How to choose:
let
/const
vs var
:
- Can’t be redeclared;
- Declared variables outside functions or blocks aren’t a property of the global object;
- Can’t be used before declaring it;
const
vs let
:
- Must be initialized;
- Can’t be reassigned;
If the value of a variable won’t be changed, use const
, otherwise use let
whenever possible.
Untyped variables
let x = 'a string';
x = 1; // now becomes a number
Variable scope
Available scopes:
- block
- local
- global
The following sample code uses var
to declare variables, it is more complicated.
// define global variables
var x = 'global variable: x';
var y = 'global variable: y';
function testScope( k /* a local variable */ ) {
// x is the above x, is global, not local
x = 'changed the global variable: x';
// automatically create a global variable z
z = 'new global variable: z';
// define a local variable
var localVar = 'local variable: localVar';
console.log('y:', y); // => y: undefined
if(k){
// define a local variable and hide the global one that has the same name
var y = 'local variable: y';
// OR
// var y;
}
}
testScope();
console.log(x); // => changed the global variable: x
console.log(y); // => global variable: y
Function parameters are local variables, like k
.
Local variables that are within function body must always use the var
/let
/const
statement to declare, don’t ignore them, otherwise the variable is global, like z
.
x
, y
(outside testScope
), z
are global variables.
Could declare local variables anywhere within a function, all of them have a same scope, like localVar
, y
(declared in testScope
).
All variables declared within a function are visible everywhere in the function, even places before they are declared. like y
:
- Line 15, at first glance, should be
y: global variable: y
. But the localy
already hides the global one, even use it before declaring it(line 15, line 19). The local one hides the global one throughout the function; - The code won’t go to line 19, but the declaration of
y
is still available;
Declaring all variables at the top of the function is obvious to reflect their scope.
var x = 1;
{
var x = 2;
}
console.log(x); // => 2
x
in the code block doesn’t hide the global one. Could redeclare variables declared by var
.
Objects
Create new objects
- Using object initializers
// empty object, no properties
let obj = {};
// add properties later
obj.x = 1;
obj.y = 2;
const obj = {
name: 'obj',
nestedObj: {x: 1, y: 2},
foo: function() {
console.log('foo');
},
bar() {
console.log('bar is an ES6 shorthand method');
},
get z() { // z's getter function
return this._z;
},
set z(val) { // z's setter function
this._z = val;
}
}
obj.z
calls z
‘s getter function. obj.z = 1
calls z
‘s setter function. More details in the following Getters/Setters section.
const x = 1, y = 'a', z = "zz";
/* x, y are ES6 shorthand property names.
* zz is an ES6 computed property name. The value in brackets [] is a property name.
*/
const obj = {x, y, [z]: 2};
console.log(obj); // => Object { x: 1, y: "a", zz: 2 }
// use the spread operator(...) to copy properties
const clonedObj = { ...obj };
console.log(clonedObj); // => Object { x: 1, y: "a", zz: 2 }
More details about the spread operator in Operators -> Spread operator section.
- Using a constructor function
// empty object, call the built-in constructor. Same as {}
let obj = new Object();
obj.x = 1;
obj.y = 2;
const anotherObj = new Object({x: 1, y: 2});
const dateObj = new Date();
// 1. write a constructor function
function Car(model) {
this.model = model;
}
// 2. call the new operator to create an instance
const myCar = new Car('Tesla');
The constructor functions have statements like line 12, to define its own properties, not global variables.
- Using the
Object.create
method
Object.create
creates a new object, using an existing object as the prototype of the newly created object.
const foo = {x: 1, y: 2}
const bar = Object.create(foo);
console.log(bar.x); // => 1
const nullObj = Object.create(null)
foo
is bar
‘s prototype object.
nullObj
hasn’t a prototype object. It is an empty object, but doesn’t inherit anything, even basic methods, like toString()
.
Accessing properties
let obj = {}; // empty object
const obj2 = new Object(); // same as obj
const obj3 = new Object; // same as obj
const obj4 = {a: 1, b: 2};
obj.a = 1;
obj.b = 2; // now obj has the same properties as obj4
// nested, simply think a simple object is a dictionary
const obj5 = {a: {x: 1, y: 2}, 'b': 2, 3: 'c'};
obj5[a]; // ReferenceError: a is not defined
obj5['a']; // => Object { x: 1, y: 2 }
obj5.b; // => 2
obj5[3]; // => 'c', implicitly convert 3 to '3'
obj5['3']; // => 'c'
obj5.3; // => SyntaxError: unexpected token: numeric literal
- Use identifier
expression.identifier
The property name must be a legal identifier, otherwise use the property name to access, like line 16, 17.
Identifiers must be typed literally. If the property name is determined at runtime, use the name to access it.
- Use name
expression [ expression ]
Property names are strings, including the empty string. The value of the second expression is converted to a string as the property name, like line 15, 16.
const xo = { a: 1 }
const yo = { a: 1 }
const zo = yo
xo == yo // => false
zo == yo // => true
Assigning an object to a variable simply assigns the reference to the variable.
Getters/Setters
A getter is a method that gets the value of a specific property.
A setter is a method that sets the value of a specific property.
- Defined by object initializers
let obj = {
get x() { return 1 },
set y(val) { this._y = val },
get z() { return this._z },
set z(val) { this._z = val },
}
// no setter, read-only
obj.x = 0; // do nothing
console.log('obj.x =', obj.x); // => obj.x = 1
// no getter, write-only
console.log('obj.y =', obj.y); // => obj.y = undefined
// read/write
obj.z = 0;
console.log('obj.z =', obj.z); // => obj.z = 0
- Defined by
Object.defineProperty()
orObject.defineProperties()
let obj = {};
Object.defineProperty(obj, 'x', {
get: function() { return 1 }
});
Object.defineProperties(obj, {
'y': {
set: function(val) { this._y = val }
},
'z': {
get: function() { return this._z },
set: function(val) { this._z = val }
}
});
// no setter, read-only
obj.x = 0; // do nothing
console.log('obj.x =', obj.x); // => obj.x = 1
// no getter, write-only
console.log('obj.y =', obj.y); // => obj.y = undefined
// read/write
obj.z = 0;
console.log('obj.z =', obj.z); // => obj.z = 0
Prototype and inheritance
All constructor functions have a prototype
property, the property is a template object for storing properties and methods that is used to build the __proto__
property of a new object when creating the object with the new
operator, making the new object is an instance of the current object. Other normal objects haven’t it unless setting it manually, but this makes no sense, normal objects haven’t a constructor function to new an instance.
Any object has the __proto__
property, or the object returned by Object.getPrototypeOf()
. The property is a part of the prototype chain, used to look up properties and methods.
A fairly common pattern for more object definitions is to define the properties inside the constructor, and the methods on the prototype object. If defining a method in the constructor, the method would be different for every object creation, might have performance issues, more details here.
function Foo() {
this.x = 1;
console.log('Foo constructor');
}
let obj = new Foo
actually does the following:
let obj = new Object();
obj.__proto__ = Foo.prototype;
Foo.call(obj);
Foo.call(obj)
makes the this
within Foo()
to point to obj
. All properties and methods defined with this.
belong to obj
now, obj
inherits Foo
.
console.log(Object.getPrototypeOf(obj) === Foo.prototype); // => true
console.log(Object.getPrototypeOf(obj) === obj.__proto__); // => true
console.log(obj instanceof Foo); // => true
When looking up the properties or methods of obj
, the lookup chain is obj
-> obj.__proto__
(Foo.prototype
) -> Foo.prototype.__proto__
(Object.prototype
) -> Object.prototype.__proto__
(null
).
At this time, Foo.prototype
is an empty normal object, we didn’t add any property or method. It likes an object created by new Object
, so its prototype is Object.prototype
.
Foo.prototype
isn’t Foo
, it is Foo
‘s template object used to be inherited by other objects. If you don’t care about performance, put all properties and methods into Foo
, that’s OK.
function Bar() {
this.y = 2;
// Foo.call(this); // don't inherit Foo.x
console.log('Bar constructor');
}
function AnotherBar() {
this.z = 3;
// Like a C++'s subclass calls its superclass's constructor during initializing the class.
Foo.call(this); // inherit Foo.x
console.log('AnotherBar constructor');
}
// Bar inherits Foo
Bar.prototype = Object.create(Foo.prototype);
// Bar.prototype.constructor(); // => "Foo constructor", not Bar()
Bar.prototype.constructor = Bar; // reset the constructor
// AnotherBar inherits Foo
AnotherBar.prototype = Object.create(Foo.prototype);
AnotherBar.prototype.constructor = AnotherBar;
console.log(Bar.prototype === AnotherBar.prototype); // => false
// => Bar constructor
obj = new Bar; // don't call Foo()
/* => Foo constructor
* AnotherBar constructor
*/
const anotherObj = new AnotherBar; // call Foo()
// => true undefined
console.log(obj.__proto__ === Bar.prototype, obj.x);
// => true 1
console.log(anotherObj.__proto__ === AnotherBar.prototype, anotherObj.x);
let foo = {
x: 1,
y: 2,
get z() { // z's getter function
return this._z;
},
set z(val) { // z's setter function
this._z = val;
this.m = 1;
}
};
let bar = Object.create(foo); // foo is bar's prototype object
bar.x = 2; // create a new bar.x, not to change foo.x
console.log('bar.x =', bar.x); // => bar.x = 2
console.log('foo.x =', foo.x); // => foo.x = 1, no change
console.log('bar.z =', bar.z); // => bar.z = undefined
console.log('foo.z =', foo.z); // => foo.z = undefined
// z has a setter, don't create a new bar.z, call foo.z(val)
bar.z = 1;
console.log('bar.z =', bar.z); // => bar.z = 1
console.log('foo.z =', foo.z); // => foo.z = undefined
// m is created in foo.z(val), but it belongs to bar, not foo
console.log('bar.m =', bar.m); // => bar.m = 1
console.log('foo.m =', foo.m); // => foo.m = undefined
foo.sharedProp = 'shared';
const anotherBar = Object.create(foo);
console.log(bar.sharedProp, anotherBar.sharedProp); // => shared shared
// bar is a normal object, not a constructor function
console.log(bar.prototype === undefined); // => true
The difference between the new
operator and Object.create()
:
function Foo() {
this.x = 1;
}
const obj = new Foo();
console.log(obj.x); // => 1, inherit x
const anotherObj = Object.create(Foo);
console.log(anotherObj.x); // => undefined, don't inherit x
The new
operator calls the constructor function, but Object.create
doesn’t.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object_prototypes
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Inheritance
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model
About multiple inheritance here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Details_of_the_Object_Model#No_multiple_inheritance
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Classical_inheritance_with_Object.create
Object attributes
Each object has three attributes:
prototype
, a reference to the inherited object
const obj={};
console.log(Object.prototype.isPrototypeOf(obj)); // => true
const anotherObj = Object.create(obj);
console.log(obj.isPrototypeOf(anotherObj)); // => true
function CustomObject() {};
CustomObject.prototype = obj;
const myObj = new CustomObject();
console.log(obj.isPrototypeOf(myObj)); // => true
const arr = new Array();
console.log(Object.getPrototypeOf(arr) === Array.prototype); // => true
- class, a string that categorizes the type of the object
Object.prototype.toString.call(1); // => "[object Number]"
Object.prototype.toString.call([]); // => "[object Array]"
Object.prototype.toString.call(''); // => "[object String]"
function CustomConstructor() {};
Object.prototype.toString.call(new CustomConstructor()); // => "[object Object]"
No way to set the attribute. Object.prototype.toString()
is the only way to query it.
In [object Number]
, Number
is the class name.
- extensible, whether new properties can be added
let obj = {};
Object.isExtensible(obj); // => true
Object.preventExtensions(obj);
Object.isExtensible(obj); // => false, can't make it extensible again
obj.x = 1; // do nothing
obj.x === undefined; // => true
// The preventExtensions function only affects the object itself
Object.prototype.myVar = 'inherited';
obj.myVar; // => "inherited"
Property attributes
Each property has three attributes:
writable
. Whether the value can be set;enumerable
. Whether the name is returned byfor...in
loop. Properties created by normal JavaScript code are enumerable unless you change them explicitly. More details here;configurable
. Whether can be deleted or the property descriptor can be altered;
let obj = { x: 1 };
// An object called property descriptor represents the four attributes
// => Object { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(obj, 'x');
// make 'x' non-writable
Object.defineProperty(obj, "x", { writable: false });
obj.x = 2; // do nothing
console.log('obj.x =', obj.x); // => obj.x = 1
// but 'x' is still configurable
Object.defineProperty(obj, "x", { value: 2 });
console.log('obj.x =', obj.x); // => obj.x = 2
Normally use configurable
and writable
attributes with extensible
object attribute to lock down an object.
let obj = {
x: 1,
get y() { return this._y },
set y(val) { this._y = val },
get z() { return this._z },
set z(val) { this._z = val },
nestedObj: {}
};
obj.y = 2;
// => Object { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(obj, 'x');
// Object { get: y(), set: y(), enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(obj, 'y');
// Object { get: z(), set: z(), enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(obj, 'z');
// make the object non-extensible, non-configurable
Object.seal(obj);
Object.isSealed(obj); // => true, can't unseal it
// => Object { value: 1, writable: true, enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'x');
// Object { get: y(), set: y(), enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'y');
// Object { get: z(), set: z(), enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'z');
// non-extensible
obj.newProp = 3;
obj.newProp; // => undefined
obj.z = 3; // z was not assigned before, doesn't exist
obj.z; // => undefined
// writable, still can set
obj.x = 0;
obj.x; // => 0
obj.y = 0;
obj.y; // => 0
// make the object non-extensible, non-configurable, non-writable
Object.freeze(obj);
Object.isFrozen(obj); // => true, can't unfreeze it
// => Object { value: 0, writable: false, enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'x');
// => Object { get: y(), set: y(), enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'y');
// => Object { get: z(), set: z(), enumerable: true, configurable: false }
Object.getOwnPropertyDescriptor(obj, 'z');
obj.x = 1;
obj.x; // => 0, no change
obj.y = 2;
obj.y; // => 0, no change
// only affect the object itself
obj.nestedObj.x = 1;
obj.nestedObj.x; // => 1
Object.prototype.myObj = {x: 1};
obj.myObj.x; // => 1
Testing properties
Check whether an object has a property with a given name:
- Directly query the property;
in
operator;hasOwnProperty()
, returnstrue
if the property belongs to the object itself, not the prototype objects;propertyIsEnumerable()
, returns true only ifhasOwnProperty()
returnstrue
and theenumerable
attribute istrue
;
let obj = {x: 1, y: 2};
obj.x === undefined; // => false, directly query, exists
obj.z === undefined; // => true, doesn't exist
'z' in obj; // => false, doesn't exist
obj.y = undefined;
obj.y === undefined; // => true, but exists
// should use the in operator to check, not depending on the value "undefined"
'y' in obj; // => true, exists
Enumerating properties
for...in
, iterates over all enumerable properties’ name, including inherited;Object.getOwnPropertyNames()
, returns all own property names, including enumerable and non-enumerable;Object.keys()
, returns all own enumerable property names;
Only get non-enumerable properties
Use the Array.prototype.filter()
function to remove the result of Object.keys()
from Object.getOwnPropertyNames()
‘s.
Arrays
- Untyped: an element may be of any type;
- Dynamic: no need to declare a fixed size, growing or shrinking as needed;
- Sparse: no need to have contiguous indexes, maybe have gaps, length is larger than the number of indexes;
const arr = []; // empty array
let arr2 = [1, 1 + 1, '3', {x: 1}, ['a', 'b']];
arr2[0]; // => 1, use index 0 to access, JavaScript converts the number 0 to a string "0"
arr2['0']; // => 1, use the property name '0' to access, an array is also an object
arr2[1.0]; // => 2, floating-point, same as arr2[1]
All indexes are property names, but only property names that are non-negative integers are indexes.
delete arr2[0];
// => Array(5) [ undefined, 2, "3", {…}, (2) […] ]
arr2; // didn't change the length, arr2 is a sparse array now
arr2.shift();
// => Array(4) [ 2, "3", {…}, (2) […] ]
arr2; // the length was changed, arr2 becomes dense again
let arr3 = [1, , 3]; // the second element is undefined, sparse array
const arr4 = [1, , 3, ]; // same as arr3
// => Array [ "0", "2" ]
Object.keys(arr3);
arr3.length; // => 3, sparse array, the length isn't equal to the number of indexes
1 in arr3; // => false
// => Array(3) [ 1, undefined, 3 ]
arr3;
arr3.length = 5; // growing the array, to add new empty slots
// => Array(5) [ 1, undefined, 3, undefined, undefined ]
arr3;
arr3.length = 4; // shortening the array
// => Array(4) [ 1, undefined, 3, undefined ]
arr3;
const arr5 = new Array(); // same as []
let arr6 = new Array(5); // create 5 empty slots, sparse array
// => Array(5) [ undefined, undefined, undefined, undefined, undefined ]
arr6;
arr6['6'] = 6; // same as arr6[6]
// If the given string can be converted to a big number, will create large number of empty slots.
// => Array(7) [ undefined, undefined, undefined, undefined, undefined, undefined, 6 ]
arr6;
arr6.length; // => 7, grew the array automatically
arr6[-1] = -1; // convert -1 to '-1', is property name
// => Array(7) [ undefined, undefined, undefined, undefined, undefined, undefined, 6 ]
arr6;
arr6.length; // => 7
arr6['-1']; // => -1
// => Array [ "6" ]
Object.keys(arr6);
0 in arr6; // => false
arr6[0]; // => undefined, empty slots' value are undefined, but don't exist.
arr6[0] = undefined;
0 in arr6; // => true, don't use 'undefined' to determine whether an element exists.
Iterating arrays
for
Array.prototype.forEach()
for...of
const arr = [1, , '3', , 5];
arr.forEach(function(val, idx) {
/* =>
index: 0 value: 1
index: 2 value: 3
index: 4 value: 5
*/
console.log('index:',idx, 'value:',val);
});
for(let i = 0; i < arr.length; i++) {
if (! (i in arr)) continue; // skip non-existence elements
// => , same as forEach
console.log('index:',i, 'value:', arr[i]);
}
// can't distinguish an element is non-existence or its value is undefined
for(const val of arr) {
/* =>
value: 1
value: undefined
value: 3
value: undefined
value: 5
*/
console.log('value:', val);
}
Functions
Function definitions can be nested within other functions. The nested functions can access any variables within a same scope where they are defined, so JavaScript functions are closures. Functions are first-class objects in JavaScript.
Defining functions
Function descriptions
function fn( /* zero or more parameters, comma-separated */ ) {
/* ... */
}
Function expressions
// 1. anonymous
var fnVar = function(){ console.log('An anonymous function expression.') };
fnVar();
// 2. named
// easily find the origin of an error from the call stack
var namedFnVar = function namedFn(){ console.log('A named function expression.') };
namedFnVar();
namedFn(); // => ReferenceError: namedFn is not defined
// 3. one-time
(function(msg) {
console.log('A one-time function expression, msg:', msg)
})('Oh');
// 4. arrow
var arrowFn = () => console.log('An arrow function expression.');
arrowFn();
Function constructors
- Create functions dynamically;
- Only execute the code within the global scope, can’t access local variables;
- The function body is a string, can’t optimize it, parsing the function body each time it is called;
- Similar security and performance issues to
eval
;
It is not recommended to use normally.
const constructorFn = new Function('msg',
'console.log("A function created by a function constructor, msg:", msg)');
constructorFn('Oh');
The argument names, such as msg
, must be strings and valid identifiers.
Function scope
fn(); // => hi
fnVar(); // => TypeError: fnVar is not a function
function fn() { console.log('hi') };
var fnVar = function() { console.log('hello') };
fnVar(); // => hello
Function expressions are not visible everywhere within the current scope, don’t like variables declared by var
and function descriptions.
Binding functions
function.bind(thisArg[, arg1[, arg2[, ...]]])
bind
binds a function to an object and returns a new function with the specified this
value and initial arguments.
It is not recommended to use new
operator to construct new instances of bound functions.
const globalObject = this;
const obj = {
x: 1,
getX: function() { console.log(this.x) },
add: function(x, y) { console.log(x + y, this === globalObject) }
};
obj.getX(); // => 1
const savedGetX = obj.getX;
savedGetX(); // => undefined, called as a function, not a method, "this" is the global object
const boundGetX = savedGetX.bind(obj);
boundGetX(); // => 1, "this" is obj
// with initial arguments
const boundAdd = obj.add.bind(null, 10); // "this" is null, use the "this" of current scope
boundAdd(1); // => 11 true
boundAdd(1, 2); // => 11 true, 2 is ignored
// create a shortcut to a function, and could specify a special "this" value
const add = Function.prototype.call.bind(obj.add);
add(null, 1, 2); // => 3 true, this "this" is not special
Determining whether a function exists
'function' === typeof this.fn
Nested functions
A function description can appear in the global code or within other functions. It behaves strangely within block code(if
, for
, …) in non-strict mode, can’t ensure that it will be defined, but in strict mode, it is defined and its scope is the block.
No such block scope restriction for function expressions and function constructors.
Nested functions don’t inherit the this
value from its caller. If it is invoked as a method, this
is the object, as a function, this
is the global object or undefined (strict mode).
const globalObject = this;
const obj = {
fn: function() {
const savedThis = this;
console.log(this === obj); // => true
(function() {
console.log(this === globalObject, savedThis === obj) // => true true
})();
obj.fn.nestedFn = function() {
// => true false true
console.log(this === obj.fn, this === globalObject, savedThis === obj)
}
return function() {
// => true false true
console.log(this === anotherObj, this === globalObject, savedThis === obj)
}
}
}
let anotherObj = {};
anotherObj.fn = obj.fn();
obj.fn.nestedFn();
anotherObj.fn();
Invoking functions indirectly
The following two methods accept an explicit this
value, so could invoke any function as a method of any object, writing a method once and then inherit it in any object.
Function.prototype.call()
function.call(thisArg, arg1, arg2, ...)
All arguments are optional.
In non-strict mode, thisArg
is null
or undefined
, which will be replaced with the global object, primitive values will be converted to wrapper objects.
Using call()
to chain constructors for an object. Like a C++’s subclass calls its superclass’s constructor during initializing the class.
Using call()
to invoke an anonymous function for multiple objects.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
Function.prototype.apply()
function.apply(thisArg, [argsArray])
thisArg
is same as call()
.
argsArray
is optional, could be null
or undefined
if the function hasn’t arguments.
Using apply()
to chain constructors for an object.
Using apply()
and built-in functions cleverly.
let array = ['a', 'b'];
let anotherArray = ['c', 'd']
const elements = [0, 1, 2];
// Syntax: Array.prototype.push(element1[, ...[, elementN]])
anotherArray.push(elements);
console.log(anotherArray); // => ["a", "b", [0, 1, 2]]
// use the built-in functions cleverly
array.push.apply(array, elements);
console.log(array); // => ["a", "b", 0, 1, 2]
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply
The difference of the two methods is only that one accepts an argument list, one accepts an argument array or array-like object.
Closures
closure = function + (its) variable scope.
The variable scope includes local(own) scope, outer function’s scope, global scope.
The variable scope is determined when the function is defined, not runtime, the function could be invoked in another scope.
A closure gives you access to an outer function’s scope from an inner function.
const fn = (function anonFn() {
let counter = 0;
++counter;
return function anotherAnonFn() { console.log(counter) };
})();
fn(); // => 1
The counter
variable is in the scope when defining anotherAnonFn()
, but its value is determined when invoking fn()
, so don’t create closures in a loop, they share the final value of a variable.
It is common using closures to create private environments which include private methods and variables, no other methods can change these private variables.
JavaScript doesn’t like C++ to use function call stack: local variables would disappear after the function returns. Have an external reference to anotherAnonFn()
after anonFn()
returns, the object anonFn
won’t be garbage collected.
It is unwise to unnecessarily create functions within other functions if closures are not needed for a particular task, because of performance, more details here.
A closure has its own this
and arguments
, if need outer function’s, save them to variables before using them.
Function parameters
- Rest parameters
// moreArgs is always an array
function fn(...moreArgs) {
// => true [ 1, 2, 3 ]
console.log(Array.isArray(moreArgs), moreArgs);
}
fn(1, 2, 3);
- Default parameters
function fn(a, b = 1) {
console.log(a, b); // => undefined 1
}
fn();
- Destructured parameters
// destructuring rest parameters
function fn(...[a, b, c]) {
console.log(a, b, c); // => 1 2 3
}
fn(1, 2, 3);
The arguments
object
Could refer to a function’s arguments within the function with arguments
. Arrow functions haven’t it.
arguments
is an array-like object, has a length
property. If you want a true argument array, use rest parameters.
function fn() {
// => 3 Arguments { 0: 1, 1: 2, 2: 3, … }
console.log(arguments.length, arguments);
}
fn(1, 2, 3);
function anotherFn(a) {
console.log(a); // => 1
arguments[0] = 2; // sync with a
console.log(a); // => 2
}
anotherFn(1);
// In non-strict mode, if the function contains rest, default, or destructured parameters, the arguments object doesn't sync with them.
function restParaFn(...moreArgs) {
// => Arguments { 0: 1, 1: 2, 2: 3, … } Array(3) [ 1, 2, 3 ]
console.log(arguments, moreArgs);
moreArgs[0] = 0; // doesn't sync with the arguments object
// => Arguments { 0: 1, 1: 2, 2: 3, … } Array(3) [ 0, 2, 3 ]
console.log(arguments, moreArgs);
}
restParaFn(1, 2, 3);
Generator functions
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators
Arrow functions
Shorter functions and non-binding of this
.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Classes
JavaScript classes, introduced in ECMAScript 2015, are primarily syntactical sugar. In fact, they are special functions.
Class declarations
class Foo {
constructor() {}
}
class SubFoo extends Foo {}
console.log(Foo.__proto__ === Function.prototype); // => true
console.log(SubFoo.__proto__ === Foo); // => true
console.log(SubFoo.__proto__ === Foo.prototype); // => false
class Bar extends Array {
constructor(args) {
super(args);
}
}
const Animal = {
speak () {}
}
class Dog {
// Declared public or private fields are always present.
// public fields
dogName = '';
breed;
/* private fields, not support yet
* #color = 'brown';
* #height;
*/
static type = 'Dog'; // static data property
static speak() {
console.log('bark, ...');
}
}
Dog.dangerous = false; // static data property
Dog.prototype.cuteRating = 10;
console.log((new Dog).__proto__ === Dog.prototype); // => true
// To inherit from a non-constructible object
Object.setPrototypeOf(Dog.prototype, Animal);
console.log(Dog.prototype.__proto__ === Animal); // => true
The constructor could be ignored. If the class is a base class, the default constructor is empty, like Foo
. If the class is derived class, the default constructor calls the parent constructor, passing along any arguments that were provided, like Bar
.
Class expressions
// anonymous class
const Foo = class {};
const AnotherFoo = class extends Array {};
// named class
const Bar = class BarClass {};
Syntactical sugar
function Foo() {
this.x = 1;
this.y = 2;
}
Foo.prototype.say = () => {};
function Bar() {
Foo.call(this);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.constructor = Bar;
class Foo {
x = 1;
constructor() {
this.y = 2;
}
say() {}
}
class Bar extends Foo {
}
They are same. In class Foo
, the method say
belongs to Foo.prototype
.
Notes
Class body is executed in strict mode;
Must first declare the class then access it, including class declarations and class expressions;
Static methods;
class Point { static distance(a, b) { // calculate distance } static nothing() { // "this" is legal in static methods return this; } } console.log(typeof Point.nothing()); // => "function" console.log(Point.nothing()); // => class Point {} // call the static method, can't call it via the instances Point.distance(5, 5);
If assigning a static or non-static method to a variable, then call it, the value of
this
is alwaysundefined
.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor
Operators
Operators and precedence
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence
Bitwise operators
&
, |
, ^
, ~
, <<
, >>
, >>>
The bitwise operators expect 32-bit integer operands, and drop any fractional part and any bits beyond the 32nd.
NaN
, Infinity
, and -Infinity
all are converted to 0
.
Comparison operators
==
, ===
, !=
, !==
===
, !==
are the strict versions of ==
, !=
, they check the type of operands first, then compare the values:
- Different types, not equal;
- One of values is
NaN
, not equal; 0 === -0
istrue
;- Strings are Unicode (UTF-16) strings. Two equal strings must have a same length and same UTF-16 bit sequences;
- Objects are compared by reference. Two equal objects must refer to a same object;
==
, !=
perform some type conversions, then compare. The rules:
- If two values have a same type, do same things like
===
,!==
, if not, check the following rules; null == undefined
istrue
;- One is number, one is string, convert the string to a number, then compare;
- One of values is boolean, convert the boolean to a number,
true
to1
,false
to0
, then compare; - One of values is object, perform object-to-primitive conversions(favor number, then string), then compare;
- Any other combinations are not equal;
<
, >
, <=
, >=
These operators favor numbers and only perform string comparison if both operands are strings, don’t like +
(favors strings).
The rules:
- One of values is an object, perform object-to-primitive conversions first;
- Both are strings, compared as strings;
- One of values is not string, convert all of them to numbers, then compare;
Infinity
is larger any number,-Infinity
is the opposite;- Either of operand is
NaN
, the result isfalse
;
in
operator
The left-side operand is a string, the right-side operand is an object. It is true
if the left-side value is a property name of the object.
const x = [ 5, 6, 7 ];
0 in x; // => true
'0' in x; // => true
5 in x; // => false
instanceof
operator
The left-side operand is an object, the right-side operand is the constructor function to initialize the object.
const x = [ 5, 6, 7 ];
x instanceof Array; // => true
x instanceof Object; // => true, all objects are instances of Object
Logical operators
&&
, ||
, ~
~
operator converts its operand to a boolean value first, but &&
and ||
don’t.
If the left-side operand can determine the result, &&
and ||
don’t evaluate the right-side operand.
! 1 // => false
! null // => true
'a' && 1 // => 1
'a' && true // => true
null.prop // => TypeError: null has no properties
0 && null.prop // => 0, no TypeError, don't evaluate null.prop
1 || null.prop // => 1
0 || null.prop // => TypeError: null has no properties
let y;
let x = y || 1; // => 1, if y is defined, assign it to x, otherwise, set x a default value 1
eval()
It is a global function which interprets a string as JavaScript source code.
eval
returns the value of the last expression or statement, or undefined
if the last expression or statement hasn’t value.
eval
propagates exceptions.
If calling eval
within a function, it uses the environment of the caller, to do anything like the caller within the function.
If calling eval
outside functions, it use the global environment.
The source code in eval
is unanalyzable. Can’t safely optimize functions that call eval
.
If assigning eval
to a variable within a function, this is an indirect call to eval
. eval
uses the global environment, not the local environment. It can’t read, write, define local variables or functions.
var x = 1;
var y = 1;
console.log('global x:', eval(x)); // => global x: 1
function testEval() {
var y = 1; // hide the global one
var z = 1;
eval('z += 1; k = 1'); // also create a new global variable k
console.log('local z, should be 2:', z); // => local z, should be 2: 2
// indirectly call eval, use the global environment
var aliasOfEval = eval;
aliasOfEval('y += 1');
// aliasOfEval('z += 1'); // => ReferenceError: z is not defined
console.log('local y, should be 1:', y); // => local y, should be 1: 1
// redefine eval, hide the global function eval()
// => TypeError: eval is not a function, the error comes from line 10
// var eval = 1;
}
testEval();
console.log('global y, should be 2:', y); // => global y, should be 2: 2
console.log('new global variable, k:', k); // => new global variable, k: 1
Conditional operator (?:
)
condition ? exprIfTrue : exprIfFalse
The operands may be of any type. If condition
is truthy, to evaluate the exprIfTrue
expression and its value is returned, otherwise is the exprIfFalse
expression.
This operator is frequently used as a shortcut for the if
statement.
let x;
let y = x ? x : 'default';
let x;
let y;
if(x) {
y = x;
}
else {
y = 'default';
}
delete
operator
delete object.property
delete object['property']
The return value is true
or false
.
delete
operator removes a property from an object, but:
- Can’t delete any properties declared by
var
,let
,const
; - Can’t delete function declaration;
var obj = {x: 1, y: 1};
delete obj.x; // => true
'x' in obj; // => false
delete obj; // => false, declared by var
obj2 = {x: 1, y: 1}; // obj2 is a property of the global object
delete obj2; // => true
obj2; // => ReferenceError: obj2 is not defined
var arr = [1, 2, 3, 4, 5];
delete arr[0]; // => true
delete arr['1']; // => true
// the size of arr was not changed, still is 5
arr; // => Array(5) [ undefined, undefined, 3, 4, 5 ]
0 in arr; // => false
arr[0]; // => undefined
arr[2] = undefined; // arr[2] is still there
arr; // => Array(5) [ undefined, undefined, undefined, 4, 5 ]
2 in arr; // => true
function fn() {};
delete fn; // => false
delete this.fn; // => false
typeof operator
typeof operand
typeof(operand)
The return value is a string.
typeof NaN; // => "number"
typeof Number('1'); // => "number"
// All constructor functions, except the function constructors, will always be "object".
typeof(new Number('1')); // => "object"
// => "function"
typeof new Function('x', 'y', 'var z = x + y; return z');
Spread operator
The spread operator begins with ...
.
function fn(x, y, z) { console.log(x) }
fn(...[1, 2, 3]); // => 1
// => Sat Feb 01 2020 00:00:00 GMT+0000 (Coordinated Universal Time)
console.log(new Date(...[2020, 1, 1]).toString());
// concatenate arrays
const arr = [...[1,2,3], ...['a', 'b']];
console.log(arr); // => Array(5) [ 1, 2, 3, "a", "b" ]
console.log(...'abc'); // => a b c
The spread operator expands an iterable, such as arrays, strings.
const obj = { nestedObj: { x: 1 } };
const clonedObj = { ... obj }; // clone an object
console.log(clonedObj); // => Object { nestedObj: {…} }
console.log(clonedObj.nestedObj === obj.nestedObj); // => true
const assignedObj = Object.assign({}, obj); // another way to clone objects
console.log(assignedObj.nestedObj === obj.nestedObj); // => true
The spread operator expands properties to do object shallow-cloning, not deep-cloning.
The spread operator copies own enumerable properties from an object when applying it to object literals.
The Object.assign()
method copies all own enumerable properties from one or more source objects to a target object. It returns the target object.
function Foo () {}
Foo.prototype.x = 1;
const obj = new Foo;
const clonedObj = {...obj};
console.log(obj.__proto__ === Foo.prototype, clonedObj.__proto__ === Foo.prototype); // true false
console.log(clonedObj.__proto__ === Object.prototype); // => true
function Foo () {}
Foo.prototype.x = 1;
const obj = new Foo;
const clonedObj = Object.assign({}, obj);
console.log(obj.__proto__ === Foo.prototype, clonedObj.__proto__ === Foo.prototype); // true false
console.log(clonedObj.__proto__ === Object.prototype); // => true
Object cloning excludes the prototype
object. If Object.assign
copies property definitions into prototypes, more details here.
const obj = {
x: 1,
get y() { return this._y },
set y(val) { this._y = val },
get z() { return 3 },
set z(val) { console.log('do nothing for z') }
}
let clonedObj = {
get z() { console.log('z is undefined') },
set z(val) { console.log('set z with the value:', val) }
}
// => Object { x: 1, y: Getter & Setter, z: Getter & Setter }
console.log(obj);
// => Object { x: 1, y: undefined, z: 3 }
console.log({...obj});
/*
* => set z with the value: 3
* => Object { z: Getter & Setter, x: 1, y: undefined }
*/
console.log(Object.assign(clonedObj, obj));
Object.assign
calls the getter of the source object and the setter of the target object, look at its name assign. The spread operator only calls the getter.
void
operator
void
evaluates an expression, then returns undefined
no matter the expression returned value.
If some places need a undefined
value, use void
ahead of the expressions.
void expression
void(expression)
const val = void function fn() {
return 1;
}
console.log("val =", val); // => val = undefined
fn(); // => ReferenceError: fn is not defined
fn
isn’t a function declaration, it is an expression.
<a href="javascript:void(0);">
This is very common. javascript:
is an URI. Click the link not to do URL jumping.
0
in void(0)
can be replaced by other JavaScript expressions, such as changing the background.
Expressions
Expression evaluation order
Always left-to-right order.
var z = x + y; // the evaluation order: z, x, y
Destructuring assignment
Array destructuring
const arr = [1, 2, 3, 4, 5];
const [one='1', two, , ...others] = arr;
console.log(one, two, others); // => 1 2 Array [ 4, 5 ]
one
has a default value.
3
is ignored, the remaining part of the array is assigned to others
.
Object destructuring
const obj = {x: 1, y: 2};
let {foo, bar} = obj;
let {x, y} = obj;
console.log(foo, bar, x, y); // => undefined undefined 1 2
// => SyntaxError: redeclaration of let foo
// const {x: foo, y: bar} = obj;
const {x: foo2, y: bar2} = obj;
console.log(foo2, bar2, 'foo2' in this); // => 1 2 false
// => TypeError: invalid assignment to const `foo2'
// foo2 = 0;
{
// => SyntaxError: expected expression, got '='
// {m, n} = {m: 1, n: 2};
// need the parentheses when destructuring a literal object without the declaration
({m, n} = {m: 1, n: 2});
console.log(m, 'm' in this); // => 1 true
m = 0;
}
console.log(m, 'm' in this); // => 0 true
delete m; // => true
Type conversions
The primitive-to-primitive and primitive-to-object conversions are straightforward.
The object-to-primitive conversion is more complicated.
Implicit conversions
"0" == 0 // => true
Converting "0"
to 0
before to compare.
0 + "" // => "0"
1 + "0" // => "10"
"0" + 1 // => "01"
If one operand of the +
operator is a string, the result is a string.
1 - "1" // => 0
1 - "1" == 0 // => true
1 - "a" // => NaN
These are arithmetic operations.
+"0" // => 0
The +
operator converts its operand to a number.
-"1"; // => -1
const x = "2";
-x + 1; // => -1
These are arithmetic operations.
!2 // => false
!"0" // => true
The !
operator converts its operand to a boolean.
Explicit conversions
parseInt("123"); // => 123
const x = 2;
x.toString() // => "2"
x.toString(2) // => "10", binary
x.toString(16) // => "2", hexadecimal
Other global functions like parseInt()
: parseFloat()
.
Other methods like toString()
: toFixed()
, toExponential()
, toPrecision()
.
Primitive-to-Object conversions
The Primitive-to-object conversions are straightforward, using wrapper objects: String()
, Number()
, Boolean()
.
No conversions and raise TypeError
when using null
and undefined
on where an object is expected.
String(123) // => "123"
Number("0") == 0 // => true
Object(null) // => Object(), empty object
Object(undefined) // => Object(), empty object
Number(null) // => 0
Number(undefined) // => NaN
String(null) // => "null"
String(undefined) // => "undefined"
Boolean(null) // => false
Boolean(undefined) // => false
Object-to-Primitive conversions
Converting all objects to true
.
All objects(don’t include web browsers defined objects) inherit toString()
and valueOf()
. Custom objects could override them.
The toString()
returns a string representing the object.
({}).toString() // => "[object Object]", by default
[1,2,3].toString() // => "1,2,3"
// => "function() {console.log('hello')}"
(function() {console.log('hello')}).toString()
The valueOf()
returns the primitive value of the object.
({}).valueOf() // => Object { }, return the object itself
[1,2,3].valueOf() // => Array(3) [ 1, 2, 3 ], return the array itself
// => function (), return the function itself
(function() {console.log('hello')}).valueOf()
String("1").valueOf() // => "1", return the wrapped primitive value
The rules:
To convert an object to a string
- Check
toString()
returned value, overridedtoString()
can return any value. If the value is primitive, then convert it to a string(if necessary), done; - Check
valueOf()
. If the value is primitive, then convert it to a string(if necessary), done; - Throw
TypeError
;
- Check
To convert an object to a number
Similar to string, but check
valueOf()
first, thentoString()
;[1].toString() // => "1" [1].valueOf() // => Array [ 1 ], return the array itself 3 - [1] // => 2
The object-to-primitive conversion is an object-to-number conversion first( valueOf()
), then an object-to-string conversion( toString()
).
+
, -
, *
, /
, %
, ==
, !=
, <
, >
operators use the object-to-primitive conversion, and use the returned primitive value directly, no further conversion to a number or string.
1 + [1] // => "11", object to string
Boolean(true).toString() // => "true"
Boolean(true).valueOf() // => true
1 == Boolean(true) // => true, object to boolean
1 + Boolean(true) // => 2, object to boolean, not string
const myObject = {
toString: function() { return "1" },
valueOf: function() { return 3 }
}
1 + myObject // => 4
2 - myObject // => -1
3 != myObject // => false
3 > myObject // => false
3 * myObject // => 9
3 / myObject // => 1
3 % myObject // => 0
If either of operands of +
is an object, perform the object-to-primitive conversion, then if one of the operands is string, perform string concatenation, otherwise, arithmetic operation. Look at the difference of line 1, line 11.
const now = new Date();
now + 1 // return a string
now == now.toString() // => true
Date
objects are special, it’s +
, ==
, !=
use the object-to-string conversion( toString()
) first, not object-to-number( valueOf()
).
Control flow
Block statement
{
StatementList
} // no ';'
// OR
LabelIdentifier: {
StatementList
}
In non-strict code, function declarations inside blocks behave strangely. Do not use them. When using let
or const
to declare variables within a block, the block also defines a block scope, these variables are only visible within the block and hide global or local variables that have the same names.
{
let x = 0;
console.log(x);
}
{}
included code is a whole, acts as one statement. Use it anywhere that JavaScript expects a single statement.
if...else
if ( expression )
statement
else if ( expression ) // Optional
statement
/* ... */
else // Optional
statement
switch
switch ( expression ) {
case value1: // case expression can be an arbitrary expression
statement
/* ... */
case valueN:
statement
default: // Optional
statement
}
function testSwitch(x) {
// use === operator to match x, not == operator, no type conversions
switch(x) {
case '1':
console.log('1');
break;
default: // 'default' label can appear anywhere within the switch statement
console.log('default');
break;
case 1:
console.log(1);
// no break, continue executing the following labels until encountering a break or end
case '2':
case 2:
console.log(2);
}
}
testSwitch('1'); // => "1"
testSwitch(1); // => 1 2
testSwitch(2); // => 2
testSwitch('2'); // => 2
testSwitch(3); // => "default"
while
while ( expression )
statement
It is impossible to completely simulate the for
loop with while
.
do...while
do
statement // iterating at least once
while ( expression ); // always be terminated with a semicolon, the while statement doesn't
for
for ([initialization]; [condition]; [final-expression])
statement
for(let i = 0; i < 10; i++) {
console.log(i);
}
for...in
for (variable in object)
statement
It iterates over all enumerable properties of an object, including inherited enumerable properties.
Think the object is a list of key-value pairs, for...in
returns the keys, not the values.
variable
must be an lvalue, may be an existing variable, a var
/let
/const
declared new variable, or any expression that evaluates to an lvalue.
{
let arr = [], i=0;
for(arr[i++] in '123'); // arr is an lvalue
arr; // => Array(3) [ "0", "1", "2" ]
}
object
can be any expression that evaluates to an object. To do type conversions and convert the primitive value to its wrapper object.
for (const v in '123')
console.log(v); // => "0" "1" "2", convert '123' to String('123')
Only enumerating enumerable properties of an object, not all properties. All properties and methods defined by our code are enumerable.
Iterating over the properties in an arbitrary order, don’t depend on the order you see, especially to use with an array and may be return non-integer property names(not recommended for array).
It is best not to add, modify, or remove properties from the object during iteration, no guarantee whether to enumerate the changed properties.
Most of the time, use for...in
to debug.
for...of
for (variable of iterable) {
statement
}
Iterating over iterable objects.
Similar to for...in
, the difference between them:
for...in
iterates over the enumerable properties of an object, in an arbitrary order;for...of
iterates over values(not keys or properties) of an iterable object. An object that implements the@@iterator
method is an iterable object, such asString
,Array
;
{
Object.prototype.fnOfObject = function () {};
Array.prototype.fnOfArray = function () {};
const iterable = [3, 5, 7];
iterable.msg = 'hello';
for (const i in iterable) {
console.log(i); // => "0" "1" "2" "msg" "fnOfArray" "fnOfObject"
}
for (const i in iterable) {
if (iterable.hasOwnProperty(i)) {
// => "0" "1" "2" "msg"
// fnOfArray and fnOfObject are inherited properties, not own properties
console.log(i);
}
}
for (const i of iterable) {
console.log(i); // => 3 5 7
}
}
break
break [label];
break
breaks the current loop, switch
, or label
statements.
If a function itself is nested within a loop, switch
, or label statements, break
cannot be used within the function body to break out of the loop, switch
, or label statements.
blockOne: {
console.log('1');
( function() {
break blockOne; // => SyntaxError: label not found
})();
}
labelFn: function testBreakAndLabel() {
console.log('hi');
break labelFn; // => SyntaxError: label not found
console.log('How are you?');
}
testBreakAndLabel();
continue
continue [label];
Only to use continue
within the body of a loop, with or without label
.
label statements
label :
statement
label
must be a legal identifier.
JavaScript has not goto
statement like C language. Can only use labels with break
and continue
.
break
can be used with any labeled statement and needs to be nested within the referenced label. It is able to break out of a loop, or switch
that is not the nearest one.
continue
can be used with looping labeled statements. It is able to restart the loop that is not the nearest one.
hi: {
console.log('hi'); // => hi
break hi;
console.log('How are you?'); // jump over the statement
}
top:
for (let v of ['hi', 'hey', 'hello']) {
if( v == 'hey' )
continue top;
console.log(v); // => "hi" "hello", skipping "hey"
}
blockOne: {
console.log('1');
break blockTwo; // => SyntaxError: label not found, break is not nested within label blockTwo
}
blockTwo: {
console.log('2');
}
return
return [expression];
const fn = function() {/* no return statement */};
const fn2 = function() {return; /* the return statement hasn't a value */};
fn(); // => undefined
fn2(); // => undefined
try...catch
Three forms:
try...catch
try...finally
try...catch...finally
// Nested try-blocks
try { // can't miss {}, even only one statement
try {
// throw; // => SyntaxError: throw statement is missing an expression
throw 'oops'; // can be any type of the expression, e.g.: throw 1
} catch (e) { // e is a block-scope variable
console.error('inner:', e); // => "inner: oops"
} finally { // always be executed regardless of what happens in the try block
console.log('inner: finally'); // => "inner: finally"
}
} catch (e) {
console.error('outer:', e);
}
try {
try {
throw new Error('oops');
} catch (e) {
console.error('inner:', e.message); // => "inner: oops"
throw new Error('another oops in catch-block');
// OR
// throw e; // rethrow the exception
} finally {
console.log('inner: finally'); // => "inner: finally"
}
} catch (e) {
console.error('outer:', e.message); // => outer: another oops in catch-block
}
function testTry(toThrow) {
try {
try {
throw new Error();
} catch (e) {
console.error('OMG, oops!');
throw new Error('Oops in catch-block');
} finally {
// If the finally-block itself causes a jump with a return, continue, break, or throw statement, or by calling another function that throws an exception, no matter whatever happened, performing the new jump.
if(toThrow) {
throw new Error('Oops in finally-blcok');
}
else {
console.log('No oops, oh, yeah!');
return 'OK';
}
}
} catch (e) {
console.error('What happened?', e.message);
}
return 'NOT OK';
}
/* output:
OMG, oops!
No oops, oh, yeah!
OK
*/
console.log(testTry(false));
/* output:
OMG, oops!
What happened? Oops in finally-blcok
NOT OK
*/
console.log(testTry(true));
debugger
debugger
If having an available and running debugger (the debugger of DevTools), the statement acts like setting a breakpoint in the debugger, otherwise, do nothing.
console.log('hi');
debugger;
console.log('How are you?');
Run it in the console of DevTools, paused at line 2.
Run it using Node.js (node testDebugger.js
), the statement does nothing.
Strict mode
Use 'use strict'
to invoke the strict mode.
"use strict"
or 'use strict'
is a directive, not a statement, appears only at the top of a JavaScript script or a function body before any real statements.
Don’t do this:
- Concatenating strict code, non-strict code. The result is strict;
- Concatenating non-strict code, strict code. The result is non-strict;
JavaScript modules are strict by default.
Code passed to eval()
is in strict mode if calling eval()
(must call directly) from strict code. Also could use 'use strict'
directly in eval()
.
Why to use
Throwing errors instead of some silent errors, fix problems easier;
Making things simpler, let JavaScript engine optimize the code better;
Writing more secure JavaScript code;
What changed
Converting mistakes into errors
const nonStrictFn = function() { console.log('"this" should be the global object', this); } function testStrictMode() { 'use strict'; // not a new implicit global variable. Must declare the variable first, then assign newGlobalVar = 0; // => ReferenceError: assignment to undeclared variable newGlobalVar nonStrictFn(); // => "this" should be the global object // functions(not methods of objects) in strict mode haven't "this". // Use the feature to determine whether the code is in strict mode. console.log('In strict mode? ', !this ? 'Yes': 'No'); // => In strict mode? Yes } testStrictMode();
Making
eval()
andarguments
simpler
eval()
in strict mode
Use the environment of the caller within a function, but can’t define new variables in the local or global scope;
Has a private environment, defined new variables are in this scope;
Can’t redefine
eval
, it is a keyword;
"use strict";
var x = 1;
var y = 1;
function testEval() {
var y = 1; // hide the global one
var z = 1;
eval('x += 1'); // strict mode
eval('z += 1'); // strict mode
// eval('k = 1'); // => ReferenceError: assignment to undeclared variable k
// strict mode, varInEvalPrivateEnv is only in the private environment of eval
eval('var varInEvalPrivateEnv = 1');
// => ReferenceError: varInEvalPrivateEnv is not defined
// console.log('varInEvalPrivateEnv:', varInEvalPrivateEnv);
console.log('local z, should be 2:', z); // => local z, should be 2: 2
// call eval indirectly, use the global environment, not strict mode
var aliasOfEval = eval;
aliasOfEval('y += 1; m = 1');
console.log('local y, should be 1:', y); // => local y, should be 1: 1
// eval is a JavaScript keyword now
// var eval = 1; // => SyntaxError: 'eval' can't be defined or assigned to in strict mode code
}
testEval();
console.log('global x, should be 2:', x); // => global x, should be 2: 2
console.log('global y, should be 2:', y); // => global y, should be 2: 2
console.log('global m, should be 1:', m); // => global m, should be 1: 1
arguments
in strict mode
- The properties of
arguments
are not aliases of the arguments of functions; - Can’t read or set
arguments.callee
;
(function (x, y) {
console.log(arguments.length); // => 2
console.log('x: %d, y: %d', x, y); // => x: 1, y: 2
arguments[0] = 'a';
y = 'b';
// arguments[0] is an alias of x, arguments[1] is an alias of y
console.log('x: %s, y: %s', x, y); // => x: a, y: b
console.log(arguments.callee); // => function () ...
})(1,2);
( function (x, y) {
'use strict';
console.log(arguments.length); // => 2
console.log('x: %d, y: %d', x, y); // => x: 5, y: 6
arguments[0] = 'c';
y = 'd';
// arguments[0] isn't an alias of x, arguments[1] is in the same way
console.log('x: %s, y: %s', x, y); // => x: 5, y: d
// => TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions
// console.log(arguments.callee);
} )(5,6);
Asynchronous JavaScript
Asynchronous approachs:
- Function callbacks;
setTimeout()
/setInterval()
/requestAnimationFrame()
;- Promise;
async
/await
;- Web Workers;
Promise
A Promise is an object representing the eventual completion or failure of an asynchronous operation, the object could be attached callback functions for the success and failure cases.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
Async
/ await
Syntactic sugar built on top of promises that allows you to run asynchronous operations using syntax that’s more like writing synchronous callback code.
async
could be putted in front of a function declaration or object methods.
await
only works inside async
functions.
More details here:
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous
https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Meta programming
The Proxy
and Reflect
objects allow you to intercept and define custom behavior for fundamental language operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
Proxy
To intercept certain operations on an object, these operations are through the proxy first, could add some custom behavior.
The proxy could be as a prototype object to create new objects. Operations on these new objects are also intercepted.
Reflect
If operations are intercepted, how to invoke original methods of an object? use
Reflect
.In addition:
- let
in
,delete
operators become function calls. - let some methods that throw exceptions return a boolean value.
- let
Save multiple prototype objects into an array property of an object, then use Proxy
, Reflect
to intercept method invoking, to implement a non-complicated multiple inheritance.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Meta_programming
JavaScript modules
Use of native JavaScript modules is dependent on the import
and export
statements. Put different features into different JavaScript files. One file, one module.
Modules are in strict mode.
Modules are deferred automatically, no need to use defer attribute in <script>
tag.
Module features aren’t available in the global scope. Therefore, you will only be able to access imported features in the script they are imported into.
Can’t load modules in local HTML files(file://
URL). One is the file protocol, one is the http protocol, will run into CORS, use a http server instead.
Applying modules
// in a HTML file
<script type="module" src="main.js"></script>
Need to include type="module"
in the <script>
tag.
Creating a module object
import * as Module from './modules/module.js';
This makes all exports as members of an object Module
, creating a namespace which avoids conflicts.
Classes
Could import and export classes. This is another option to avoid conflicts.
Aggregating modules
// shapes.js
export { Square } from './shapes/square.js';
export { Triangle } from './shapes/triangle.js';
export { Circle } from './shapes/circle.js';
import { Square, Circle, Triangle } from './modules/shapes.js';
This grabs the exports from three submodules and effectively makes them available from one module shapes.js
.
Dynamic module loading
Calling import()
as a function, it returns a Promise
, to dynamically load modules only when they are needed. Such as calling import()
in a click event callback function would only load the module when clicking.
squareBtn.addEventListener('click', () => {
import('./modules/square.js').then((Module) => {
// to access all exports through the Module object
})
});
Other module solutions
The revealing module pattern
var revealingModule = (function () {
var privateVar = "";
function publicSetName( strName ) {
privateVar = strName;
}
function publicGetName() {
return privateVar;
}
return {
setName: publicSetName,
getName: publicGetName
};
})();
It is a common way using closures to create a private environments as a module.
CommonJS and AMD
The official JavaScript specification defines APIs that are useful for building browser-based applications. CommonJS defines a series of specifications which APIs could handle many common application needs. Module definition is a part of these specifications. AMD(Asynchronous Module Definition) was split from CommonJS, is also a specification which specifies a mechanism for defining modules. The main difference for modules between AMD and CommonJS is that AMD supports asynchronous module loading. Normally, CommonJS modules are used in the server-side, AMD modules are in the browser-side.
Node.js is one of implementations of CommonJS(in partial form). Node.js also supports native JavaScript modules.
RequireJS is one of implementations of AMD.
More details here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export
https://auth0.com/blog/javascript-module-systems-showdown/
JavaScript and HTML
DOM (Document Object Model)
The DOM is a programming interface for HTML and XML, can be implemented by any programming language.
A Web page is a document, a document consists of a hierarchical tree of nodes, the DOM is an object-oriented representation of the tree which can be modified with JavaScript to change its structure, style, and content.
The Document
object represents a document.
The Node
object represents a node. Having different kind of nodes, element node, text node, comment node, attribute node, etc.
Nodes are empty vessels, they can’t represent visual content, elements are for this. The Element
object represents an element, which inherits from the Node
object. HTML has an enhanced HTMLElement
object, the HTMLElement
object has many specific implementations, such as HTMLImageElement
represents the <img>
element, HTMLInputElement
represents the <input>
element. Each node is an HTML element in an HTML document.
Web API = DOM + JavaScript
HTML element list here:
https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API
More DOM details here:
https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model
window
object
We say the global object many times before, the window
object is the global object in browsers.
this === window; // => true
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Window
location
object
location
is a property of the window
object, representing URL info, such as protocol
, host
, port
, href
of the current page. Changing properties of the location
object lets the browser to apply them and refresh the page.
location === this.location; // => true
// => Location https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof
location;
// jump to
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof
location='instanceof';
// location.href='instanceof'; // do the same thing
location = 'https://www.bing.com'; // jump to www.bing.com
// jump to
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/www.bing.com
// location = 'www.bing.com'
/** the following do the same thing
* location.href = 'https://www.bing.com';
* location.assign('https://www.bing.com');
* location.replace('https://www.bing.com');
*/
history
object
history.back()
/history.forward()
/history.go()
likes clicking/holding the Back/Forward button in the browser toolbar.
history.pushState()
/history.replaceState()
changes the browser history for the current tab or window.
Events: window.onpopstate
, window.popstate
, windows.onhashchange
, window.hashchange
(when changing localtion.hash
)
navigator
object
navigator
contains the browser and OS information, such as User-Agent, language, platform, geolocation, keyboard.
screen
object
screen
represents a screen on which the current window is being rendered, including width, height, orientation, color depth, etc.
Selecting HTML elements
By
id
document.getElementById(); // return an Element object
Only can be used by the
document
object. Any HTML element can have theid
attribute.By
name
attributedocument.getElementsByName(); // return a live NodeList object
Only can be used by the
document
object. Thename
attribute is only valid on a handful of HTML elements, suchbutton
,input
;It is deprecated on
<img>
element.More details here:
https://developer.mozilla.org/en-US/docs/Web/HTML/AttributesBy tag name
document.getElementsByTagName();
Return a live
HTMLCollection
object. The object is automatically updated when the DOM is changed, no need to call methods which return aHTMLCollection
object again to retrieve changes, theNodeList
object also;Make a static snapshot using
Array.prototype.slice
to add or remove elements when iterating through such type of objects;Could replace the
document
object with a specified Element object, only to search all descendants of the element except itself;Sample code here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/getElementsByClassName./** * For historical reason, the document object has shortcut properties to access * some kinds of elements: * document.anchors * document.embeds * document.forms * document.images * document.links * document.plugins * document.scripts * They all are HTMLCollection objects, exposing their members directly as properties * by both names and indexes. * * Other useful properties: * document.head * document.title * document.body * document.cookie * document.documentElement (the <html> element) * document.URL * document.referrer * document.lastModified * document.location * document.domain, could use it to reduce the same-origin restrictions */ document.images[0].tagName; // => "IMG", HTML's tag names are always upper-case document.getElementsByTagName('img'); // lower-case for compatibility with XHTML // use a specified Element object (document.getElementById('content').getElementsByTagName('p'))[0].innerHTML; document.scripts[0].innerText document.links["skip-main"] document.links.skipsearch
By
class
attributedocument.getElementsByClassName();
With
class
attribute name or names(separated by whitespace);The name or names are case sensitive;
By CSS selectors
This is a modern approach to match an element.
document.querySelector(); // only return the first matched element document.querySelectorAll(); // return a non-live NodeList object
Could replace the
document
object with a specified Element object, only to match all descendants of the element except itself;CSS pseudo-elements will never return any elements;
When being invoked on an element, the two methods search the whole document first, then filter. Including ancestors of the element within the CSS selector is legal, but it is weird, likes a mistake;
Sample code here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll
DOM traversal
As tree of nodes
Use properties of the Node
object:
parentNode
childNodes
firstChild
lastChild
nextSibling
previousSibling
As tree of elements
Use properties of the Element
object:
children
firstElementChild
lastElementChild
previousElementSibling
nextElementSibling
Text and comment nodes are not elements, they are ignored.
<html>
...
<h2 id="properties">Properties</h2>
...
</html>
The <h2>
element is in the traversal path, its text “Properties” won’t be.
Element attributes
The Element
object has an attributes
property which is a live collection including all attributes of the element, also have a getAttributeNames()
methods which returns all attribute names.
Standard HTML attributes
These attributes are properties of an element, straightforward.
HTML attributes are not case sensitive, but JavaScript isn’t, the property names are lowercase. A few HTML properties are longer than one word, such as http-equiv
, converting them to the style: propNameLikeThis
.
If HTML attribute names are JavaScript reserved words, prefixing them with html
, but class
is an exception, it is className
, not htmlClass
.
<html>
...
<meta id="myMeta" http-equiv="X-UA-Compatible" content="IE=Edge">
...
<label id="myLabel" for="main-q" class="visually-hidden" draggable="false" style="">Search MDN</label>
...
</html>
document.querySelector('#myMeta').httpEquiv; // => "X-UA-Compatible"
document.querySelector('#myLabel').htmlFor; // => "main-q"
document.querySelector('#myLabel').for; // => undefined, for is a reserved word
document.querySelector('#myLabel').htmlClass; // => undefined
document.querySelector('#myLabel').className; // => "visually-hidden"
document.querySelector('#myLabel').draggable; // => false, convert it to a boolean, numbers also
document.querySelector('#myLabel').style; // => CSS2Properties(0), an object
Non-standard HTML attributes
Use methods:
getAttribute()
hasAttribute()
setAttribute()
removeAttribute()
getAttribute()
returns a string, or null
, or ""
.
Use hasAttribute()
to check for an attribute’s existence, not the return value of getAttribute()
.
setAttribute()
always converts its argument value to a string. It doesn’t care about boolean attributes’ actual value, they’re present, they’re true
, we can just set their value to ""
.
removeAttribute()
can be used to remove a standard attribute, but it just empties its value in Javascript, the HTML attribute in the web page will disappear.
data-*
attributes
HTML5 defines a standard way to attach additional data. Any attribute whose name is lowercase and begins with the prefix data-
is a valid custom data attribute. e.g.
<li data-item="Technologies" role="menuitem">
Valid custom data attribute names can only include lowercase letters, numbers, dash(-
), underscore(_
), dot(.
), colon(:
).
<li data-abc:123-efg_-h32.hi='1' data-item="Tech">
The property name in JavaScript is abc:123Efg_H32.hi
, removing data-
and dashes, the letters that follow a dash are transformed into its uppercase.
The dataset
property is a DOMStringMap
which stores all custom data attributes of the element and
is live, the property itself is read-only, but can read, write, delete its members.
Each property in dataset
is always a string.
// => DOMStringMap { "abc:123Efg_H32.hi" → "1", item → "Tech" }
document.querySelector('li').dataset;
document.querySelector('li').dataset.item; // => "Tech"
document.querySelector('li').dataset["abc:123Efg_H32.hi"]; // => "1"
document.querySelector('li').getAttribute('data-abc:123-efg_-h32.hi'); // => "1"
document.querySelector('li').dataset = {}; // do nothing, read-only
document.querySelector('li').dataset.item = "Technologies";
delete document.querySelector('li').dataset.item;
'item' in document.querySelector('meta').dataset; // => false
Notes:
- Don’t store content that should be visible and accessible in data attributes;
- Poor read performance;
More details here:
https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/dataset
https://bugzilla.mozilla.org/show_bug.cgi?id=802157
Element content
As HTML
innerHTML
property. It gets or replaces the HTML code contained within the element;outerHTML
property. LikeinnerHTML
, but applying to the element itself and its descendants;insertAdjacentHTML()
method. Inserting HTML code;
If an element has no parent element, setting its outerHTML
property does nothing.
The markup and formatting of the returned HTML by innerHTML
or outerHTML
is likely not to match the original page markup.
HTML5 doesn’t execute <script>
tag in innerHTML
and outerHTML
, but there are still other ways to execute JavaScript, don’t use them to insert untrusted HTML code or plain text (use textContent
property instead).
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML
https://developer.mozilla.org/en-US/docs/Web/API/Element/outerHTML
https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
As plain text
textContent
property. It gets the text content of an element and its descendants, or replaces them with a single text node;innerText
property. LiketextContent
, but the text is rendered;
textContent
returns concatenated text which is like innerHTML
returned HTML code, but stripping all HTML tags, so ignores <br>
, a newline is gone, but preserves tabs and spaces that are within these tags. It gets the content of all elements, including <script>
and <style>
elements.
innerText
returns concatenated text which is like we highlight an area of a web page with the cursor and then copy it, only shows human-readable elements, no <script>
, <style>
and hidden (e.g. display:none
) elements, these elements are not rendered.
The returned values by textContent
and innerText
are same if the element is not rendered, such as:
// => true
document.scripts[0].innerText === document.scripts[0].textContent
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent
Text in <script>
Could embed arbitrary text into <script>
, just set an invalid MIME type, to make the script non-executable.
let noexesrc = document.createElement('script');
noexesrc.setAttribute('id', 'whoknows');
noexesrc.setAttribute('type', 'text/x-whoknows'); // invalid MIME type
noexesrc.text='alert("Yeah")';
document.body.appendChild(noexesrc); // should be no popup dialog
// => "alert(\"Yeah\")"
document.querySelector('#whoknows').text; // text property is same to textContent
DOM tree manipulation
Creating nodes
Methods in the Document
object:
createElement()
createTextNode()
createDocumentFragment()
createAttribute()
createComment()
importNode()
adoptNode()
createDocumentFragment()
returns aDocumentFragment
object that is a miniDocument
object and has no parent. A common use is to assemble a DOM subtree within theDocumentFragment
object, then put the subtree (not theDocumentFragment
object itself) into current active DOM tree, this will empty the object, it likes a temporary nodes container.
Methods in the Node
object:
cloneNode()
Cloned node has a same id attribute, should modify it manually if it exists.
Inserting nodes
Methods in the Node
object:
appendChild()
insertBefore()
replaceChild()
Invoke these methods on the parent node and pass the child node to them.
Deleting nodes
Methods in the Node
object:
removeChild()
Invoke the method on the parent node and pass the child node to it.
Element coordinates
Document Coordinates: a point (x, y)
of an element is relative to the top-left corner of the document.
Viewport Coordinates: a point (x, y)
of an element is relative to the top-left corner of the viewport, the viewport is the portion of the browser window that actually displays document content, excluding browser menus, toolbars.
width, height of the document
document.documentElement.offsetWidth
document.documentElement.offsetHeight
width, height of the viewport
window.innerWidth
window.innerHeight
Properties in the Window
object:
innerWidth
innerHeight
The two properties’ value include scrollbars and borders.
scrollX
(alias:pageXOffset
, for compatibility)scrollY
(alias:pageYOffset
, for compatibility)Return the number of pixels that the document is currently scrolled horizontally or vertically.
Methods in the Window
object:
scroll()
scrollBy()
scrollTo()
Properties in the Element
object:
clientWidth
clientHeight
Return inner width, height of an element, including padding but excluding borders, margins, and scrollbars (if present). Scrollbars’ position are between padding and borders;
When they are used on the root element, they are width, height of the viewport, excluding scrollbars;
They return
0
for elements with no CSS or inline layout boxes.scrollWidth
scrollHeight
Minimum required width, height of an element to show without scrollbars, measured like
clientWidth
,clientHeight
, but including width, height of pseudo-elements;Don’t need a horizontal scrollbar if
clientWidth == scrollWidth
, the vertical scrollbar also.scrollLeft
scrollTop
Get or set the number of pixels that an element’s content is scrolled horizontally or vertically.
Methods in the Element
object:
scroll()
scrollBy()
scrollIntoView()
Scroll to the element;
Another way is to mark an anchor on the element, then change
location.hash
to the anchor;scrollTo()
getBoundingClientRect()
Returns the size of an element and its position relative to the viewport. The position includes padding and borders, no margins. The returned value is the union of the rectangles returned by
getClientRects()
.To convert the viewport coordinates to the document coordinates:
const box = e.getBoundingClientRect(); const x = box.left + window.scrollX; const y = box.top + window.scrollY;
getClientRects()
Like
getBoundingClientRect()
, but it returns a collection. Inline elements may consist of multiple rectangles, use it to query these individual rectangles.elementFromPoint()
elementsFromPoint()
Return the element or elements at the specified coordinates (relative to the viewport).
Properties in the HTMLElement
object:
offsetWidth
offsetHeight
Return width, height of an element, include padding, borders, scrollbars, no margins. They are opposite to
clientWidth
andclientHeight
;scrollWidth > offsetWidth
while a content overflow is happening.
Selected text
// https://developer.mozilla.org/en-US/search?q=querySelector
// the two methods are identical, but they don't work on <textarea> and <input>
// => "1,468 documents found for \"querySelector\""
window.getSelection().toString();
// document.getSelection().toString();
const searchInput = document.querySelector('#main-q'); // #main-q is the search box
// => "Selector"
searchInput.value.substring(searchInput.selectionStart, searchInput.selectionEnd);
Editable elements
Any HTML element can be editable.
document.designMode
makes the entire document editable. There is not a corresponding HTML attribute to it.
document.designMode
makes it is possible to transform a web page into a simple rich-text editor.
More details here:
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content
JavaScript and CSS
How to include CSS
Inline stylesheet
<h1 style="color:red">typeof</h1>
Using JavaScript
Embedded stylesheet
<head> <style> h1 { color: red } </style> </head>
Linked external stylesheet
<link href="style.css" rel="stylesheet">
Imported stylesheet
@import 'custom.css';
The styles which reside closest to the HTML tag will take the precedence, the above sequence is also the precedence order, JavaScript generated styles are same as the inline stylesheet.
Comments
Comments in CSS begin with /*
and end with */
.
Margin and padding
margin
specifies space outside the border. It provides visual space between an element and its neighbors in the normal document flow;padding
specifies space inside the border. The space is between the border and the element content;- No border,
padding
is not necessary.margin
is irrelevant if the element is dynamically positioned;
How to scripting CSS
With the style
property
The property is used to get as well as set the inline style of an element. The returned object is read-only, but can set value to its properties, all values must be string. Getting or setting the inline style as a single string to use elt.setAttribute()
or elt.style.cssText
(elt
is an element object).
Will get nothing if the element hasn’t inline styles, only has value if setting it by JavaScript code before or the element has inline styles. In general, only useful for setting styles.
Setting a property of the style object to null
or ""
will reset it, prefer ""
.
CSS properties in JavaScript:
/* like font-size */
elt.style.fontSize
/* like float, float is a reserved word */
elt.style.cssFloat
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style
https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model/Using_dynamic_styling_information
Window.getComputedStyle()
Use it to query the styles that actually apply to the element.
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle
With CSS classes
Two properties of the Element
object:
className
. It is the HTMLclass
attribute,class
is a reserved word in JavaScript;classList
. It is a live collection, includingadd()
,remove()
,toggle()
,replace()
methods;
More details here:
https://developer.mozilla.org/en-US/docs/Web/API/Element/classList
Web components
Web Components is a suite of different technologies to create reusable custom elements with encapsulated functionality.
It consists of three main technologies:
- Custom elements
- Shadow DOM
- HTML templates
More details here:
https://developer.mozilla.org/en-US/docs/Web/Web_components