Contents

Js Trick

warning
Many of JavaScript cool feature or syntactic sugar included since ES6(ES2015).

You can read this Article to know What new feature brings in since ES6

Conversion

Any => Boolean

1
2
3
4
5
6
7
8
!!false; // false
!!undefined; // false
!!null; // false
!!NaN; // false
!!0; // false
!!''; // false

!!variable == Boolean(variable);

String => Integer

1
2
3
4
Number('100'); //100
+'100'; // 100

+'abc'; // NAN

Object <=> Array

Array => Object

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
let arr = [1, 2, 3, 4, 5];

let objFromArr1 = Object.assign({}, arr);

let objFromArr2 = { ...arr };

console.log(objFromArr1); // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 }

console.log(objFromArr2); // { '0': 1, '1': 2, '2': 3, '3': 4, '4': 5 }

let pair = [
    ['key1', 'val1'],
    ['key2', 'val2'],
]; // Map works as well

let objFromPair = Object.fromEntries(arr); // ES10

console.log(objFromPair); //{ key1: 'val1', key2: 'val2' }

Object.fromEntries(arr) included in ES10 (ES2019). Before ES10 or convert a complex array, arr.reduce(()=>{}, {}) is a good method

Object => Array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let obj = {
    name: 1,
    age: 2,
};

let keys = Object.keys(obj);
let values = Object.values(obj);
let entries = Object.entries(obj);

console.log(keys); // [ 'name', 'age' ]
console.log(values); // [  1, 2 ]
console.log(entries); // [ [ 'name', 1 ], [ 'age', 2 ] ]

Object.values & Object.entries are from ES8 (ES2017)

let vs var

  • var is function-scoped
  • let (and const) are block-scoped
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function doMoreStuff() {
    if (2 + 4 === 4) {
        // Here, `a` will be available for the whole function
        var a = 5;
        // But `b` will be available only inside this if block
        let b = 5;
    }
    console.log(a); // undefined
    console.log(b); // ​​b is not defined​​
}

doMoreStuff(); // ​​b is not defined​​

Shallow copy & Deep copy

Shallow copy

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// array
let nums1 = [1, 2, 3, 4, 5];

let nums2 = [...nums1];

let num3 = nums1.concat();

let num3 = nums1.slice();

// Object
let obj1 = {
    test1: 1,
    test2: {
        test3: 2,
        test4: 3,
    },
};
let obj2 = { ...obj1 };
let obj3 = Object.assign({}, obj1);

deep copy

1
2
// array and obj`
let copy = JSON.parse(JSON.stringify(orig));

Exponential

ES7 feature

1
2
3
let res = Math.pow(x, 2);
// new operator added in ES7
res = x ** 2;

Nullish coalescing

MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator

The nullish coalescing operator (??) is a logical operator that returns its right-hand side operand when its left-hand side operand is null or undefined, and otherwise returns its left-hand side operand.

This operator used to replace || in some situations that or logic cannot handle elegantly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
let calTax = (price, taxRate) => {
    taxRate = taxRate || 0.05; // if taxRate not provided (undefined), set default to 0.05
    // But there is a problem when taxRate to 0
    return price * (1 + taxRate);
};

let res1 = calTax(100, 0); // 105 WRONG X
let res2 = calTax(100); // 105 correct ✓

let calTax2 = (price, taxRate) => {
    taxRate = taxRate ?? 0.05; // if taxRate not provided (undefined), set default to 0.05
    // But there is a problem when taxRate to 0
    return price * (1 + taxRate);
};

let res3 = calTax2(100, 0); // 100 correct X
let res4 = calTax2(100); // 105 correct ✓

console.log(res3, res4);

Optional chaining

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    getAge() {
        return this.age;
    }
}

let p1 = new Person(undefined, 12);

console.log(p1.getAge()); // 12
console.log(p1.name.firstName); // Error
console.log(p1.getSex()); // Error
console.log(p1.name?.firstName); // undefined
console.log(p1.getSex?.()); // undefined

Naming object

If object key has same name with the variable in value, just use variable name.

1
2
3
4
5
6
let a = 1,
    b = 2,
    c = 3;

let res = { a, b, c }; // this is equal to // res = {a: a, b: b, c: c};
console.log(res); // { a: 1, b: 2, c: 3 }

Destruct

Array destructing is ES6 feature, But Object destructing is ES9 feature….

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let array = [1, 2, 3, 4];
let [a1, a2, ...a3] = array;
console.log(a1, a2, a3); // 1 2 [ 3, 4 ]

let test = {
    a: 1,
    b: 2,
    c: {
        d: 3,
        e: 4,
    },
    f: 5,
    g: 6,
};

let {
    a,
    b,
    c: { d, e },
    ...f
} = test;

console.log(a, b, d, e, f); // 1 2 3 4 { f: 5, g: 6 }

Add space for every four digit

