25 JavaScript Tricks You Need To Know About (Part 2)


25 JavaScript Tricks You Need To Know About (Part 2)

You can read about part one of 25 Javascript tricks in another article where you can find equally awesome code solution snippets to improve your codebase with and learn more about Javascript by examples.

Code Source

These are code snippets you can use in your projects and evolve to become something bigger. They teach various lessons and reveal great features of Javascript and the Environment where it runs. All code links are below the images.

1 — Deep value retriever

This simple function leverages the power of the Array reduce method to allow you to retrieve the deep value in deeply nested objects, array, and Javascript Map. You can access value belonging to the object itself or its prototype, like accessing a string length or getting the size of a Map.

function deepValue(dict, path) {
  path = Array.isArray(path) ? path : path.split('.');

  const value = path.reduce(
    (obj, key) =>
      // prevents error on retrieving key in undefined
      obj === undefined
        ? null
        : // check if Map otherwise it is object like
        obj instanceof Map
        ? obj.get(key) ?? obj[key]
        : obj[key],
    dict,
  );

  return value === undefined ? null : value;
}

Code on Github

deepValue(obj, 'top.in.list.0'); // get list 1st item
deepValue(obj, 'top.in.list.length'); // get list length
deepValue(obj, 'top.in.noExistentKey'); // returns null

2 — Date Formatter

Javascript Date is already powerful if you take time to learn it a little. Combined with the Intl object it becomes limitless. This is a small sample of a date formatter that even handles Internationalization that can be extended and modified to fulfill the needs of your project.

function formatDate(date, formatStr = 'MMMM DD, YYYY', lang = 'default') {
  date = new Date(date);

  const day = date.toLocaleString(lang, { weekday: 'long' });
  const month = date.toLocaleString(lang, { month: 'long' });
  const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
  const pastDaysOfYear = (date - firstDayOfYear) / 86400000; // one day;
  const week = Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);

  return formatStr
    .replace(/\bYYYY\b/g, date.getFullYear())
    .replace(/\bYY\b/g, `${date.getFullYear()}`.substring(2))
    .replace(/\bWW\b/g, week.toString().padStart(2, '0'))
    .replace(/\bW\b/g, week)
    .replace(/\bDDDD\b/g, day)
    .replace(/\bDDD\b/g, day.substring(0, 3))
    .replace(/\bDD\b/g, date.getDate().toString().padStart(2, '0'))
    .replace(/\bD\b/g, date.getDate())
    .replace(/\bMMMM\b/g, month)
    .replace(/\bMMM\b/g, month.substring(0, 3))
    .replace(/\bMM\b/g, (date.getMonth() + 1).toString().padStart(2, '0'))
    .replace(/\bM\b/g, date.getMonth() + 1);
}

Code on Github

formatDate(Date.now(), 'MM-DD-YYYY');
// "02-06-2021"
formatDate(Date.now());
// "February 06, 2021"
formatDate(Date.now(), 'D de MMMM de YYYY', 'pt');
// "6 de fevereiro de 2021"
formatDate(Date.now(), 'MMMM DD, YYYY', 'zh');
// "二月 06, 2021"
formatDate(Date.now(), 'MMM DD (DDD), YYYY');
// "Feb 06 (Sat), 2021"

3 — Promisifier

NodeJs promisify utility is one of my favorite utils in NodeJs and I decided I need that in the client as well so I created this replica that handles any type of functions including async function, callback function, and those which simply return results.

function promisify(subject) {
  return (...args) =>
    new Promise(async (resolve, reject) => {
      const callbackHandler = (...results) => {
        // filter error from results to remove dependency on error, result order
        const { error, result } = results.reduce((obj, res) => {
          if (res instanceof Error) {
            obj['error'] = res;
          } else {
            obj['result'] = res;
          }

          return obj;
        }, {});

        if (error) {
          reject(error);
        } else {
          resolve(result);
        }
      };

      try {
        const result = await subject(...args, callbackHandler);
        resolve(result); // only runs if callbackHandler does not resolve the promise
      } catch (error) {
        reject(error);
      }
    });
}

Code on Github

Here is an example of usage with an “add” function that returns a result and a divide function that takes a callback and may throw or call the callback with the result.

