javascript_basic

Javascript

  • The single quote/double quotes patterns are identical in terms of functionality
  • ; can be ignore if unnecessary, but it’s not a good habit.
  • There’s no Integer in JavaScript (except BigInt), all are float(64 bits).
  • Index starts from 0, like for String and Array.

JS behaves like C style for supports operators.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
+   Add
- Minus
* Multiple
/ Divid
% Modulo
& AND
| OR
^ XOR
~ NOT
<< Shift Left
>> Shift Right With Sign
>>> Shift Right With Zero Fill

== Equal
!= Not equal
=== Equal using strict methods both value and type are same
!== Not equal using strict methods

> Greater than
< Less than
>= Greater than or equal to
<= Less than or equal to
in check key of an object
instanceof Is an instance of an Object

delete Removes a property from an object who owns it, the inherited property can NOT deleted.
void Returns undefined
typeof Returns a string representing the data type
++ Increments a number
-- Decrements a number
+ Converts the operand to a number
- Negates the operand
~ Bitwise NOT
! Logical NOT

*= Multiplies the left operand by the right operand
/= Divides the left operand by the right operand
%= Provides the division remainder (modulus) of the left and right operand
+= Adds the right operand to the left operand
-= Subtracts the right operand from the left operand
<<= Bitwise left shift
>>= Bitwise right shift
>>>= Bitwise unsigned right shift
&= Bitwise AND
^= Bitwise XOR
|= Bitwise OR

printing in JS

Printing is an easy way to show result, there are several ways to print variable value. here we go.

  • in one line
    1
    2
    console.log("hello \
    jason");
  • in multiple lines
    1
    2
    3
    console.log("hello \n jason");
    console.log(`hello
    jason`);//template string
  • show one parameter at last position
    1
    2
    3
    const name = "jason";
    console.log("hello", name); # a space is added between after 'hello' automatically
    console.log("hello %s", name); # no auto added space
  • show multiple parameters at different positions
    1
    2
    const msg = "welcome";
    console.log("hello %s msg %s", name, msg);
  • variable can be at any position
    1
    2
    3
    4
    console.log(name, "hi"); #has auto added space before 'hi'
    console.log(name, "hi", name); # has auto added space before and after 'hi'
    console.log(name, "hi %s", name); # NOT support, %s must be in the first string if has
    console.log(name + "hi %s", name); # support
  • template string
    1
    console.log(`${last_name} hi`); // last_name is name of a variable

Types

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
==========================javascript types=============================================
type
|
|
primitive--------+----+object
| |
+-----------+-----+ +---------+-------+--------+------+----------+
| | | | | | | |
| | | | | + | +
string number Number String Array Map Math Custom(class)
+ + ^ ^
| | auto-box | |
| +--------------+ |
| |
| auto-box |
+--------------------------------------------+

always use primitive string and number as they have high performance even in es6!

======================================================================================

auto-boxing

javaScript has two main type categories, primitive(string, number, boolean) and object.

1
2
3
4
// primitive
const s = 'test';
// object, ss's value can NOT change as well even it's an object.
const ss = new String('test');

A primitive is converted to its wrapper type(auto-boxing) only when a method of the wrapper type is invoked, when auto-boxing happens, a temporary object is created and destroyed after that call.

It’s a primitive data type. It has no methods, it is nothing more than a pointer points to a raw data memory reference, which explains the much faster random access speed than object.

So what happens when you do s.charAt(i) for instance?

1
s[0] == s.charAt(0)

Since s is not an instance of String, JavaScript will auto-box s, which has typeof string to its wrapper type, String, with typeof object or more precisely.

The auto-boxing behaviour casts a back and forth to its wrapper type as needed.

If you want to force the auto-boxing or to cast a primitive to its wrapper type, you can use Object.prototype.valueOf, but the behaviour is different. Based on a wide letiety of test scenarios auto-boxing only applies the ‘required’ methods, without altering the primitive nature of the letiable. Which is why you get better speed.

Set on wrapped temporary object has no lasting effect as that temporary is destroyed soon after

1
2
3
const str = 'hello';
str.age = 10; // a temporary String object is created and set.
console.log(str.age); //undefined as the temporary object is destroyed.

Numbers and Dates

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function number_date() {
const a2 = 0b11;
const a16 = 0x10;
const n_number = 12;
const n_object = new Number(12) //object
console.log(a2, a16, n_number, n_object);

//Math class
console.log(Math.PI, Math.random(), Math.round(4.3), Math.min(1, 2, 3), Math.max(...[1, 2, 3]))
//Date class library
const today = new Date();
console.log(today.toString(), today.getMilliseconds(), today.getHours(), )
console.log(3 / 2);
console.log(Math.floor(3 / 2));

//random float number from [0-1)
console.log(Math.random());
//random integer from [0-10)
console.log(Math.floor(Math.random() * 10));

console.log(1 + 2 == 3); // true
console.log(0.1 + 0.2 == 0.3); // false, never compare float!!!
}
number_date()
3 16 12 [Number: 12]
3.141592653589793 0.3273509254139666 4 1 3
Wed Nov 04 2020 18:26:24 GMT+0800 (China Standard Time) 700 18
1.5
1
0.8324244498379996
2
true
false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function string_number_convert() {
// use parseInt and parseFloat which are more poweroff than Number()
console.log(Number('123'));
console.log(Number(' 123'));

//not a number
console.log(Number('123a')); //NaN

console.log(parseInt('123', 10));
console.log(parseInt(' 123', 10));
// still get a number
console.log(parseInt(' 123a', 10)); // 123

//convert string to number

//valueOf(valueOf is called when number is needed) is called implicitly,
console.log('3' - 1);
// call valueOf explicitly.
console.log('3'.valueOf() - 1);

// convert number to string
const num = 123;
console.log(num.toString());
console.log(String(num));
console.log( 2 + 'hello'); //toString is called implicitly when string is needed.
}