1
2
3
4
5
6
7
let value = '123456789';
value = value
    .replace(/\W/gi, '')
    .replace(/(.{4})/g, '$1 ')
    .trim();

console.log(value); // 1234 5678 9

.trim() for supporting deletion

Function compose

Compose a list of function into one. I saw this in create middleware

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const f1 = (val) => `1<${val}>`;
const f2 = (val) => `2<${val}>`;

const compose = (...functionList) => {
    return functionList.reduce((prevFn, curFn) => {
        return (val) => curFn(prevFn(val));
    });
};

const composedFun = compose(f1, f2);
console.log(composedFun('hello world')); // 2<1<hello world>>

But we can improve this compose function.

  1. not need those return
  2. the final reduced function (val) => curFn(prevFn(val)); may have several arguments, therefore val change to ...args
  3. reduce function should give the second parameter as initial state in case the function list is empty
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const f1 = (val) => `1<${val}>`;
const f2 = (val) => `2<${val}>`;

const compose = (...functionList) =>
    functionList.reduce(
        (prevFn, curFn) =>
            (...args) =>
                curFn(prevFn(...args)),
        (val) => val
    );

const composedFun = compose(f1, f2);
console.log(composedFun('hello world')); // 2<1<hello world>>
const composedFun2 = compose();
console.log(composedFun2('hello world')); // hello world

tagged template

Since es6, js provide a special function called tagged template which use template string as parameter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function normal(...args) {
    console.log(args);
}

normal(1, 2, 'string1', 'string2');
// [ 1, 2, 'string1', 'string2' ]

function tag(...args) {
    console.log(args);
}

tag`string1${1}string2${2}string3`;
// [ [ 'string1', 'string2', 'string3' ], 1, 2 ]

Normally, the tagged template function will handle arguments like:

1
2
3
4
5
6
function tag(strings, ...args) {
    console.log(strings, args);
}

tag`string1${1}string2${2}string3`;
// [ 'string1', 'string2', 'string3' ] [ 1, 2 ]

Debounce & throttle

Here is my Example: Try it Now

Play in CodePen

Debounce

The debounce function delays the processing of event until the user has stopped trigger it.

The core logic is using HOF to only keep the last trigger.

1
2
3
4
5
6
7
8
9
const debounce = (fn, delay) => {
    let timer = null;
    return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
            fn(...args);
        }, delay);
    };
};

Throttle

The Throttle function only allows one event running in a period of time

The core logic is using HOF to only keep the first trigger.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const throttle = (fn, delay) => {
    let timer = null;
    return (...args) => {
        if (timer !== null) {
            return;
        }
        clearTimeout(timer);
        timer = setTimeout(() => {
            timer = null;
            fn(...args);
        }, delay);
    };
};

Snapshot & current value

This problem due to the fact that every time you log out base on same reference or different variable

 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
for (var i = 0; i < 10; i++) {
    setTimeout(() => console.log('val:', i)); // current value
}
// 10 10 ... 10 10

for (let i = 0; i < 10; i++) {
    setTimeout(() => console.log('val:', i)); // snapshot
}
// 1 2 ... 8 9

// var is function scope, let is block scope

for (var i = 0; i < 10; i++) {
    setTimeout(((val) => console.log('val:', val)).bind(null, i)); // snapshot
}
// 1 2 3 4 ... 9

const ref = { current: null };
for (var i = 0; i < 10; i++) {
    ref.current = i;
    setTimeout(((val) => console.log('val:', val.current)).bind(null, ref)); // current val
}
// 9 9 ... 9 9

for (var i = 0; i < 10; i++) {
    // snapshot
    const t = i;
    setTimeout(() => {
        console.log('t:', t);
    });
}
// 1 2 3 4 ... 9

bind <==> callback

fn.bind(context, ...args) will return a new function with a given context and given several arguments.

If context is null, bind() same like callback

newFn = fn.bind(null, ...args); == newFn = (...newArgs) => fn(...args,...newArgs);

Format time string

The direct way to format a date object is used the second attribute in toLocaleString also as Intl.DateTimeFormat()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const today = new Date();

console.log(
    today.toLocaleString('en-US', {
        hour12: true,
        year: '2-digit',
        month: '2-digit',
        day: '2-digit',
        minute: '2-digit',
        hour: '2-digit',
        second: '2-digit',
        timeZoneName: 'short',
    })
);

Relative time expression

Sometime we want to know the relative time between two date. Like 3 days ago. 1 year ago.

There is no vanilla js function direct support that. It is ok to use existing time library to handle it.

day.js is good option here. Small and efficient.

For relative time part. See: https://day.js.org/docs/en/customization/relative-time#docsNav

Lodash api

Lodash is a very power for utility library

Lodash to Chrome

1
2
3
fetch('https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js')
    .then((response) => response.text())
    .then((text) => eval(text));

original post