const add = (a, b) => {
  return a + b;
};
const divide = (a, b, cb) => {
  if (b === 0 && a !== 0) {
    cb(new Error('Cannot divide by "Zero"'), null);
  } else {
    const total = a / b;
    cb(null, total);
  }
};
const asyncAdd = promisify(add);
const asyncDivide = promisify(divide);
add(23, 67); // 90
asyncAdd(23, 67).then((total) => {
  console.log(total); // 90
});
divide(35, 5, (error, total) => {
  console.log(error); // null
  console.log(total); // 7
});
divide(8, 0, (error, total) => {
  console.log(error); // Error: 'Cannot divide by "Zero"'
  console.log(total); // 'total' null
});
asyncDivide(35, 5).then((total) => {
  console.log(total); // 7
});
asyncDivide(35, 0).catch((error) => {
  console.log(error); // Error: 'Cannot divide by "Zero"'
});

4 — Merge Objects

Especially in react, you will feel the need to merge two objects without having to deal with the deep nesting and still maintain the structure. This simple snippet will deep merge your objects and array nicely using the power of recursion to return a new copy to you. It can be extended to support more object types as you wish.

function mergeObjects(a, b) {
  if (a === null || typeof a !== 'object') return b ?? {};
  if (b === null || typeof b !== 'object') return a ?? {};

  const obj = Array.isArray(a) ? [...a] : a;

  for (const key in b) {
    if (b.hasOwnProperty(key)) {
      obj[key] = mergeObjects(obj[key], b[key]);
    }
  }

  return obj;
}

Code on Github

5 — Deep Equality

Checking if 2 objects are equal may require you to do a lot and can be expensive. I always wanted something that is simple and extensible and this snippet does that. It can be extended to include checks for different types of objects as you can see in lines 7, 9, 12, and 13.

function isEqualFunctions(fn1, fn2) {
  if (fn1 === fn2) return true;

  return (
    typeof fn1 === 'function' && typeof fn2 === 'function' && fn1.toString() === fn2.toString()
  );
}

function isEqualSymbols(symb1, symb2) {
  if (symb1 === symb2) return true;

  return (
    typeof symb1 === 'symbol' && typeof symb2 === 'symbol' && symb1.toString() === symb2.toString()
  );
}

function isEqualMap(map1, map2) {
  if (map1 === map2) return true;

  if (!(map1 instanceof Map) || !(map2 instanceof Map) || map1.size !== map2.size) return false;

  const keys1 = Array.from(map1.keys());
  const keys2 = Array.from(map2.keys());
  const values1 = Array.from(map1.values());
  const values2 = Array.from(map2.values());

  return (
    keys1.every((k, i) => isEqual(k, keys2[i])) && values1.every((v, i) => isEqual(v, values2[i]))
  );
}

function isEqualSet(set1, set2) {
  if (set1 === set2) return true;

  if (!(set1 instanceof Set) || !(set2 instanceof Set) || set1.size !== set2.size) return false;

  const values1 = Array.from(set1.values());
  const values2 = Array.from(set2.values());

  return values1.every((v, i) => isEqual(v, values2[i]));
}

function isEqual(a, b) {
  // handles primitives and same instances
  if (a === b) return true;

  switch (`${typeof a}:${typeof b}`) {
    case 'symbol:symbol':
      return isEqualSymbols(a, b);
    case 'function:function':
      return isEqualFunctions(a, b);
    case 'object:object':
      // inner cases for objects other than Array and Object
      if (a instanceof Map) return isEqualMap(a, b);
      if (a instanceof Set) return isEqualSet(a, b);

      // handles Object and Array
      const keysA = Object.keys(a);
      const keysB = Object.keys(b);

      if (keysA.length != keysB.length) return false;

      for (let key of keysA) {
        if (!keysB.includes(key) || !isEqual(a[key], b[key])) return false;
      }

      return true;
    default:
      // handles when a and b is of different types
      return false;
  }
}

Code on Github

6 — 🔥 Siblings selector

jQuery has some useful siblings selector. In fact, these are super simple methods you can create on your own. Below is a simple command object that allows you to access element siblings easily and efficiently.

function element(selectorOrElement) {
  const element =
    selectorOrElement instanceof Element
      ? selectorOrElement
      : document.body.querySelector(selectorOrElement);

  return {
    get self() {
      return element;
    },
    get nextElement() {
      return element.nextElementSibling;
    },
    get prevElement() {
      return element.previousElementSibling;
    },
    get siblings() {
      return [...element.parentNode.children].filter((el) => el !== element);
    },
    get nextSiblings() {
      const siblings = [];
      let nextElement = element.nextElementSibling;

      while (nextElement) {
        siblings.push(nextElement);
        nextElement = nextElement.nextElementSibling;
      }

      return siblings;
    },
    get previousSiblings() {
      const siblings = [];
      [...element.parentNode.children].some((el) => {
        if (el !== element) siblings.push(el);
        return el === element;
      });

      return siblings;
    },
  };
}

