value = 10; value += 10; // this is ok value = 100; // this is ok let refItems = [1, 2, 3]; refItems.push(4); // this is ok refItems = [5, 6, 7]; // this is ok const obj = {a: 10}; obj.b = 20; // this is ok obj = {c: "ok"}; // this is ok // const means that the variable cannot be reassigned const value = 10; value += 10; // this is NOT-OK value = 100; // this is NOT-OK const refItems = [1, 2, 3]; refItems.push(4); // this is ok refItems = [5, 6, 7]; // this is NOT-OK const obj = {a: 10}; obj.b = 20; // this is ok obj = {c: “no”}; // this is NOT-OK for (var i = 0; i < 10; ++i) { var xyz = 123 * i; } console.log(i, xyz); // 10 1107 for (let i = 0; i < 10; ++i) { const xyz = 123 * i; } console.log(i, xyz); // ReferenceError: i and xyz are not defined var do not use! let can be reassigned const cannot be reassigned Block-scoped variables It’s time to wave goodbye to var globally-scoped variables Use let and const
{}; let b = 10; const c = [1, 2, 3, 4]; for (…) { b *= items[i] } if (b == 0) return; for (let i = 0; i < b; ++i) { sum += b * c[i % c.length]; } if (sum < 1000) return; a = {b: 1 + b, sum: 10 * sum}; let b = 10; for (…) { b *= items[i] } if (b == 0) return; const c = [1, 2, 3, 4]; for (let i = 0; i < b; ++i) { sum += b * c[i % c.length]; } if (sum < 1000) return; const a = {b: 1 + b, sum: 10 * sum}; a is allocated here but not even used until the last line. The object allocated here was not even used and thrown away then we assigned a new one. DO NOT allocate objects if not necessary. The machine does not have infinite memory And allocating object is not cheap Our method is over, and a and c were not used Our method is over, and a was not used Keeping variables near were they are used allows to save memory and time in case of early exit And it should improve code readability
= 0; i < items.length; ++i) { const name = body.items[i].data['user'].info.name; const surname = body.items[i].data['user'].info.surname; const email = body.items[i].data['user'].info.email; const photo = body.items[i].blobs['user'].profile; users.push({name, surname, email, photo}); } const items = body.items; for (let i = 0; i < items.length; ++i) { const item = items[i]; const userInfo = item.data['user'].info; const photo = item.blobs['user'].profile; const name = user.name; const surname = user.surname; const email = user.email; users.push({name, surname, email, photo}); } Rewrite As Accessing to properties of Objects/Maps is not cheap Especially when there are lots of nested objects Use variables to store the nearest object you have to use. Do lookup the the same item once for (let i = 0; i < items.length; ++i) { const x = i * body.asJson().value; } const value = body.asJson().value; for (let i = 0; i < items.length; ++i) { const x = i * value; } Always assume that functions are slow and have computations Rewrite As
'car', …]; for (let i = 0; i < items.length; ++i) { if (expected.indexOf(items[i].type) >= 0) { // the item is what we expect } } const expected = ['foo', 'bar', 'car', …]; for (let i = 0; i < items.length; ++i) { if (expected.findIndex(v => v == items[i].type) >= 0) { // the item is what we expect } } const expected = ['foo', 'bar', 'car', …]; for (let i = 0; i < items.length; ++i) { const results = expected.filter(v => v == items[i].type); if (results.length == 1) { // the item is what we expect } } const expected = new Set(['foo', 'bar', 'car', …]); for (let i = 0; i < items.length; ++i) { if (expected.has(items[i].type) >= 0) { // the item is what we expect } } The .findIndex() calls a function for each item. Calling a function is expensive The .filter() scans every item, and return a list. If we just need to find the first one matching we are wasting time looking at other items even if we have already the answer we wanted Searching into an Array is an O(N) operation In the worst cast “item not present” We must scan and compare every item If the Array is Sorted you can use Binary Search and you get down to O(log n) In general prefer the use of Set for “contains” operations. Set.has() operation is O(1) Even Worse Even Worse Rewrite as const processed = new Set(); for (const item of items) { if (!processed.has(item.type)) { // process 'type' item once processed.add(item.type); } } Example 1 Example 2
3].map(v => v * 2); const r = []; for (const v of [1, 2, 3]) { r.push(v * 2); } It creates a new array with the results of calling the provided function on every element Use it only to “map” (transform) the input .map(v => {…}) Writing Is the same as for (let i = 0; i < items.length; i++) { processItem(items[i); } for (const item of items) { processItem(item); } items.forEach(item => processItem(item)); iterate through each items [1, 2, 3].map(v => v * 10); // [10, 20, 30 [1, 2, 3].map(v => { v * 10 }); // [undefined, undefined, undefined] Watch out for Curly braces Is not the same as With curly braces you must use return [1, 2, 3].map(v => { return v * 10; }); // [10, 20, 30]
function that, when called, has its this keyword set to the provided value let foo = new Foo(1); const func = foo.bar.bind(foo); setTimeout(func, 100); class Foo { value: number; constructor(value: number) { this.value = value; } bar() { console.log(this.value); } } function test(callback) { // You probably expect to print 1 // But, You get undefined! callback(); } const foo = new Foo(1); test(foo.bar); Mistake #1 function test(callback) { // You probably expect to print 1 callback(); } const foo = new Foo(1); // Not working! //test(foo.bar); // Working alternative test(() => foo.bar()); Solutions #1 let foo = new Foo(1); setTimeout(() => foo.bar(), 100); foo = new Foo(2); // You probably expect to print 1 // But, You get 2 Mistake #2 let foo = new Foo(1); // Not working! //setTimeout(() => foo.bar(), 100); // Working alternative const func = foo.bar.bind(foo); setTimeout(func, 100); foo = new Foo(2); Solutions #2
for (let i = 0; i < 3; ++i) { // notify the value change callback(i); } }, 1000); } const results = []; doSomething(value => results.push(value)); console.log(results); // [] doSomething(value => { console.log('received a value from the callback:’, value) }); // received a value from the callback: 0 // received a value from the callback: 1 // received a value from the callback: 2 The results of the callbacks will not be available on the next line Handle Events When they arrive Using the callback
= await httpResult.json(); console.log(result); fetch(SERVICE_URL) .then(response => response.json()) .then(data => { const result = data; console.log(result); // the fetch is completed, // execute everything else here }); // any async operation above // are still in progress... let result; fetch(SERVICE_URL) .then(response => response.json()) .then(data => result = data); console.log(result); // undefined function doSomething() { return new Promise((resolve, reject) => { setTimeout(() => { const results = []; for (let i = 0; i < 3; ++i) { results.push(i); } resolve(results); }, 1000); }); } doSomething().then(results => console.log('callback', results)); console.log(‘after doSomething() using callbacks’); // after doSomething() using callbacks // callback [ 0, 1, 2 ] Remember that With Async Code the Execution is non-linear const results = await doSomething(); console.log('after doSomething, already with the result', results); // after doSomething, with the result [ 0, 1, 2 ]
function getItems() { const items: Item[] = []; for (let i = 0; i < 10; ++i) { items.push({ id: i, price: i * 10 }); } return items; } function *getItems(): Generator<Item> { for (let i = 0; i < 10; ++i) { yield { id: i, price: i * 10 }; } } Using Yield (Generator Functions) Using a list (Without Generator) async function *getItems(): AsyncGenerator<Item> { const URL = 'https://dummyjson.com/products/1'; for (let i = 0; i < 10; ++i) { // demo async "generation/computation" const r = await fetch(URL); yield await r.json(); } } for await (const item of getItems()) { console.log(item); } For await Async function * async generators Generator functions allow you to declare a function that behaves like an iterator The Yield Keyword
= ‘36b8f84d-df4e-4d49-b662-bcde71a8764f’ const buf = new Uint8Array(16); crypto.getRandomValues(buf); function bad_uuidv4() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' .replace(/[xy]/g, c => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } Pseudo-Random number generators have limited randomness and can generate collisions There is already a method to generate an UUID v4 …and a strong random value generator Use the Crypto API
used Don’t Spend time for the same lookups, and use variables Choose the right data structure and learn about time complexity .map() is not a .forEach() be aware of callbacks and optional parameters use bind() or arrow functions when passing methods as callback beaware of asynchronous code, even if Looks sequential beaware of comparisons, use the triple equals as much as you can sort() converts elements into string, Use Custom comparators JSON stringify() may not work as expected on all the types avoid Math.random() for IDs, use the crypto module remember the standard library for digests and encryption Best Practices & Mistakes to Avoid Javascript