string_number_convert()
123
123
NaN
123
123
123
2
2
123
123
2hello

Special number NaN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function sepcial_number() {
console.log('special number: Infinity and -Infinity,NaN are numbers \nNaN: any number of other numeric operations that don not yield a meaningful result.');
console.log(Infinity);
console.log(-Infinity);
console.log(typeof NaN);
// NaN is for **numeric operations** that don not yield a meaningful result
console.log(0/0);
console.log('a' * 2);
// Strings cannot be divided, multiplied, or subtracted
console.log('a' - 1);
// NaN meaningless result, hence NaN==NaN is false!!!
console.log(NaN == NaN)
// check its value if it's NaN or Not
console.log(Number.isNaN(NaN));
console.log(Number.isNaN('1'));
console.log(Number.isNaN('a'));
console.log(Number.isNaN(1));

// isNaN check if the parameter is number or not
// not a number
console.log(isNaN('1'));
console.log(isNaN('a'));
console.log(isNaN(1));
}
sepcial_number()
special number: Infinity and -Infinity,NaN are numbers
NaN: any number of other numeric operations that don not yield a meaningful result.
Infinity
-Infinity
number
NaN
NaN
NaN
false
true
false
false
false
false
true
false

String

Sting index starts with 0, not support -1 as the last index.

There are two ways to create a string, one uses string primitive, the other uses String object.

1
2
3
let s_hi = "Hello";
let s_hi_o = new String("Hello")
//The String object is a wrapper around the string primitive data type

But you can call any of the methods of the String object on a string literal value—JavaScript automatically converts the string literal to a temporary String object, calls the method, then discards the temporary String object

strings are immutable array-like objects, you can’t change individual characters.

There is another big feature for string is template string, you access var from string directly, more like Bash, it’s easy to create complex string by accessing var.

1
2
3
4
let name = "jason";
console.log("hi ${name}");
let msg = "hi ${name}";
consolg.log(msg);

run js from string

1
2
3
4
5
function eval_demo() {
eval('var x = 10;');
console.log(x);
}
eval_demo();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function string_demo() {
//convert 100 to string
let s_n = String(100);
console.log(s_n, "hi number " + 100);
let s_hi = "9hello, boy boy ";
s_hi[0] = "B"; //no effect, but no exception.
// last index: -1, not supported by function!!! s_hi[-1] not supported!!!
console.log(s_hi, s_hi[0], s_hi.length,
// auto-box happens
'\n' + s_hi.indexOf("ll"), //check substring index
'\n' + s_hi.slice(1, -1), //slice, from index 1 to index -1
'\n' + s_hi.substr(1, 2), //from index 1, include 2 bytes
'\n' + s_hi.toUpperCase(),
'\n' + s_hi.replace("ll", "xx"),//replace substring, support regular pattern
'\n' + s_hi.startsWith("he"),
'\n' + s_hi.endsWith("xx"),
'\n' + s_hi.includes("ll"),
'\n' + s_hi.split(","),
'\n' + s_hi.trim(),//trim tab, space from start and end.
'\n' + s_hi.repeat(3)
); //repeat string
}
string_demo()
100 hi number 100
9hello, boy boy  9 16
3
hello, boy boy
he
9HELLO, BOY BOY
9hexxo, boy boy
false
false
true
9hello, boy boy
9hello, boy boy
9hello, boy boy 9hello, boy boy 9hello, boy boy

some String APIs support regex

when use with regex with such API, patter must be written in way /pattern/, not "pattern".

  • search: check if match return -1 if no match
  • match: got the matched value, return null if no match
  • replace: repalce matched part, return a new string

match: return an object that looks like an array, if no matched, null returned

  • index: the start matched index
  • [0]: the full matched string
  • [1]…[n]: the substring matched in ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function string_regex_api() {
let s_hi = "9hello, boy boy ";
console.log(s_hi.search(/[0-9]h/g), s_hi.search(/[0-9]b/g));

// The $1 and $2 in the replacement string refer to the parenthesized groups in
// the pattern. $1 is replaced by the text that matched against the first group, $2
// by the second, and so on, up to $9. The whole match can be referred to with $&.
console.log(s_hi.replace(/(hello), (boy)/g, '$2, $1'));//NOT '/boy/g'

console.log(s_hi.replace('boy', 'girl')); // only first replace without pattern


let matched = s_hi.match(/9([a-z]*)/);
console.log(matched.index, matched[0], matched[1]);

//dynamic pattern, must create RegExp explictly with dynamic value.
let pattern = '9[a-z]';

let reg = new RegExp(pattern);
console.log(s_hi.match(reg)[0])
}
string_regex_api()
0 -1
9boy, hello boy
9hello, girl boy
0 9hello hello
9h

Array

Array element can have different types, but most time, it stores same type.

Like string, there are two ways to create an array, but both created object

1
2
3
4
5
let array_p = ['a', 2];
let array_o = new Array(["a", 2]);
//let array_o = new Array();
array_o[2] = "c";

Array supports method same like Python

  • pop(), push(elm) at end
  • shift(), unshift(elm) at head
  • reverse, sort
  • join
  • find: return the first element, filter use callback for testing, return a new filtered array.
  • indexOf/lastIndexOf: return index or last of the element, -1 if not found
  • includes: like indexOf, but return true or false.
  • forEach, map, reduce, filter always use this if possible to avoid for loop
    • forEach,map, reduce, filter provides second argument(thisArgs) used as this by callback handler
  • slice(subarray, copy array if no parameter), concat(combine two arrays): new array is returned, orignal unchanged
  • some/every: return true if all/some pass test.
  • Array.isArray(obj): return true if an arry