Code on Github

console.log(element('ul li:nth-child(3)').siblings);
console.log(element('ul li:nth-child(3)').previousSiblings);
console.log(element('ul li:nth-child(3)').nextSiblings);
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
  <li>5</li>
  <li>6</li>
</ul>
function element(selectorOrElement) {
  const element =
    selectorOrElement instanceof Element
      ? selectorOrElement
      : document.querySelector(selectorOrElement);
  return {
    get self() {
      return element;
    },
    get nextElement() {
      return element.nextElementSibling;
    },
    get prevElement() {
      return element.previousElementSibling;
    },
    get siblings() {
      return [...element.parentNode.children].filter((el) => el !== element);
    },
    get nextSiblings() {
      const siblings = [];
      let nextElement = element.nextElementSibling;
      while (nextElement) {
        siblings.push(nextElement);
        nextElement = nextElement.nextElementSibling;
      }
      return siblings;
    },
    get previousSiblings() {
      const siblings = [];
      [...element.parentNode.children].some((el) => {
        if (el !== element) siblings.push(el);
        return el === element;
      });
      return siblings;
    }
  };
}
console.log(element("ul li:nth-child(3)").siblings);
console.log(element("ul li:nth-child(3)").previousSiblings);
console.log(element("ul li:nth-child(3)").nextSiblings);

7 — Ancestor selector

Similar to siblings, you may need to select an ancestor element that is not the direct parent of your element. You can do that with this simple built-in capability.

<section id="top">
  <div><span>text</span></div>
  <form action="">
    <fieldset>
      <label for="">
        <input type="" />
      </label>
    </fieldset>
  </form>
</section>
const input = document.querySelector('input');
input.parentElement; // "<label for=''>...</label>"
input.closest('form'); // "<form action=''>...</form>"
input.closest('#top'); // "<section id='top'>...</section>"

8 — Key list by

You can change lists like array or map and even objects to be keyed by a specific value. This allows you to create maps easily from other data which can help for quicker data retrieving and check.

Code on Github

const arr = [
  { id: 1, name: 'one' },
  { id: 2, name: 'two' },
  { id: 3, name: 'three' },
];
keyBy(arr, 'id');
/* outputs 
{
  '1': { id: 1, name: 'one' },
  '2': { id: 2, name: 'two' },
  '3': { id: 3, name: 'three' }
}
*/
const obj = {
  1: { id: 1, str: 'uno' },
  2: { id: 2, str: 'doz' },
  3: { id: 3, str: 'tres' },
};
keyBy(obj, 'str');
/* outputs
{
  uno: { id: 1, str: 'uno' },
  doz: { id: 2, str: 'doz' },
  tres: { id: 3, str: 'tres' }
}
*/
const map = new Map([
  ['one', { id: 1, name: 'one' }],
  ['two', { id: 2, name: 'two' }],
  ['three', { id: 3, name: 'three' }],
]);
keyBy(map, 'name');
/* outputs
{
  one: { id: 1, name: 'one' },
  two: { id: 2, name: 'two' },
  three: { id: 3, name: 'three' }
}
*/

9 — Loop Anything with the ability to break out

Javascript while and for loops accept the “break” keyword in the body that allows them to quit early and avoid unnecessary iterations. My wish is to loop any data type easily with the same ability to break out.

The following allows you to loop any iterable with the option to break out of the loop by making your callback return true. It can also be extended to create other methods similar to many of the other Array methods.

Code on Github

forEach([1, 2, 3], console.log); // array
forEach('123', console.log); // string
forEach({ a: 1, b: 3, c: 4 }, console.log); // object
forEach(
  new Map([
    ['a', 1],
    ['b', 20],
  ]),
  console.log,
); // Map
forEach(new Set([4, 12, 89]), console.log); // Set
forEach(new Set([4, 12, 89]).keys(), console.log); // Iterator

12 — Map Anything

My favorite Array methods are .from, .some and .reduce. You can do anything with these so I used the .from to allow me to map anything I want as well with a simple utility function.

