1. 1. What it is
  2. 2. Identifiers
  3. 3. Comments
  4. 4. Statements
  5. 5. Data types
    1. 5.1. number
    2. 5.2. string
      1. 5.2.1. Template strings
    3. 5.3. boolean
    4. 5.4. null
    5. 5.5. undefined
    6. 5.6. Wrapper objects
  6. 6. Variables
    1. 6.1. Variable declaration
    2. 6.2. Untyped variables
    3. 6.3. Variable scope
  7. 7. Objects
    1. 7.1. Create new objects
    2. 7.2. Accessing properties
    3. 7.3. Getters/Setters
    4. 7.4. Prototype and inheritance
    5. 7.5. Object attributes
    6. 7.6. Property attributes
    7. 7.7. Testing properties
    8. 7.8. Enumerating properties
      1. 7.8.1. Only get non-enumerable properties
  8. 8. Arrays
    1. 8.1. Iterating arrays
  9. 9. Functions
    1. 9.1. Defining functions
      1. 9.1.1. Function descriptions
      2. 9.1.2. Function expressions
      3. 9.1.3. Function constructors
    2. 9.2. Function scope
    3. 9.3. Binding functions
    4. 9.4. Determining whether a function exists
    5. 9.5. Nested functions
    6. 9.6. Invoking functions indirectly
    7. 9.7. Closures
    8. 9.8. Function parameters
    9. 9.9. The arguments object
    10. 9.10. Generator functions
    11. 9.11. Arrow functions
  10. 10. Classes
    1. 10.1. Class declarations
    2. 10.2. Class expressions
    3. 10.3. Syntactical sugar
    4. 10.4. Notes
  11. 11. Operators
    1. 11.1. Operators and precedence
    2. 11.2. Bitwise operators
    3. 11.3. Comparison operators
    4. 11.4. in operator
    5. 11.5. instanceof operator
    6. 11.6. Logical operators
    7. 11.7. eval()
    8. 11.8. Conditional operator (?:)
    9. 11.9. delete operator
    10. 11.10. typeof operator
    11. 11.11. Spread operator
    12. 11.12. void operator
  12. 12. Expressions
    1. 12.1. Expression evaluation order
    2. 12.2. Destructuring assignment
      1. 12.2.1. Array destructuring
      2. 12.2.2. Object destructuring
  13. 13. Type conversions
    1. 13.1. Implicit conversions
    2. 13.2. Explicit conversions
      1. 13.2.1. Primitive-to-Object conversions
      2. 13.2.2. Object-to-Primitive conversions
  14. 14. Control flow
    1. 14.1. Block statement
    2. 14.2. if...else
    3. 14.3. switch
    4. 14.4. while
    5. 14.5. do...while
    6. 14.6. for
    7. 14.7. for...in
    8. 14.8. for...of
    9. 14.9. break
    10. 14.10. continue
    11. 14.11. label statements
    12. 14.12. return
    13. 14.13. try...catch
    14. 14.14. debugger
  15. 15. Strict mode
    1. 15.1. Why to use
    2. 15.2. What changed
    3. 15.3. eval() in strict mode
    4. 15.4. arguments in strict mode
  16. 16. Asynchronous JavaScript
    1. 16.1. Promise
    2. 16.2. Async / await
  17. 17. Meta programming
  18. 18. JavaScript modules
    1. 18.1. Applying modules
    2. 18.2. Creating a module object
    3. 18.3. Classes
    4. 18.4. Aggregating modules
    5. 18.5. Dynamic module loading
    6. 18.6. Other module solutions
      1. 18.6.1. The revealing module pattern
      2. 18.6.2. CommonJS and AMD
  19. 19. JavaScript and HTML
    1. 19.1. DOM (Document Object Model)
    2. 19.2. window object
    3. 19.3. location object
    4. 19.4. history object
    5. 19.5. navigator object
    6. 19.6. screen object
    7. 19.7. Selecting HTML elements
    8. 19.8. DOM traversal
      1. 19.8.1. As tree of nodes
      2. 19.8.2. As tree of elements
    9. 19.9. Element attributes
      1. 19.9.1. Standard HTML attributes
      2. 19.9.2. Non-standard HTML attributes
      3. 19.9.3. data-* attributes
    10. 19.10. Element content
      1. 19.10.1. As HTML
      2. 19.10.2. As plain text
    11. 19.11. Text in <script>
    12. 19.12. DOM tree manipulation
      1. 19.12.1. Creating nodes
      2. 19.12.2. Inserting nodes
      3. 19.12.3. Deleting nodes
    13. 19.13. Element coordinates
    14. 19.14. Selected text
    15. 19.15. Editable elements
  20. 20. JavaScript and CSS
    1. 20.1. How to include CSS
    2. 20.2. Comments
    3. 20.3. Margin and padding
    4. 20.4. How to scripting CSS
      1. 20.4.1. With the style property
      2. 20.4.2. With CSS classes
  21. 21. Web components

Know JavaScript

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 local y 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() or Object.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 by for...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(), returns true if the property belongs to the object itself, not the prototype objects;
  • propertyIsEnumerable(), returns true only if hasOwnProperty() returns true and the enumerable attribute is true;
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 always undefined.

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 is true;
  • 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 is true;
  • 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 to 1, false to 0, 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 is false;

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

    1. Check toString() returned value, overrided toString() can return any value. If the value is primitive, then convert it to a string(if necessary), done;
    2. Check valueOf(). If the value is primitive, then convert it to a string(if necessary), done;
    3. Throw TypeError;
  • To convert an object to a number

    Similar to string, but check valueOf() first, then toString();

    [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 as String, 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() and arguments 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.

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 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 the id attribute.

  • By name attribute

    document.getElementsByName();  // return a live NodeList object

    Only can be used by the document object. The name attribute is only valid on a handful of HTML elements, such button, input;

    It is deprecated on <img> element.

    More details here:
    https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes

  • By 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 a HTMLCollection object again to retrieve changes, the NodeList 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 attribute

    document.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. Like innerHTML, 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. Like textContent, 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 a DocumentFragment object that is a mini Document object and has no parent. A common use is to assemble a DOM subtree within the DocumentFragment object, then put the subtree (not the DocumentFragment 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 and clientHeight;

    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 HTML class attribute, class is a reserved word in JavaScript;
  • classList. It is a live collection, including add(), 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