check an element in an array with several ways

  • for to loop every element
  • indexOf
  • includes

NOTE: index from 0 and element can be any type, not same

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function check_array_type() {
// array is an object with constructor == Array
var arr = [];
console.log(typeof arr);
if (typeof arr == 'object' && arr.constructor == Array) {
console.log('var arr is an array');
}

}

check_array_type()

function array_demo() {
function is_number(val) {
return !isNaN(val);
}
let array_p = [1, 'a', 2];
array_p.push(3); //add one at tail
array_p.shift(); //remove one from head
console.log(array_p,
//auto-box
array_p.concat([3, 4, 5]),
array_p.slice(), //copy array
array_p.slice(1),
array_p.slice(1,2), //sub array index from [1, 2)
array_p.toString(),
array_p.find(is_number), //first number in array
array_p.indexOf(3),
array_p.indexOf(4),
array_p.includes(3),
array_p.filter(is_number),
array_p.join(';'));

// good way to use but can not break at some point
array_p.forEach(function(elm) {
console.log(elm);
})
//better new way to iterator array, can break at some point
for(let elm of array_p) {
console.log(elm);
}
for(let elm in array_p) {
console.log(elm); //print index 0, 1, 2
}

//map, reduce, filter
let array_n = [1, 2, 3];
//map takes every element as input and return a value to contruct a new array
console.log(array_n.map((x)=> x+2));
//filter takes every element as input, return true or false, only true element contruct a new array
console.log(array_n.filter((x)=>{if (x>1) return true; else return false;}))
//reduce takes parameter, previous call return value and current element, the final output is last call output
console.log(array_n.reduce((prereturn, x)=>prereturn + x))
console.log(array_n.every(x=>x>2));
console.log(array_n.some(x=>x>2));
}
array_demo()
object
var arr is an array
[ 'a', 2, 3 ] [ 'a', 2, 3, 3, 4, 5 ] [ 'a', 2, 3 ] [ 2, 3 ] [ 2 ] a,2,3 2 2 -1 true [ 2, 3 ] a;2;3
a
2
3
a
2
3
0
1
2
arrCustom
[ 3, 4, 5 ]
[ 2, 3 ]
6
false
true

delete an element from array

Remove?
An item array.splice(index, 1)
First item array.shift()
Last item array.pop()
What about delete? Try to avoid delete, causes sparse arrays.

delete do NOT change length and leave empty slot! others NOT

Using delete creates these kinds of holes. It removes an item from the array, but it doesn’t update the length property. This leaves the array in a funny state that is best avoided.

1
2
3
4
5
6
7
8
9
10
11
function array_del() {
var array = ['a', 'b', 'c', 'd'];
delete array[1];
console.log(array, array.length);
array = ['a', 'b', 'c', 'd'];
//first argument is element index!!!, the second is from that index how many elements will be deleted.
array.splice(1, 1);
console.log(array, array.length);
}

array_del()
[ 'a', <1 empty item>, 'c', 'd' ] 4
[ 'a', 'c', 'd' ] 3

MAP

Map is a litte different with Array, map uses keys as index while Array uses number.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function map_demo() {
let map_o = new Map();
//map_o["a"] = 1; //this never update size, never use this way!!!
map_o.set("b", 2);
//map_o.delete("b");//delete map_o['b']

console.log(map_o,
map_o.size,
map_o.get('b'),
// check key exist
map_o.has('b'));
let i = 0;
let iter = map_o.keys();
while (i < map_o.size) {
let key = iter.next().value;
console.log("item: %s %s", key, map_o.get(key));
i++;
}

map_o.forEach(function(value, key) {
//can loop each Map item, but can't break/continue somehow!!
console.log(key, value)
})

//new ways, should we use for map!!!
for(let [k, v] of map_o) {
console.log(k, v);
}
for(let k of map_o.keys()) {
console.log(k);
}
}
map_demo();
Map { 'b' => 2 } 1 2 true
item: b 2
b 2
b 2
b

Enum

There is no Enum in javacript at all, use object to simulate it

1
2
3
4
5
6
7
8
9
10
function enum_demo() {
const size = {SMALL: 0, MEDIUM: 1, LARGE: 2};

var count = [];
count[size.SMALL] = 1;
count[size.MEDIUM] = 2;
count[size.LARGE] = 3;
console.log(count)
}
enum_demo()
[ 1, 2, 3 ]

Function

arguments

If you pass too many, the extra ones are ignored. If you pass too few, the missing parameters get assigned the value undefined, but you can access all arguments by arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function test_arg(a, b, c = 12)
{
console.log(a, b, c,);
for ( let i = 0; i < arguments.length; i++) {
console.log(arguments[i]);
}
}

test_arg(1);
/*
1 undefined 12
1
*/
test_arg(1, 2, 4, 5);
/*
1 2 4
1
2
4
5
*/

Function closure

This feature—being able to reference a specific instance of a local binding in
an enclosing scope—is called closure. A function that references bindings from
local scopes around it is called a closure

1
2
3
4
5
6
7
8
9
10
var f2 = (function(){
var counter = 1;
return function(){ return counter++;};
})()
// we calls first function(local counter =1) which can be accessed by inner function
// after return, the local counter is not freed at all as f2 still have a reference to it!!!

//counter as a global to f2
f2(); //we call the second function
f2(); //we call the second function

Another case

1
2
3
4
5
6
7
function makeAdder(a) {
return function(b) {
return a + b;
};
}
var add5 = makeAdder(5);
add5(6); //