const handler = (n) => n + 10;
map([1, 2, 3], handler); // array
map('123', handler); // string
map({ a: 1, b: 3, c: 4 }, handler); // object
map(
  new Map([
    ['a', 1],
    ['b', 20],
  ]),
  handler,
); // Map
map(new Set([4, 12, 89]), handler); // Set
map(new Set([4, 12, 89]).keys(), handler); // Iterator

11 — Filter anything

Javascript Array comes with the “filter” method and what I often feel the need for is to filter things out of other data structures that are not arrays. So I created this based on the forEach you saw above, and it inherits the ability to filter from any object or iterable list-like objects. It always returns a new array containing only the things you want.

Code on Github

const arr = [2, 4, 7, 13, 89];
filter(arr, (n) => n > 7); // returns [ 13, 89 ]

12 — Fixed Size Array

Javascript Arrays are not Arrays by definition but Javascript has typed arrays that follow the array definition. You can define a size for them but that won’t stop you from adding more or remove items dynamically. If you feel the need to have that restriction then you need this snippet.

Code on Github

const arr1 = createFixedSizeArray(10, 2, 3, 4);
const arr2 = createFixedSizeArray(3);
arr1[9] = 20; // has no effect
arr1.push(10); // throws TypeError: Cannot add property...
arr1.pop(); // throws TypeError: Cannot delete property ...
arr2[1] = 12; // sets the value since index one slot exists
console.log(arr1); // prints [ 10, 2, 3, 4 ]
console.log(arr2); // prints [ undefined, 12, undefined ]

13 — Capitalize Words

CSS has the ability to capitalize text in many different ways but to do the same in Javascript you need something of your own. This small function gives you this power.

Code on Github

capitalize('My dog ate my homework');
// returns 'My Dog Ate My Homework'

12 — File Uploader

I used a version of this snippet on my custom multifile resumable uploader video which you can watch for even more details but this simple snippet allows you to upload any data type to any provided server endpoint URL. It lets you listen to the upload progress as well but if you want to learn more about resuming and pausing upload watch my video on the topic.

Code on Github

postData('http://localhost:1234/upload', { name: 'John Doe' }).then((res) => {
  console.log('end', res);
});
const onProgress = (event) => {
  console.log(event.total, event, loaded);
};
postData('http://localhost:1234/upload', myFile, { onProgress });

13 — Class private properties

Javascript has its own way to create class private members, but it does not get the love it deserves from the community — it has a weird syntax. If you don’t like the class built-in way to declare private members you can use the power of closure, scope, and IIFE to create private things.

14 — Abstract class

An abstract class is a class that can only be extended and not instantiated directly. It is useful to create classes that will serve as templates for others or to group common stuff for similar other classes. A good example of its usage is the Item class in my File System video.

This trick works by checking the constructor name. If it is the same as the class where you are checking it is because it is being initialized, otherwise, extended.

15 — Find Average

Need to find the average of a group of numbers? This is how you do it using the reduce Array method.

Code on Github

16 — Group Data by key

Similarly to key data by a specific key you saw above, we can make a tiny change to let us group similar data by using the Array reduce method. This snippet is awesome to help you collect similar data from big lists. If you are using react, consider using the flatlist-react module which has a grouping option that works great to render lists already grouped.

Code on Github

const people = [
  { firstName: 'John', lastName: 'Doe' },
  { firstName: 'Peter', lastName: 'Carter' },
  { firstName: 'Jane', lastName: 'Sigfield' },
  { firstName: 'Jonathan', lastName: 'Sigfield' },
  { firstName: 'Alice', lastName: 'Doe' },
  { firstName: 'Carlos', lastName: 'Carter' },
  { firstName: 'Bruno', lastName: 'Sigfield' },
  { firstName: 'Before', lastName: 'Semicolon' },
];
groupBy(people, 'lastName');
/* outputs
{
  Doe: [
    { firstName: 'John', lastName: 'Doe' },
    { firstName: 'Alice', lastName: 'Doe' }
  ],
  Carter: [
    { firstName: 'Peter', lastName: 'Carter' },
    { firstName: 'Carlos', lastName: 'Carter' }
  ],
  Sigfield: [
    { firstName: 'Jane', lastName: 'Sigfield' },
    { firstName: 'Jonathan', lastName: 'Sigfield' },
    { firstName: 'Bruno', lastName: 'Sigfield' }
  ],
  Semicolon: [ { firstName: 'Before', lastName: 'Semicolon' } ]
}
*/

