Some gotchas I met in JS development.
1. Trace Call Stack [1] [2] #
function foo() {
bar();
}
function bar() {
baz();
}
function baz() {
console.log(new Error().stack);
}
foo();
2. console.log
an Object is pass by reference [3] #
- object is pass by reference
- object may have changed when log shows
Solution #
- Don't use
console.log(obj)
, useconsole.log(JSON.parse(JSON.stringify(obj)))
instead. - This way you are sure you are seeing the value of obj at the moment you log it. Otherwise, many browsers provide a live view that constantly updates as values change. This may not be what you want.
3. Initializing 2D array with Array.fill #
- if the filled value is Object, Array or function (pass by reference), they will reference to the same object.
// filled each row with `same reference` of `new Array(4).fill(1)`.
const list = new Array(3).fill(new Array(4).fill(1));
// filled each row with `different reference` of `new Array(4).fill(1)`.
const list2 = new Array(3).fill(null).map(() => new Array(4).fill(1));
list[1][2] = 2;
list2[1][2] = 2;
console.log(list);
// false
//[
// [ 1, 1, 2, 1 ],
// [ 1, 1, 2, 1 ],
// [ 1, 1, 2, 1 ]
//]
console.log(list2);
// correct
//[
// [ 1, 1, 1, 1 ],
// [ 1, 1, 2, 1 ],
// [ 1, 1, 1, 1 ]
//]
4. Deep Copy v.s. Shallow Copy #
Shallow Copy #
Deep Copy #
-
JSON.parse(JSON.stringify(object))
.- If you do not use
circular reference
,Dates
,functions
,undefined
,Infinity
,RegExps
,Maps
,Sets
,Blobs
,FileLists
,ImageDatas
,sparse Arrays
,Typed Arrays
or other complex types within your object, it is a very simple one liner to deep clone an object.
- If you do not use
-
with library: lodash - cloneDeep
5. Manipulate with floating-point number [4] #
JavaScript has a single and unpredictable number type, 64-bit floating point.
0.1 + 0.2 === 0.3; // false
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON;
}
numbersCloseEnoughToEqual(0.1 + 0.2, 0.3);
6. Key order of Object.keys(obj)
? [5] #
ES5 #
- depend on browser
- ref: ECMA 5.0 Object.keys (O)
- For each own enumerable property of O whose name String is P
Call the [[DefineOwnProperty]] internal method of array with arguments ToString(index), the PropertyDescriptor {[[Value]]: P, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
Increment index by 1.
If an implementation defines a specific order of enumeration for the for-in statement, that same enumeration order must be used in step 5 of this algorithm.
ES6: #
- depend on
OwnPropertyKeys
methodReflect.ownKeys
also depend onOwnPropertyKeys
- Order of
OwnPropertyKeys
integers
: in numeric orderstrings
: in chronological orderSymbols
: in chronological order
- ref: ECMA 6.0 Object.keys ( O )
const obj = {
2: "integer: 2",
foo: "string: foo",
"01": "string: 01",
1: "integer: 1",
[Symbol("first")]: "symbol: first",
};
obj["0"] = "integer: 0";
obj[Symbol("last")] = "symbol: last";
obj["veryLast"] = "string: very last";
console.log(Reflect.ownKeys(obj));
// [ "0", "1", "2", "foo", "01", "veryLast", Symbol(first), Symbol(last) ]
7. Remove Duplicate Element in Array #
const chars = ["A", "B", "A", "C", "B"];
const uniqueChars = chars.filter((c, index) => chars.indexOf(c) === index);
//[ 'A', 'B', 'C' ]
8.DOM Element #
DOM Element is a special object #
var a = document.createElement("div");
typeof a; // "object"
Object.prototype.toString.call(a); // "[object HTMLDivElement]"
a.tagName; // "DIV"
- Cannot call some built-in methods such as
toString()
- Cannot be overwritten / override some properties such as
this
DOM Element will produce global variable #
be careful for duplicate naming.
<div id="foo"></div>
console.log(foo); // HTML Element
9. using await
inside setInterval
is pointless #
- since interval won't wait for
await
async function getData (){
return fetch("url...")
}
const timer = setInterval(
await getData();
,1000)
above code can change into
async function getData() {
return fetch("https://google.com");
}
async function sleep(ms) {
return await new Promise((resolve) => setTimeout(resolve, ms));
}
function periodicGetData() {
return new Promise(async (resolve, reject) => {
if (some_condition) {
resolve();
}
const res = await getData();
if (res instanceof Error) {
reject(res);
}
console.log("res", res);
await sleep(1000);
return periodicGetData();
});
}
periodicGetData();