when makeAdder() is called, a scope object is created with one property: a, which is the argument passed to the makeAdder() function. makeAdder() then returns a newly created function. Normally JavaScript’s garbage collector would clean up the scope object created for makeAdder() at this point, but the returned function maintains a reference back to that scope object. As a result, the scope object will not be garbage-collected until there are no more references to the function object that makeAdder() returned.

Arrow function

As js supports annymous function(a function without name), more over, you can even skip function keyword for annymous function, that’s arrow function =>, like annymous function, you can use arrow function as a callback directly, or save it as function object, call it later on

There are severls rule to define an arrow function.

parameter rule

  • If there are 0 Parameters, arrow functions use empty parenthesis: () => { statements; }
  • If there is 1 Parameter, arrow functions can omit the parenthesis around the parameter: parameter => { statements }
  • If there are 2+ Parameters, parameters go inside parenthesis: (param1, param2, …) => { statements }

return rule

  • If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword.
  • if you arrow function does NOT return, say just long or printing, you can NOT omit brackets, that means brackets is not omitted in most case, with brackents if you want to return a value, you must use return keyword, otherwise, undefined is returned.

Definition and Call

1
2
3
4
5
6
7
8
9
10
11
12
13
14

function fun_demo() {
var arrow1 = name=>name;
var arrow2 = ()=>{console.log("hi");} //no return undefined returned
var arrow3 = (a, b)=>a+b;
var arrow4 = (a, b)=>{a+b;}; //undefined return as with brackets, need a return value, must use `return`
var arrow5 = (a, b)=>{return a+b;};
console.log(arrow1("jason"));
console.log(arrow2()); //print 'hi' return undefined
console.log(arrow3(1,2))
console.log(arrow4(1,2))
console.log(arrow5(1,2))
}
fun_demo()
jason
hi
undefined
3
undefined
3

Class/Object/object

object

There are two ways to declare a class ES5 way, ES6(ES2015), object is light weighted Object, object no built-in properties and methods, it’s more effecient thant Object, object behaves like a Map, key:value pairs, but it’s not a map hence object.get('a') will report error as object no built-in stuff.

  • object key must be string, but Map key can be anything.

  • Object.assign() copies the values (of all enumerable own properties) from one or more source objects to a target object. It has a signature of Object.assign(target, …sources).

The target object is the first parameter and is also used as the return value.

  • Object.assign() is useful for merging objects or cloning them shallowly.

property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let a = 1, b = 2;

// shorthand to create an object
let ob1 = {a, b}; // same as let ob1={'a':1, 'b':2};

let ob1 = {
name: 'jason',
'your name': 'jason' //'' is required as 'your name' has space
};

// two ways to access name property.
console.log(ob1.name, ob1['name']);
// only one way for 'your name' property.
console.log(ob1['your name']);

// add one property
ob1.age = 10;

// merge two objects(ob1 is returned and modified, the key conflicts, the last source wins
Object.assign(ob1, {age: 100, id: 100});
console.log(ob1);

// clone an object
let clone_ob = Object.assign({}, ob1);
console.log(clone_ob);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function object_demo() {
// object instance of it's own property and functions
let obj = {
"your name": "jason", // NOT ; but , "" is required
id: 16, // no quote is OK!

get_name_again: function get_name_again() {
return this['your name'];
},
//shorthand to define a method
get_name() {
return this['your name'];
},
}
obj.get_name_v2 = function() {
return this['your name'];
}
// two ways to access method
console.log(obj.id, obj.get_name());
console.log(obj.id, obj["get_name_again"]());
console.log(obj.id, obj.get_name_v2());

for (let key in obj) {
//loop all attributes and its value
console.log(key, obj[key]);
}

let a = 1;
let b = 2;
// shorthand to create an object
let ob1 = {a, b}; // same as let ob1={'a':1, 'b':2};
console.log(ob1);

let ob2 = {'a': 1, "b": 2};
ob2['c'] = 3;
// delete a property
delete ob2.a
console.log(ob2,
ob2.c,
ob2['b'],
// check key exist
'b' in ob2)
//get keys of object, return an array of keys
console.log(Object.keys(ob2));

let ob3 = {a: 1};
let ob4 = {a: 1};
// compare object, it compares its address, hence it's false.
console.log(ob3 == ob4);
}
object_demo()
16 jason
16 jason
16 jason
your name jason
id 16
get_name_again [Function: get_name_again]
get_name [Function: get_name]
get_name_v2 [Function]
{ a: 1, b: 2 }
{ b: 2, c: 3 } 3 2 true
[ 'b', 'c' ]
false

Class

ES5 class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Person(name) {
this.name = name;
}

Person.prototype.get_name = function() {
return this.name;
}

function Student(name, id) {
Person.call(this, name); // call super class constructor bound to same this as Student.
this.id = id;
}
// same prototype as parent(extended),hence Student can see get_name as well!!!
Student.prototype = Object.create(Person.prototype);
Student.prototype.get_id = function() {
return this.id;
}
Student.prototype.prefix_name = function() {
return 'prefix_' + this.name;
}

let s1 = new Student('jason', 10);
console.log(s1.get_name(), s1.prefix_name(), s1.get_id()); // jason prefix_jason 10

With ES6, add keyword like class, construtor, static, super, extends