🔥 19 — Shuffle an array

Shuffling is nice when you want to randomize things especially if you are building games or want to simulate a real-world scenario where you get things in a nonspecific order. It can also be a good way to test some code. This snippet is based on the Fisher-Yates shuffle algorithm,

const shuffle = ([...arr]) => {
  let m = arr.length;
  while (m) {
    const i = Math.floor(Math.random() * m--);
    [arr[m], arr[i]] = [arr[i], arr[m]];
  }
  return arr;
};

🔥 20 — Debounce 防抖

Debouncing things ensures that in a specific interval only a single thing happens. For example, you have a button that when clicked it calls the API with a request. In case a user repeatedly clicks the button it means one request for every click but if you denounce it means that once they stop the click and wait a certain time pass then the request is made.

防抖就是指触发事件特定时间间隔内后才执行函数,如果在特定时间间隔内又触发了事件,则会重新计算函数执行时间。节流就是指连续触发事件但是在特定时间间隔内内只执行一次函数 发送请求 输入验证 浏览器窗口 resize

P.S: Debounce function can be used with anything.

const debounce = (duration = 0, cb) => {
  let timer = null;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      cb(...args);
    }, duration);
  };
};

Code on Github

const btn = document.querySelector('button');
const clickHandler = (event) => {
  console.log(event.target);
};
btn.addEventListener('click', debounce(1000, clickHandler));

21 — Handle Event Once

In case you are listening to events but only want to react to them once, this snippet will do the trick.

Code on Gtihub

const btn = document.querySelector('button');
btn.addEventListener('click', once(console.log));

22 — Handle Event until

Similar to the “once” snippet above, you can make some alterations that make you respond to an event only until a condition is met. The below example handles click on a button incrementing the number until it reaches 6 then stops.

Code on Github

const btn = document.querySelector('button');
const handler = (e) => {
  const n = Number(e.currentTarget.textContent);
  e.currentTarget.textContent = n + 1;
};
const checker = (e) => Number(e.currentTarget.textContent) >= 5;
btn.addEventListener('click', until(handler, checker));

23 — Observe DOM element

This simple snippet is based on the MutationObserver API and it is just an abstraction/facade with some simple tricks to handle the changes. It detects when an element attribute or content changes. It does not detect things like offsetWidth, height changes.

Code on Github

const btn = document.querySelector('button');
observeElement(btn, console.log);
btn.style.display = 'block';
/* triggers
{
  type: "attributes", 
  oldValue: null, 
  newValue: "display: block;"
}
*/
btn.innerHTML = 'another text <span>label</span> after';
/* triggers
{
  type: "content", 
  oldValue: null, 
  newValue: ["another text ", span, " after"]
}
*/

24 —Simple Observable (like RxJs)

You dont need to install RxJs to do observables. You definitely need it for more complex stuff but for simple observables, you can do this.

Learn More Design Patterns Like This here

Code on Github

const obs = new Observable((observer) => {
  observer.next(10);
  observer.next(20);
  observer.complete();
  // gets ignored since its complete
  observer.error(new Error());
});
obs.subscribe({
  next(value) {
    console.log(value); // prints 10, 20
  },
});
obs.unsubscribe();
// never executes
obs.subscribe((value) => {
  console.log('next', value);
});

25 — Extend DOM element with data

By leveraging the power of WeakMap and the wish to extend some type of objects like the DOM Element with some data or capabilities this is your snippet. It lets you add new capabilities to any built-in or third-party libraries without changing them directly.

I used a version of this during my File System implementation video to connect files system data with DOM Elements for easier manipulation.

If powered up with Proxy, this snippet becomes even more powerful, you can learn about that in this other article.

Code on Github

let btn = extendObject(document.querySelector('button'));
let array = extendObject([1, 2, 100]);
// extends API with "position"
btn.set('position', (() => btn.obj.getBoundingClientRect())());
btn.get('position');
// {x: 8, y: 8, width: 34.53125, height: 21, top: 8, …}
// Add ability to get last item to array
array.set('lastItem', (() => array.obj.slice(-1)[0])());
array.get('lastItem');
// 100

Conclusion

Javascript is full of tricks and as a Javascript Developer, you almost feel like a Magician. The goal is to continue learning and be thirsty for knowledge. Learning Javascript is a fun and interesting journey so let me help you by welcoming you to Before Semicolon.


文章作者: Elson Correia
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Elson Correia !
  目录