1
2
3
4
5
6
7
8
9
class Person {
constructor(name) {
this.name = name;
}
// no function keyword
get_name() {
return this.name
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function es5_class_demo() {
//constructor function always with UpperCase first letter
function Person(name, id) {
'use strict'; // call me without new will throw an error!!
this.name = name;
this.id = id;
}
Person.prototype.get_name = function() {
return this.name;
}
//Outside method defines.
Person.prototype.set_name = function(name) {
this.name = name;
}

// all instances have these, outside the contructor
Person.prototype.age = 10; //static, shared by all instances

let p1 = new Person('jason', 1);
p1.gender = 'male'; //only to this instance, add new attribute, do not use this way
let p2 = new Person('cob', 2);
p2.get_id = function() { //add method to an existing object
return this.id;
}
console.log(p1.get_name());
console.log(p2.name);

console.log(p2.get_id());
p2.set_name("frank");
console.log(p2.get_name());
console.log(p2.age);
}

es5_class_demo()
jason
cob
2
frank
10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function es6_class_demo() {
class Person {
constructor(name) {
this.name = name;
}
get_name() {
return this.name;
}
}

class Student extends Person {
constructor(name, id) {
// call parent constructor!!!
super(name);
this.id = id;
// parent and subclass has the same name property
// actuall the two property are same one!!! on instance object!
// never do this, avoid subclass has same name property with parent!!!
this.name = 'hello';
}
get_id() {
return this.id
}
get_prefix_name() {
// call parent's function in child
return 'prefix_' + super.get_name();
}
}
const s1 = new Student("jason", 1);
console.log(s1.get_name(), s1.get_id(), s1.get_prefix_name());
}
es6_class_demo();
hello 1 prefix_hello

loop properties

  1. for…in: which returns all the enumerable properties of an object, regardless of whether they are own properties, or inherited from the prototype chain.
    • 返回对象自身的和继承的可枚举属性(不含 Symbol 属性)。
  2. Object.keys(ob): which returns all own, enumerable properties of an object, an ECMA5 method
    • 返回一个数组,包括自身的(不含继承) 所有可枚举属性(不含 Symbol 属性)。
  3. Object.getOwnPropertyNames(obj): which returns all own properties of an object, both enumerable or not
    • 返回一个数组,包括自身的(不含继承) 所有属性(不含Symbol属性,但包括不可枚举属性)。
  4. Object.getOwnPropertySymbols(obj): which returns all own symbol properties of an object, enumerable or not
    • 返回一个数组。包含对象自身(不含继承)的所有 Symbol 属性。
  5. Reflect.ownKeys(obj)
    返回一个数组,包含对象所有的属性(不含继承)== 3 + 4 。
  6. obj.hasOwnProperty(prop): check if a property is inherited or actually belongs to that object
  7. obj.propertyIsEnumerable(prop) if a property is enumerable.

use Object.defineProperty() to define non-enumerable property

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function loop_properties() {
let get_id = Symbol("get_id");
let plain_object = {
id: 12,
name(){return 'plain'},
// define a symbol property with [Symbol]
[get_id]() {
return this.id
}
}

//add non-enumerable property, you never see me by for ...in
Object.defineProperty(plain_object, "get_name", {
value: function() {
return 'get_name';
},
writable: true,
enumerable: false,
configurable: false
});

for (let attr in plain_object) {
// no get_name printed here as it's not enumerable!!!
console.log(attr);
}
let id = Symbol("id");
// add a symbol properity to an object.
plain_object[id] = 1000;

// access a symbol function.
console.log(plain_object[get_id]());
console.log(Object.keys(plain_object));
console.log(Object.getOwnPropertyNames(plain_object));
console.log(Object.getOwnPropertySymbols(plain_object));
console.log(Reflect.ownKeys(plain_object));

// plain_object.hasOwnProperty = 10;
// not safe if hasOwnProperty is set
console.log(plain_object.hasOwnProperty('id'));
var hasOwn = {}.hasOwnProperty; //extract or Object.prototype.hasOwnProperty
hasOwn.call(plain_object, 'id');
}

loop_properties()
id
name
12
[ 'id', 'name' ]
[ 'id', 'name', 'get_name' ]
[ Symbol(get_id), Symbol(id) ]
[ 'id', 'name', 'get_name', Symbol(get_id), Symbol(id) ]
true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function getAllPropertyNames(obj) {
// own, inherited properties, enumerable and non-enumerable!!!
let result = [];
while (obj) {
Object.getOwnPropertyNames(obj).forEach(p => result.push(p));
result.push('#');
//get parent prototype!!!
obj = Object.getPrototypeOf(obj);
}
return result;
}

{
let obj = {
abc: 123,
xyz: 1.234,
foobar: "hello"
};
console.log(getAllPropertyNames(obj));

// obj1 has no inherited property!!!
let obj1 = Object.create(null);
obj1.abc = 123;
obj1.xyz= 1.234;
obj1.foobar = "hello";
console.log(getAllPropertyNames(obj1));
}
[
  'abc',              'xyz',
  'foobar',           '#',
  'constructor',      '__defineGetter__',
  '__defineSetter__', 'hasOwnProperty',
  '__lookupGetter__', '__lookupSetter__',
  'isPrototypeOf',    'propertyIsEnumerable',
  'toString',         'valueOf',
  '__proto__',        'toLocaleString',
  '#'
]
[ 'abc', 'xyz', 'foobar', '#' ]

Symbol

A “symbol” represents a unique identifier, it’s mostly used to avoid conflict, you can image it as a unique string.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// id is a symbol with the description "id"
let id1 = Symbol("id"); // id1 is not in global registry!!!!
let id2 = Symbol("id");
//They are not equal, but only the description is same for debug meta data
id1 == id2; // false!!!


// id is a symbol with the description "id"
let id1 = Symbol.for("name"); //create if not found in global registry!!
let id2 = Symbol.for("name"); //check global registry with 'name' key, return if found

console.log(id1 == id2); //true

let id3 = Symbol("name");
let id4 = Symbol.for("name");
console.log(id3 == id4); // false

That call checks the global registry, and if there’s a symbol described as key, then returns it, otherwise creates a new symbol Symbol(key) and stores it in the registry by the given key.

Symbols don’t auto-convert to a string, and Symbolic properties do not participate in for..in loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function symbol_demo() {
let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1.toString(), id2.toString());

let id = Symbol("id");
let name = Symbol("name")
let user = { // belongs to third-party code
age: 30,
[name]: "John",
// MUST use [] to refer to symbol.
//no one else can change the value outside of third-party, it' safe for library

//without function keyword
[id](){
return 10;
},
//with function keyword
get_age: function() {
return this.age;
}
};
console.log(user[name], user[id]());

//Symbolic properties do not participate in for..in loop.
for(let att in user) {
console.log(att, user[att]);
}
console.log(user.get_age())
}

symbol_demo()
Symbol(id) Symbol(id)
John 10
age 30
get_age [Function: get_age]
30

Iterator/Generator

Iterator

Iterator is an object, with next method, it wrapps an object(string, array, map) to make it iterable, so that you can iterate the wrapped object by for/of, most of time, you did not call iterator at all, but now how see how it works.

1
2
3
4
5
6
7
8
// String, Array, Map etc has a property Symbol.iterator which returns an Iterator.
let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// . {value: "O", done: false}
console.log(okIterator.next());
// . {value: "K", done: false}
console.log(okIterator.next());
// . {value: undefined, done: true}

Iterator Abstract Interface required
First let simulate an Iterator which has next() method, it returns an object which has two keys value and done

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//user is an object.
let user = {
age: 10
}

function makeIterator(array) {
let index = 0;
return {
next() {
return index < array.length?
{value: array[index++], done: false} :
{value: undefined, done: true};
}
}; /* here return an object instance which has next method */
}
let iterS = makeIterator([99,88]);
console.log(iterS.next());
console.log(iterS.next());
console.log(iterS.next());


let arr1 = [1];
let iter = arr1[Symbol.iterator](); /*!get Iterator of array!*/
console.log(iter.next()) /* value: 1 done: false */
console.log(iter.next()) /* value: undefined, done: true */

Iterator

Generator

When you define a function with function* (placing an asterisk after the word function), it becomes a generator. When you call a generator, it returns an iterator.

Every time you call next on the iterator, the function runs until it hits a yield expression, which pauses it and causes the yielded value to become the next value produced by the iterator. When the function returns (the one in the example never does), the iterator is done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function generator_demo() {
//Generator!!
function *helloWorldGen(){
let x;
yield 'hello';
x = yield 'world';
// called get 'world' but x undefined if next doesn't take parameter for next call!
return x;
}
//generator return an Iterator.
let hw_gen = helloWorldGen();
console.log(hw_gen.next());
console.log(hw_gen.next());
// when next takes parameter, it indicates the last yield express value used internal!
console.log(hw_gen.next(2)); //console.log(hw_gen.next());

for (let elm of helloWorldGen()) {
console.log(elm);
}



let objiter = {
data: ["kk", "dd"],

//* means a Generator, if you call generator, it returns an Iterator
*[Symbol.iterator](){
for(let item of this.data) {
//yield like return, but next call still goes here
yield item;
}
}
};

for (let elm of objiter) {
console.log(elm);
}
}
generator_demo()
{ value: 'hello', done: false }
{ value: 'world', done: false }
{ value: 2, done: true }
hello
world
kk
dd

Async operation

There are couples of ways of syanc operation, from ES5(callback), ES6(Promise), ES7(async/await, really new), most of them just wants to make easier to understand and use, here we go

Promise(ES6)

Create Promise by three ways

  • one is use new call resolve or reject based on condition —>common way
  • the other two are call resolve() or reject directly

Promise.resolve('hello') and Promise.reject('hello') set Promise's state with Resolved or Rejected!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//Basic Implementation of Promise
/* 1. each Promise only take one function as its parameter
* 2. must provide built-in method, then, resolve, reject
* 3. user passed function when create a Promise and call the passed function with built-in resolve, reject methond
* 4. built-in then() used to add resolve callbacks and failed callback to proper queue.
* 5. built-in resolve and reject called callbacked in the proper queues added by then.
* 6. built-in resolve and reject are called by caller at proper time.
*/

function my_promise_demo() {
let MyPromise = function(fn) {
let state = 'pending';
let doneList = [];
let failList= [];
this.then = (done ,fail) => {
switch(state){
case 'pending':
if(done)
doneList.push(done);
if(fail)
failList.push(fail);
return this;
// no need to add to the queue as state is settled.
case 'fulfilled':
done();
return this;
case 'rejected':
fail();
return this;
}
}
// function keyword can't be omitted in function class! can be ignore in class
function tryToJson(obj) {
let value;
try {
value = JSON.parse(obj);
} catch (e) {
value = obj;
}
return value
}
//user calls resolve and rejct with proper value
//then resolve passed such value to done callbacks added by then()
function resolve(newValue) {
state = 'fulfilled';
setTimeout(() => {
let value = tryToJson(newValue);
for (let i = 0; i < doneList.length; i++){
//call done hander added by then
let temp = doneList[i](value);
if (temp instanceof MyPromise) {
let newP = temp;
//done handler returns a new Promise.
for (i++; i < doneList.length; i++) {
//add leftlist to new Promise
newP.then(doneList[i], failList[i]);
}
} else {
//the previous done return value as the parameter for next done handler.
value = temp;
}
}
}, 0);
}
function reject(newValue) {
state = 'rejected';
setTimeout(() => {
let value = tryToJson(newValue);
let tempRe = failList[0](value);
if(tempRe instanceof MyPromise){
let newP = tempRe;
for (i=1;i<doneList.length;i++) {
newP.then(doneList[i],failList[i]);
}
} else {
//如果不是promise,执行完当前的fail之后,继续执行doneList
value = tempRe;
doneList.shift();
failList.shift();
resolve(value);
}
}, 0);
}

fn(resolve,reject);
}

MyPromise.prototype.catch = function(onRejected) {
//catch is just another .then with undefined as input
return this.then(undefined, onRejected)
}

let promiFn = (resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 0)
};
let ps = new MyPromise(promiFn);
ps.then(d => {
console.log("resolve:", d)
}).catch(e => {
console.log("error:", e);
})
}
my_promise_demo();
resolve: 2

standard promise API

  • catch(): registers a handler to be called when the promise is rejected, this callback can also register by .then(success, fail) as second parameter.
  • .then(success, fail): register success or fail, It returns another promise, which resolves to the value that the handler function returns or, if that returns a promise, waits for that promise and then resolves to its result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
function promise_demo() {
let p = new Promise((resolve, reject) => {
if(1) {
resolve("resolve");
} else {
reject("reject");
}
});

//then takes two parameters, success, fail, most of time, we did not set fail, but use catch as well.
p.then(data => {
console.log(data);
});

let promise1 = new Promise(function(resolve){
resolve(2);
});
promise1.then(function(value){
return value * 2;
}).then(function(value){
return value * 2;
}).then(function(value){
console.log(`${value}`);
});
// for each then, it will return a new Promise, and pass the return value to next then

//Promise.all可以接受一个元素为Promise对象的数组作为参数,
//当这个数组里面所有的promise对象都变为!!!resolved or rejected!时,该方法才会返回
//返回值 是所有的Promise的传递的值的数组`)

let p1 = new Promise(function(resolve){
setTimeout(function(){
resolve(1);
},300);
});
let p2 = new Promise(function(resolve){
setTimeout(function(){
resolve(2);
},100);
});

Promise.all([p1,p2]).then(function(value){
console.log(value); // 打印[1,2]
});

//Promise.race的含义是只要有一个promise对象进入Resloved或者Rejected状态的话,
//程序就会停止,且会继续后面的处理逻辑;

// `delay`毫秒后执行resolve
function timerPromise(delay){
return new Promise(function(resolve){
setTimeout(function(){
console.log('set timeout',delay);
resolve(delay);
},delay);
});
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
timerPromise(1),
timerPromise(32),
timerPromise(64),
timerPromise(128)
]).then(function (value) {
console.log('reslove timeout',value); // => 1
});
}

promise_demo();
resolve
8
set timeout 1
reslove timeout 1

async/await

  • async ensures that the function returns a promise, and wraps non-promises in it, nothing to do for promise return, console.log(async_function()), it shows promise!!!
  • await makes JavaScript wait until that promise settles and returns its result
    • If it’s an error, the exception is generated — same as if throw error were called at that very place.
    • Otherwise, it returns the resolved result not promise.

Note:

  • await only works inside an async function, await won’t work in the top-level code as the top-level is not a async function.

  • await literally makes JavaScript wait until the promise settles, and then go on with the result. that doesn’t cost any CPU resources, because the engine can do other jobs in the meantime: execute other scripts, handle events.

async/await function no longer, like a regular JavaScript function, runs from start to completion in one go. Instead, it can be frozen at any point that has an await, and can be resumed at a later time

async/await aims to replace Promise.then().catch which is hard to understand

typical use for async/wait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
async function f() {

try {
let response = await fetch('/no-user-here');
//json() is async as well! need await
let user = await response.json();
} catch(err) {
// catches errors both in fetch and response.json
alert(err);
}
}

f();


// wait for the array of results
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);

Guide

When we use async/await, we rarely need .then, because await handles the waiting for us. And we can use a regular try..catch instead of .catch. That’s usually (but not always) more convenient

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function async_wait_demo() {
async function solve() {
return 1;//wrapper to Promise.resolve(1);
}
async function reject() {
return Promise.reject(2);//no wrapper, as it's a promise
}

async function test() {
let s = solve();
let svalue = await s;
console.log(svalue);

try {
let rvalue = await reject();
console.log(rvalue);
} catch(err) {
//err has the value 2
console.log(err);
}
//async always return a Promise(wrapper or not)
return 20;
}

test()
//test().then(data=>console.log(data)); is fine!! should we use it this way?
}

async function retun_is_promise() {
return 1;
}
console.log(retun_is_promise());
//get resolved promise value
retun_is_promise().then(data=>console.log("resolved data: ", data));

async_wait_demo();
Promise { 1 }

spread destructing operator

These are special cases for Array and object, shorthand.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
function operator_demo() {
//spread using ...
let array1 = [1, 2];
// let array2 = array1.concat(3);
let array2 = [...array1, 3];
console.log(array2);

let ob1 = {"a": 1};
//spread using ...
let ob2 = {...ob1, "b":2}
console.log(ob2)

function rest(a, ...rest) {
console.log(rest);
}
rest(1, 2, 3); //the rest parameters as an array

//destructing, the left, should no more than right, the right is iterable!!!
let [as, bs, cs] = "str"
console.log(as, bs, cs);

let [a, b, c=3] = [1, 2, 4, 5]; // c default is overwritten
console.log(a, b, c);

//other store left attributes with new object
//the var name must be same as key as object has no order,
// you need to which value you want to destruct on left side
let {oa, ob, ...other} = {"oa":1, "ob":2, "oc":3};
//other is new object with {"oc": 3}
console.log(oa, ob, other);


function dest(d, [a, b, c=3]) {
console.log(a, b, c);
}
dest(4, [1, 2]);

//must have {} in parameter and pass an object with same key
//a rename a1, c is object, take all left.
function mdest(d, {a:a1, b, ...c}) {
console.log(a1, b, d, c);
}
function adest(d, obj) {
console.log(obj.a, obj.b, d, obj.c);
}
mdest(3, {"a":1, "b":2, "c": 3, d: 4});
adest(3, {"a":1, "b":2, "c": 3});
}

operator_demo()
resolved data:  1
1
2
set timeout 32
[ 1, 2, 3 ]
{ a: 1, b: 2 }
[ 2, 3 ]
s t r
1 2 4
1 2 { oc: 3 }
1 2 3
1 2 3 { c: 3, d: 4 }
1 2 3 3

Modules

ES5

ES5 commonJS way used by node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//one symbol(default)
//add.js

function add(a, b) {
return a + b;
}
//default
module.exports = add;

//test.js
//rename import symbol
var add_two = require("./add")
var add = require("./add")
console.log(add(1, 2));
console.log(add_two(1, 2));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//several symbols
//op.js
function add_two(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}

//rename export symbols
module.exports.add = add_two;
module.exports.sub = sub;

//test.js
var {add, sub} = require('./op')
console.log(add(1, 2));

var sub_two = require('./op').sub;
console.log(sub_two(2, 1));

ES6

ES5 has severals way to export and import for a module while ES6 defines a standard way, here we go.
It only loads the required at compiling, you can load some exported symbols or all.

1
2
3
import {stat as fileStat, readFile} from 'fs';
import * as fs from 'fs' //load all exported symbols!
//fs.count, fs.stat, fs.readFile, fs.number

export ways

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//fs.js
export let count = 0;
export function stat() {
}
export function readFile() {
}

//Or this way
let count = 0;
function stat() {

}
function readFile(){
}
// with this way can rename when export, Better Way!!!
export {stat, readFile, count as number};

default export
Each module can only has one default export which has no export name(actuall it’s default name)

1
2
3
4
5
6
7
8
9
10
//fs.js
//no function name
export default function() {
}

//use module name as default name, it's common way.
import fs from 'fs';
import fs as renamed_stat from fs;
import {default as renamed_stat from fs};

Node finding module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# js search module in such path order(current, global)
# make sure npm or yarn add module at proper path

(base) root@dev:~/shared/github# node
> module.paths
[ '/root/windows/shared/github/repl/node_modules',
'/root/windows/shared/github/node_modules',
'/root/windows/shared/node_modules',
'/root/windows/node_modules',
'/root/node_modules',
'/node_modules',
'/root/.node_modules',
'/root/.node_libraries',
'/root/anaconda3/lib/node' ]

# check yarn/npm global module dir
$ yarn global dir
$ npm ls -g --depth 0

Low-leve API for buffer

In some case, you may want to manipulate on buffer(bytes level) like in network programming, JS supports this by providing low-level API Binary buffer and DataView.

for in vs for of

  • The for…in statement iterates over the enumerable properties(include inherited) of an object, in an arbitrary order different js engine may produce different result, it used to check attribute(key) of object, the each key is a string!!!

  • The for…of statement iterates over values that the iterable object(string, array, map) defines to be iterated over, get the value of each iterator object like string, array, map!

Be Careful

  • Make sure not to modify an object while enumerating its properties with a for...in loop.
  • Use a while loop or classic for loop instead of a for…in loop when iterating over an object whose contents might change during the loop.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function for_in_of_demo() {
Array.prototype.arrCustom = function() {};

const iterable = [3, 5, 7];
iterable.foo = 'hello';

for (const i in iterable) {
console.log(typeof i, i); //0, 1, 2, "foo", "arrCustom"
// ther order is not guaranteed, it depends on js engine.
}

for (const i in iterable) {
// arrCustom is not peoprity but prototype(method)
if (iterable.hasOwnProperty(i)) {
console.log(i); // logs 0, 1, 2, "foo"
}
}

for (const i of iterable) {
console.log(i); // logs 3, 5, 7
}
}
for_in_of_demo()
string 0
string 1
string 2
string foo
string arrCustom
0
1
2
foo
3
5
7

let vs const vs var

let and const declarations allow you to create block-scoped variables
A variable declared with the var keyword is available from the function it is declared in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// myVarVariable *is* visible out here

for (var myVarVariable = 0; myVarVariable < 5; myVarVariable++) {
// myVarVariable is visible to the whole function
}

// myVarVariable *is* visible out here


if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name); // Matt

var special

without declaration to access a variable, a global variable is defined after that access, it's valid non strict mode even it’s not good way to do so.

var Declaration Hoisting
The interpreter pulls all variable declarations to the top of its scope. It also allows you to use redundant var declarations without penalty.

1
2
3
4
5
6
7
8
9
function foo() {
console.log(age);
var age = 26; // you not see age value until here
inner(); // you can see inner here as function declaration always lift up!!!
function inner() {
console.log('inner');
}
}
foo(); // undefined

equal to below

1
2
3
4
5
6
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
1
2
3
4
5
6
7
8
9
10
11
function test() {
// "use strict";
b = 15;
console.log(b);

console.log(age); // undefined as all var declarations are pulled up to begining of function.
var age = 26;
}

test()
console.log(b)
15
undefined
15
set timeout 64
set timeout 128
[ 1, 2 ]

let

let is not hoisted

1
2
console.log(age); // ReferenceError: age is not defined
let age = 26

A let declaration also does not allow for any redundant declarations within a block scope. Doing so will result in an error:

1
2
3
4
var name;
var name;
let age;
let age; // SyntaxError; identifier 'age' has already been declared

const

1
2
3
4
5
6
7
8
9
10
for (const i = 0; i < 10; ++i) {} // TypeError: assignment to constant variable

for (const key in {a: 1, b: 2}) {
console.log(key);
}
// a, b
for (const value of [1,2,3,4,5]) {
console.log(value);
}
// 1, 2, 3, 4, 5

Ref