Nest all content into snippets
This commit is contained in:
@ -0,0 +1,54 @@
|
||||
---
|
||||
title: 10 must-have VS Code extensions for JavaScript developers
|
||||
shortTitle: Essential VS Code extensions
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [devtools,vscode]
|
||||
author: chalarangelo
|
||||
cover: computer-screens
|
||||
excerpt: Boost your productivity with these 10 essential VS Code extensions for JavaScript developers.
|
||||
unlisted: true
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Developers will most likely argue for the rest of eternity about the most productive code editor and the best extensions. Here are my personal extension preferences for VS Code as a JavaScript developer:
|
||||
|
||||
### ESLint
|
||||
|
||||
[ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) turns the popular JavaScript linter into an extension of VS Code. It automatically reads your linting configuration, identifies problems and even fixes them for you, if you want.
|
||||
|
||||
### GitLens
|
||||
|
||||
[GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) is a very powerful collaboration tool for VS Code. It provides many useful tools for git such as blame, code authorship, activity heatmaps, recent changes, file history and even commit search.
|
||||
|
||||
### Debugger for Chrome
|
||||
|
||||
[Debugger for Chrome](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) allows you to debug your JavaScript code in Chrome or Chromium. Breakpoints, call stack inspection and stepping inside a function are only some of its features.
|
||||
|
||||
### Bracket Pair Colorizer 2
|
||||
|
||||
[Bracket Pair Colorizer 2](https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer-2) makes reading code faster as it makes matching brackets the same color. This extension for VS Code improves upon its predecessor by providing improved performance.
|
||||
|
||||
### Bookmarks
|
||||
|
||||
[Bookmarks](https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks) is one of those extensions that will significantly reduce your time jumping between different files, as it allows you to save important positions and navigate back to them easily and quickly.
|
||||
|
||||
### TODO Highlight
|
||||
|
||||
[TODO Highlight](https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight) simplifies tracking leftover tasks by allowing you to list all of your TODO annotations, as well as adding a handy background highlight to them to make them pop out immediately.
|
||||
|
||||
### Live Server
|
||||
|
||||
[Live Server](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer) gives you an easy way to serve web pages from VS Code, making previewing and debugging a lot easier. One of the core features is the live reload support that many developers are used to.
|
||||
|
||||
### REST Client
|
||||
|
||||
[REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) allows you to send HTTP requests and view the responses directly in VS Code. This extension supports a wide range of formats and authorization and should work with most setups.
|
||||
|
||||
### One Dark Pro
|
||||
|
||||
[One Dark Pro](https://marketplace.visualstudio.com/items?itemName=zhuangtongfa.Material-theme) is one of the most popular VS Code themes and with very good reason. It provides a clean theme with a nice palette that has great contrast and is very comfortable to use on a daily basis.
|
||||
|
||||
### Fira Code
|
||||
|
||||
[Fira Code](https://github.com/tonsky/FiraCode) is not a traditional VS Code extension and might take a couple more steps to set up, but it's a superb programming font with ligatures that will help you scan code faster once you get used to it.
|
||||
303
snippets/articles/s/25-css-gradients.md
Normal file
303
snippets/articles/s/25-css-gradients.md
Normal file
@ -0,0 +1,303 @@
|
||||
---
|
||||
title: 25 CSS gradients for your next project
|
||||
shortTitle: CSS gradients
|
||||
type: cheatsheet
|
||||
language: css
|
||||
tags: [visual]
|
||||
author: chalarangelo
|
||||
cover: colors-mural
|
||||
excerpt: We hand picked 25 of our favorite CSS gradients from uiGradients for your next design. Get them now!
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
[uiGradients](https://uigradients.com/) has an amazing collection of ready-to-use CSS gradients for pretty much anything. I highly recommend checking out the full collection. Meantime, here are our top picks in case you're looking for some color:
|
||||
|
||||
<style>
|
||||
.gradient-box {
|
||||
width: 100%;
|
||||
height: 64px;
|
||||
border-radius: 8px 8px 0 0;
|
||||
margin-bottom: -16px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.gradient-box + .gatsby-highlight > pre.blog-code {
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.stripe {
|
||||
background: linear-gradient(to right, #1fa2ff, #12d8fa, #a6ffcb);
|
||||
}
|
||||
.flare {
|
||||
background: linear-gradient(to right, #f12711, #f5af19);
|
||||
}
|
||||
.vanusa {
|
||||
background: linear-gradient(to right, #da4453, #89216b);
|
||||
}
|
||||
.sublime-light {
|
||||
background: linear-gradient(to right, #fc5c7d, #6a82fb);
|
||||
}
|
||||
.bighead {
|
||||
background: linear-gradient(to right, #c94b4b, #4b134f);
|
||||
}
|
||||
.velvet-sun {
|
||||
background: linear-gradient(to right, #e1eec3, #f05053);
|
||||
}
|
||||
.relay {
|
||||
background: linear-gradient(to right, #3a1c71, #d76d77, #ffaf7b);
|
||||
}
|
||||
.crystal-clear {
|
||||
background: linear-gradient(to right, #159957, #155799);
|
||||
}
|
||||
.celestial {
|
||||
background: linear-gradient(to right, #c33764, #1d2671);
|
||||
}
|
||||
.ibiza-sunset {
|
||||
background: linear-gradient(to right, #ee0979, #ff6a00);
|
||||
}
|
||||
.fresh-turboscent {
|
||||
background: linear-gradient(to right, #f1f2b5, #135058);
|
||||
}
|
||||
.cheer-up-emo-kid {
|
||||
background: linear-gradient(to right, #556270, #ff6b6b);
|
||||
}
|
||||
.starfall {
|
||||
background: linear-gradient(to right, #f0c27b, #4b1248);
|
||||
}
|
||||
.nelson {
|
||||
background: linear-gradient(to right, #f2709c, #ff9472);
|
||||
}
|
||||
.forever-lost {
|
||||
background: linear-gradient(to right, #5d4157, #a8caba);
|
||||
}
|
||||
.blurry-beach {
|
||||
background: linear-gradient(to right, #d53369, #cbad6d);
|
||||
}
|
||||
.influenza {
|
||||
background: linear-gradient(to right, #c04848, #480048);
|
||||
}
|
||||
.calm-darya {
|
||||
background: linear-gradient(to right, #5f2c82, #49a09d);
|
||||
}
|
||||
.titanium {
|
||||
background: linear-gradient(to right, #283048, #859398);
|
||||
}
|
||||
.pinky {
|
||||
background: linear-gradient(to right, #dd5e89, #f7bb97);
|
||||
}
|
||||
.purple-paradise {
|
||||
background: linear-gradient(to right, #1d2b64, #f8cdda);
|
||||
}
|
||||
.horizon {
|
||||
background: linear-gradient(to right, #003973, #e5e5be);
|
||||
}
|
||||
.noon-to-dusk {
|
||||
background: linear-gradient(to right, #ff6e7f, #bfe9ff);
|
||||
}
|
||||
.jshine {
|
||||
background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
|
||||
}
|
||||
.argon {
|
||||
background: linear-gradient(to right, #03001e, #7303c0, #ec38bc, #fdeff9);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="gradient-box stripe"></div>
|
||||
|
||||
```css
|
||||
.stripe {
|
||||
background: linear-gradient(to right, #1fa2ff, #12d8fa, #a6ffcb);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box flare"></div>
|
||||
|
||||
```css
|
||||
.flare {
|
||||
background: linear-gradient(to right, #f12711, #f5af19);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box vanusa"></div>
|
||||
|
||||
```css
|
||||
.vanusa {
|
||||
background: linear-gradient(to right, #da4453, #89216b);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box sublime-light"></div>
|
||||
|
||||
```css
|
||||
.sublime-light {
|
||||
background: linear-gradient(to right, #fc5c7d, #6a82fb);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box bighead"></div>
|
||||
|
||||
```css
|
||||
.bighead {
|
||||
background: linear-gradient(to right, #c94b4b, #4b134f);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box velvet-sun"></div>
|
||||
|
||||
```css
|
||||
.velvet-sun {
|
||||
background: linear-gradient(to right, #e1eec3, #f05053);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box argon"></div>
|
||||
|
||||
```css
|
||||
.argon {
|
||||
background: linear-gradient(to right, #03001e, #7303c0, #ec38bc, #fdeff9);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box celestial"></div>
|
||||
|
||||
```css
|
||||
.celestial {
|
||||
background: linear-gradient(to right, #c33764, #1d2671);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box relay"></div>
|
||||
|
||||
```css
|
||||
.relay {
|
||||
background: linear-gradient(to right, #3a1c71, #d76d77, #ffaf7b);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box crystal-clear"></div>
|
||||
|
||||
```css
|
||||
.crystal-clear {
|
||||
background: linear-gradient(to right, #159957, #155799);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box ibiza-sunset"></div>
|
||||
|
||||
```css
|
||||
.ibiza-sunset {
|
||||
background: linear-gradient(to right, #ee0979, #ff6a00);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box fresh-turboscent"></div>
|
||||
|
||||
```css
|
||||
.fresh-turboscent {
|
||||
background: linear-gradient(to right, #f1f2b5, #135058);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box cheer-up-emo-kid"></div>
|
||||
|
||||
```css
|
||||
.cheer-up-emo-kid {
|
||||
background: linear-gradient(to right, #556270, #ff6b6b);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box starfall"></div>
|
||||
|
||||
```css
|
||||
.starfall {
|
||||
background: linear-gradient(to right, #f0c27b, #4b1248);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box nelson"></div>
|
||||
|
||||
```css
|
||||
.nelson {
|
||||
background: linear-gradient(to right, #f2709c, #ff9472);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box forever-lost"></div>
|
||||
|
||||
```css
|
||||
.forever-lost {
|
||||
background: linear-gradient(to right, #5d4157, #a8caba);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box blurry-beach"></div>
|
||||
|
||||
```css
|
||||
.blurry-beach {
|
||||
background: linear-gradient(to right, #d53369, #cbad6d);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box influenza"></div>
|
||||
|
||||
```css
|
||||
.influenza {
|
||||
background: linear-gradient(to right, #c04848, #480048);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box jshine"></div>
|
||||
|
||||
```css
|
||||
.jshine {
|
||||
background: linear-gradient(to right, #12c2e9, #c471ed, #f64f59);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box calm-darya"></div>
|
||||
|
||||
```css
|
||||
.calm-darya {
|
||||
background: linear-gradient(to right, #5f2c82, #49a09d);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box titanium"></div>
|
||||
|
||||
```css
|
||||
.titanium {
|
||||
background: linear-gradient(to right, #283048, #859398);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box pinky"></div>
|
||||
|
||||
```css
|
||||
.pinky {
|
||||
background: linear-gradient(to right, #dd5e89, #f7bb97);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box purple-paradise"></div>
|
||||
|
||||
```css
|
||||
.purple-paradise {
|
||||
background: linear-gradient(to right, #1d2b64, #f8cdda);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box horizon"></div>
|
||||
|
||||
```css
|
||||
.horizon {
|
||||
background: linear-gradient(to right, #003973, #e5e5be);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="gradient-box noon-to-dusk"></div>
|
||||
|
||||
```css
|
||||
.noon-to-dusk {
|
||||
background: linear-gradient(to right, #ff6e7f, #bfe9ff);
|
||||
}
|
||||
```
|
||||
59
snippets/articles/s/4-javascript-array-methods.md
Normal file
59
snippets/articles/s/4-javascript-array-methods.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: 4 JavaScript Array methods you must know
|
||||
shortTitle: Useful array methods
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [array,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: arrays
|
||||
excerpt: JavaScript arrays have a very robust API offering some amazing tools. Learn the 4 must-know JavaScript array methods in this quick guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript arrays have a very robust API offering a plethora of amazing tools. Here are our top 4 JavaScript array methods every developer should know:
|
||||
|
||||
### Array.prototype.map()
|
||||
|
||||
[`Array.prototype.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) creates a new array by applying the provided transformation to each element of the original array. The result is an array with the same length as the original array and elements transformed based on the provided function.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
const double = x => x * 2;
|
||||
arr.map(double); // [2, 4, 6]
|
||||
```
|
||||
|
||||
### Array.prototype.filter()
|
||||
|
||||
[`Array.prototype.filter()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) creates a new array by using a filtering function to keep only elements that return `true` based on that function. The result is an array with equal or less than the original array's length, containing a subset of the same elements as the original array.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
const isOdd = x => x % 2 === 1;
|
||||
arr.filter(isOdd); // [1, 3]
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Array.prototype.reduce()
|
||||
|
||||
[`Array.prototype.reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) creates an output value of any type depending on a reducer function and an initial value. The result can be of any type such as an integer, an object or an array, based on the reducer function provided.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
|
||||
const sum = (x, y) => x + y;
|
||||
arr.reduce(sum, 0); // 6
|
||||
|
||||
const increment = (x, y) => [...x, x[x.length - 1] + y];
|
||||
arr.reduce(increment, [0]); // [0, 1, 3, 6]
|
||||
```
|
||||
|
||||
### Array.prototype.find()
|
||||
|
||||
[`Array.prototype.find()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) returns the first element for which a matcher function returns `true`. The result is a single element from the original array.
|
||||
|
||||
```js
|
||||
const arr = [1, 2, 3];
|
||||
const isOdd = x => x % 2 === 1;
|
||||
arr.find(isOdd); // 1
|
||||
```
|
||||
27
snippets/articles/s/4-seo-tips-for-developers.md
Normal file
27
snippets/articles/s/4-seo-tips-for-developers.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: 4 SEO tips for developers
|
||||
shortTitle: SEO tips
|
||||
type: story
|
||||
tags: [webdev,seo]
|
||||
author: chalarangelo
|
||||
cover: sunrise-over-city
|
||||
excerpt: SEO is a very relevant topics that most developers are inexperienced in. Here are 4 actionable SEO tips you can implement today.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
SEO is becoming increasingly relevant as the internet keeps growing. While most web developers usually have little experience with SEO, it is not particularly difficult to handle some of the basics. Here are my top SEO tips for web developers:
|
||||
|
||||
### Craft user-friendly URLs and map the appropriately
|
||||
|
||||
First and foremost, start at the very beginning, which is your **URL slugs**. For URL slugs the rule is that you want them to be human-readable, with words separated by dashes and with no random letters or digits added. Secondly, it's a good idea to make sure that parts of your URLs match to a **logical structure** in your website (e.g. `/blog/posts/awesome-list-of-seo-tips`). Finally, take the time to build a sitemap and redirect any broken or old URLs to new ones. This will help reduce 404 pages.
|
||||
|
||||
### Use structured data to help Google understand your pages
|
||||
|
||||
**Structured data** is what Google uses to power its **featured snippets**, those little cards that appear at the top of certain searches. It's a good idea to set up structured data for your pages, which will help you rank higher in search results and possibly even land a featured snippet every once in a while. Just make sure to find the appropriate structured data type and include it in your page and you should be good to go.
|
||||
### Set up Google Analytics and Google Search Console
|
||||
|
||||
This is hardly a new tip, but I think it deserves a spot on the list, as both of these tools are extremely important. **Google Analytics** allows you to track user behavior and gather data that can help you identify problems and opportunities, while **Google Search Console** is helpful for figuring out what users are searching for before landing on your website.
|
||||
|
||||
### Keep an eye on your markup, performance and accessibility
|
||||
|
||||
Last but not least, something that is probably already on your list, is **optimizing your code**. This includes everything from writing semantic markup and keeping requests to a minimum to optimizing for all device types, making your website accessible and ensuring fast load times. Keep in mind that websites have many moving parts that evolve and change over time, so it's a good idea to audit your website often with a tool like Lighthouse.
|
||||
33
snippets/articles/s/5-tips-for-better-pull-requests.md
Normal file
33
snippets/articles/s/5-tips-for-better-pull-requests.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: 5 tips for better Pull Requests
|
||||
type: story
|
||||
language: git
|
||||
tags: [github,programming,webdev]
|
||||
author: chalarangelo
|
||||
cover: keyboard-tea
|
||||
excerpt: Writing good code is only part of the job. Here are 5 tips to improve your pull requests and help people review them.
|
||||
unlisted: true
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Writing good code is only part of the job. Here are 5 tips to improve your pull requests and help people review them:
|
||||
|
||||
### Small pull requests
|
||||
|
||||
The pull requests that get reviewed more thoroughly and confidently and are most often prioritized by developers with limited time are the smallest ones. Make sure you separate concerns into different pull requests (e.g. refactoring and feature implementation), while also keeping commits atomic and well-documented to make the changes easier to understand and review.
|
||||
|
||||
### Good descriptions
|
||||
|
||||
Always take the time to describe your code and any related tasks in your pull request. Explain the feature you are implementing or the bug you are fixing and provide images and steps to reproduce, if applicable. Note decisions made during implementation, your approach, as well as any limitations, findings and points of interest that might help others better understand your code.
|
||||
|
||||
### Rebase onto master
|
||||
|
||||
Always rebase your pull requests onto the `master` branch of the repository. This way you can always test your code against the latest changes and resolve merge conflicts, minimizing issues that might arise later on. Apart from that, reviewers will not have to deal with missing features or bug fixes that might have been deployed already, which can considerably speed up review times.
|
||||
|
||||
### Review it yourself
|
||||
|
||||
Before submitting your pull request for review, always take the time to review it yourself. That way you can handle some low-hanging fruits (typos, easy optimizations, leftover code etc.) and check things you would in other people's pull requests. Self-reviewing has the added benefit of allowing you to reason about decisions and realize which ones might need clarification.
|
||||
|
||||
### Respond to reviews
|
||||
|
||||
Set some time aside to respond to reviews after submitting your pull request. Handle anything you can as soon as possible and start discussion whenever necessary to arrive to a solution. Use `--fixup` for changes suggested in review comments or add new commits to help reviewers parse new changes more easily. Finally, assume good intentions, be polite and thank your peers.
|
||||
121
snippets/articles/s/6-javascript-regexp-tricks.md
Normal file
121
snippets/articles/s/6-javascript-regexp-tricks.md
Normal file
@ -0,0 +1,121 @@
|
||||
---
|
||||
title: 6 JavaScript Regular Expression features you can use today
|
||||
shortTitle: JavaScript Regular Expression tips
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [string,regexp]
|
||||
author: chalarangelo
|
||||
cover: taking-photos
|
||||
excerpt: Regular expressions are very powerful, but hard to master. Understand these features and start using them in your JavaScript code.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Regular expressions, while very powerful, are notoriously hard to master. Here are 6 useful features that can help you start using them in your JavaScript projects:
|
||||
|
||||
### Capturing groups
|
||||
|
||||
Capturing groups allow you to get specific parts of the matched string, simply by wrapping part of the regular expression in parentheses `(...)`:
|
||||
|
||||
```js
|
||||
const str = 'JavaScript is a programming language';
|
||||
/(JavaScript) is a (.*)/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'JavaScript is a programming language',
|
||||
1: 'JavaScript',
|
||||
2: 'programming language'
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Non-capturing groups
|
||||
|
||||
Non-capturing groups are used for matching something without capturing it, like an either/or matching group that you do not really need. They are defined similarly to capturing groups, but prefixed with `?:`:
|
||||
|
||||
```js
|
||||
const str = 'JavaScript is a programming language';
|
||||
/(?:JavaScript|Python) is a (.+)/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'JavaScript is a programming language',
|
||||
1: 'programming language'
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Named capturing groups
|
||||
|
||||
Named capturing groups allow you to name a capturing group, by prefixing it with `<name>`:
|
||||
|
||||
```js
|
||||
const str = 'JavaScript is a programming language';
|
||||
/(?<subject>.+) is a (?<description>.+)/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'JavaScript is a programming language',
|
||||
1: 'JavaScript',
|
||||
2: 'programming language',
|
||||
groups: {
|
||||
subject: 'JavaScript,
|
||||
description: 'programming language'
|
||||
}
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Capturing group backreferences
|
||||
|
||||
Backreferences help you write shorter regular expressions, by repeating an existing capturing group, using `\1`, `\2` etc. Similarly, you can also repeat named capturing groups using `\k<name>`:
|
||||
|
||||
```js
|
||||
const str = 'JavaScript is a programming language - an awesome programming language JavaScript is';
|
||||
/(.+) is a (?<description>.+) - an awesome \k<description> \1 is/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'JavaScript is a programming language - an awesome programming language JavaScript is',
|
||||
1: 'JavaScript',
|
||||
2: 'programming language',
|
||||
groups: {
|
||||
subject: 'JavaScript,
|
||||
description: 'programming language'
|
||||
}
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Lookaheads
|
||||
|
||||
Lookaheads allow you to check if something is followed by a certain pattern, without actually matching it. You can create positive lookaheads using `?=` and negative lookaheads using `?!`:
|
||||
|
||||
```js
|
||||
const str = 'JavaScript is not the same as Java and you should remember that';
|
||||
/Java(?=Script)(.*)/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'JavaScript is not the same as Java and you should remember that',
|
||||
1: 'Script is not the same as Java and you should remember that'
|
||||
]
|
||||
*/
|
||||
|
||||
/Java(?!Script)(.*)/.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'Java and you should remember that',
|
||||
1: ' and you should remember that'
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
### Unicode characters
|
||||
|
||||
Finally, you can match unicode characters, using `/p{...}` and the `/u` flag. Examples include, but are not limited to `{Emoji}`, `{Math_Symbols}` and `{Script=Greek}`:
|
||||
|
||||
```js
|
||||
const str = 'Greek looks like this: γεια';
|
||||
/\p{Script=Greek}+/u.exec(str);
|
||||
/*
|
||||
[
|
||||
0: 'γεια'
|
||||
]
|
||||
*/
|
||||
```
|
||||
80
snippets/articles/s/6-python-f-strings-tips.md
Normal file
80
snippets/articles/s/6-python-f-strings-tips.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: 6 Python f-strings tips and tricks
|
||||
type: story
|
||||
language: python
|
||||
tags: [string]
|
||||
author: chalarangelo
|
||||
cover: sea-view
|
||||
excerpt: Python's f-strings can do a lot more than you might expect. Learn a few useful tips and tricks in this quick guide.
|
||||
dateModified: 2021-07-20T05:00:00-04:00
|
||||
---
|
||||
|
||||
Python's f-strings provide a more readable, concise and less error-prone way to format strings than traditional string formatting. They are packed with useful features that are sure to come in handy in day-to-day use. Let's take a look at some of them.
|
||||
|
||||
### String Interpolation
|
||||
|
||||
The most used f-string feature by far is string interpolation. All you need to do is wrap the value or variable in curly braces (`{}`) and you're good to go.
|
||||
|
||||
```py
|
||||
str_val = 'apples'
|
||||
num_val = 42
|
||||
|
||||
print(f'{num_val} {str_val}') # 42 apples
|
||||
```
|
||||
|
||||
### Variable names
|
||||
|
||||
Apart from getting a variable's value, you can also get its name alongside the value. This can be especially useful when debugging and can be easily accomplished by adding an equals sign (`=`) after the variable name inside the curly braces.
|
||||
|
||||
Bear in mind that whitespace inside the curly braces is taken into account, so adding spaces around the equals sign can make for a more readable result.
|
||||
|
||||
```py
|
||||
str_val = 'apples'
|
||||
num_val = 42
|
||||
|
||||
print(f'{str_val=}, {num_val = }') # str_val='apples', num_val = 42
|
||||
```
|
||||
|
||||
### Mathematical operations
|
||||
|
||||
Not syntactically unlike variable names, you can also perform mathematical operations in f-strings. You can place the mathematical expression inside the curly braces and, if you add an equal sign, you'll get the expression and its result.
|
||||
|
||||
```py
|
||||
num_val = 42
|
||||
|
||||
print(f'{num_val % 2 = }') # num_val % 2 = 0
|
||||
```
|
||||
|
||||
### Printable representation
|
||||
|
||||
Apart from plain string interpolation, you might want to get the printable representation of a value. This is already easy to accomplish using the `repr()` function. f-strings provide a much shorter syntax by appending a `!r` inside the curly braces.
|
||||
|
||||
```py
|
||||
str_val = 'apples'
|
||||
|
||||
print(f'{str_val!r}') # 'apples'
|
||||
```
|
||||
|
||||
### Number formatting
|
||||
|
||||
Additionally, f-strings can also be used for formatting - hence the **f** in the name. To add formatting to a value you can add a colon (`:`) followed by a format specifier. This can also be combined with the equals sing from before, shall you want to print the name of the variable as well.
|
||||
|
||||
Numbers are a great candidate for this. If, for example, you want to trim a numeric value to two digits after the decimal, you can use the `.2f` format specifier.
|
||||
|
||||
```py
|
||||
price_val = 6.12658
|
||||
|
||||
print(f'{price_val:.2f}') # 6.13
|
||||
```
|
||||
|
||||
### Date formatting
|
||||
|
||||
Finally, dates can also be formatted the same way as numbers, using format specifiers. As usual, `%Y` denotes the full year, `%m` is the month and `%d` is the day of the month.
|
||||
|
||||
```py
|
||||
from datetime import datetime;
|
||||
|
||||
date_val = datetime.utcnow()
|
||||
|
||||
print(f'{date_val=:%Y-%m-%d}') # date_val=2021-07-09
|
||||
```
|
||||
@ -0,0 +1,39 @@
|
||||
---
|
||||
title: 7 essential Chrome extensions for web developers
|
||||
shortTitle: Essential Chrome extensions
|
||||
type: story
|
||||
tags: [webdev,devtools]
|
||||
author: chalarangelo
|
||||
cover: computer-screens
|
||||
excerpt: Boost your productivity with 7 must-have Google Chrome developer tool extensions.
|
||||
unlisted: true
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Google Chrome's developer tools are nothing short of amazing, but there are a few missing tools that can increase your productivity even further. Here are my personal favorite extensions that I use everyday:
|
||||
|
||||
### CSS Peeper
|
||||
[CSS Peeper](https://chrome.google.com/webstore/detail/css-peeper/mbnbehikldjhnfehhnaidhjhoofhpehk?hl=en) is an all-in-one tool for CSS inspection that allows you to quickly peek at the styles, fonts, color palettes and assets of any website you want.
|
||||
|
||||
### React Developer Tools
|
||||
[React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) is the de facto extension when working with React, providing all the necessary inspection tools for your React applications.
|
||||
|
||||
### LastPass
|
||||
|
||||
[LastPass](https://chrome.google.com/webstore/detail/lastpass-free-password-ma/hdokiejnpimakedhajhdlcegeplioahd?hl=en) keeps your accounts and API keys secure, by providing a free password manager complete with password generation and password-protected notes.
|
||||
|
||||
### uBlock Origin
|
||||
|
||||
[uBlock Origin](https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm?hl=en) is a simple yet effective ad blocker extension that you can just install and forget about, as it keeps your browsing experience ad-free.
|
||||
|
||||
### VisBug
|
||||
|
||||
[VisBug](https://chrome.google.com/webstore/detail/visbug/cdockenadnadldjbbgcallicgledbeoc?hl=en) allows you to tweak website designs on the fly, using very intuitive controls and tools, without having to delve into any code at all.
|
||||
|
||||
### JSON Viewer
|
||||
|
||||
[JSON Viewer](https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh?hl=en) provides some much needed syntax highlighting and style customization to JSON responses, which are nowadays very commonplace in most web APIs.
|
||||
|
||||
### EditThisCookie
|
||||
|
||||
[EditThisCookie](https://chrome.google.com/webstore/detail/editthiscookie/fngmhnnpilhplaeedifhccceomclgfbg?hl=en) is a powerful cookie manager extension, that allows you to add, delete, edit and even export and import cookies quickly and efficiently.
|
||||
43
snippets/articles/s/8-tips-for-accessible-websites.md
Normal file
43
snippets/articles/s/8-tips-for-accessible-websites.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: 8 tips for accessible websites
|
||||
shortTitle: Accessibility tips
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [accessibility,webdev]
|
||||
author: chalarangelo
|
||||
cover: accessibility
|
||||
excerpt: Accessibility (a11y) can improve your website and attract new users. Learn how to get started with these 8 quick tips.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Use semantic HTML
|
||||
|
||||
HTML5 introduced a variety of new semantic HTML elements to help replace the much dreaded `<div>`, such as `<section>`, `<main>`, `<article>`, `<nav>` etc. When developing a website, you should understand what each part of your layout represents and try to use the appropriate element for it.
|
||||
|
||||
### Use color correctly
|
||||
|
||||
[WCAG](https://www.w3.org/WAI/standards-guidelines/wcag/) specifies a minimum contrast ratio of `4.5:1` between text and background (viewable in Chrome Developer Tools), so you should always design with that in mind. Additionally, remember that color should be used meaningfully and sparsely to avoid confusion and information overload.
|
||||
|
||||
### Caption images and video
|
||||
|
||||
Try to provide `alt` attributes for your `<img>` elements, so that screenreaders don't read the `src` attribute. You can use empty `alt` attributes for decorative images, which will inform screenreaders to skip them. Similarly, try to provide closed captions for any video content on your website.
|
||||
|
||||
### Show and tell
|
||||
|
||||
Using icons and colors to indicate state, highlight or provide context is very common and provides a nice user experience. However, icons and colors alone might not be accessible for everyone, so make sure to support them with the appropriate text literals, catering to all of your users in the process.
|
||||
|
||||
### Be consistent
|
||||
|
||||
Elements with similar meaning and/or functionality should look similar across your website. This is especially true for `<a>` and `<button>` elements and their respective states as users will have to be able to identify easily what elements they can interact with and anticipate their behavior.
|
||||
|
||||
### Label your inputs
|
||||
|
||||
Any kind of `<input>` element should be labelled appropriately, using either a `<label>` wrapper, the `for` attribute or an `aria-label` attribute. Do not rely on `placeholder` attributes to convey meaning about your `<input>` elements as this will cause problems for users on screenreaders.
|
||||
|
||||
### Design responsively
|
||||
|
||||
Responsiveness is often thought in terms of screen size or mobile vs desktop, but there are many different devices where a user could browse your website. Try catering to any and all of them by providing ways to navigate and use your application via mouse, keyboard, thumb or any combination of the three.
|
||||
|
||||
### Organize your content
|
||||
|
||||
A website's layout should be easy to scan, understand and find the content that is relevant to the user. Good organization with clear sections and properly grouped content provides a better user experience for all users, regardless of device or accessibility needs.
|
||||
43
snippets/articles/s/8-tips-for-remote-work.md
Normal file
43
snippets/articles/s/8-tips-for-remote-work.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: 8 tips for working from home
|
||||
type: story
|
||||
tags: [webdev,career,programming,jobs]
|
||||
author: chalarangelo
|
||||
cover: kettle-laptop
|
||||
excerpt: Working from home seems great compared to going to the office, but there are challenges that come with it that you need to overcome.
|
||||
unlisted: true
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Working from home (also known as remote work) seems like a great alternative to going to the office every morning, but it comes with its own set of challenges that you need to overcome in order to stay healthy and productive. Here are my tips for achieving just that:
|
||||
|
||||
### Set up your work environment
|
||||
|
||||
Working from home has its perks, but nothing beats a well-designed office space where everything is set up with only one purpose in mind: working. Figure out your home office, experiment with different settings and understand what works best for you as soon as possible. An ideal working space is comfortable, quiet and has the right equipment for you.
|
||||
|
||||
### Establish ground rules
|
||||
|
||||
Most likely you are not living alone, so you have to establish some ground rules with your roommate or significant other. It's up to you to drive the point home that during working hours you are, for the most part, not home. Sure, you can answer the door if you expect a delivery, but that's pretty much as far as you can go. People should not bother you during working hours, unless absolutely necessary, as small distractions pile up fast.
|
||||
|
||||
### Inform others of your availability
|
||||
|
||||
It's important to let people know that you are online and working or that you are taking a short break for lunch. Remember that you are still part of a team that requires coordination and others probably depend on your work to some extent. Remember to update your status as necessary to make collaboration easier.
|
||||
|
||||
### Socialize with coworkers
|
||||
|
||||
Working from home can lead to feelings of loneliness, disconnect, isolation which can quickly spin out of control and lead to depression. Communicate with people on your team as if you were in the same room. A healthy amount of communication will help you feel more like you are all working together rather than each one on their own.
|
||||
|
||||
5. Be your best professional self
|
||||
Nobody might be watching you at home, so you can theoretically slack off as much as you like in your pajamas, but that's not very professional. Try to dress appropriately in case you join a video call and behave professionally, so no inappropriate websites or hours upon hours of checking social media. Ask yourself if someone in a shared office space would do whatever it is you are doing and, if the answer is no, stop doing it.
|
||||
|
||||
### Plan your daily and weekly tasks
|
||||
|
||||
Having a coherent working plan helps you organize your time and prioritize important tasks above trivial ones. It also helps to put things into perspective and have a general idea of what other people on the team are working on. Plan ahead of time together with your team and keep each other posted on the progress of each task. Short term plans help you get through the day, long term plans help everyone meet their deadlines.
|
||||
|
||||
### Use the right collaboration tools
|
||||
|
||||
Working from home has its own challenges and issues, so try to find the right tools for the job. Slack, Skype, Zoom, Hangouts are great for communication. Design tools such as Figma or Sketch cloud help you communicate designs quickly and effectively. GitHub is the perfect tool for code collaboration and VS Code has an extension (Live Share) for sharing your code editors. Communicate with your team, figure out your needs and pick tools that work for you.
|
||||
|
||||
### Maintain regular hours
|
||||
|
||||
It's as easy to forget about breaks as it is to start working late. Try to set a schedule that sits well with your team and stick to it. If you would work 9 to 5 in a regular office environment, do the same at home. Take regular breaks, do not forget to sign off when put in your daily hours and come back tomorrow. After all, it's business as usual.
|
||||
113
snippets/articles/s/async-javascript-cheatsheet.md
Normal file
113
snippets/articles/s/async-javascript-cheatsheet.md
Normal file
@ -0,0 +1,113 @@
|
||||
---
|
||||
title: Asynchronous JavaScript Cheat Sheet
|
||||
type: cheatsheet
|
||||
language: javascript
|
||||
tags: [function,promise]
|
||||
author: chalarangelo
|
||||
cover: green-plant
|
||||
excerpt: Learn everything you need to know about promises and asynchronous JavaScript with this handy cheatsheet.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Promise basics
|
||||
|
||||
- **Promises** start in a **pending state**, neither fulfilled or rejected.
|
||||
- When the operation is completed, a promise will become **fulfilled with a value**.
|
||||
- If the operation fails, a promise will get **rejected with an error**.
|
||||
|
||||
### Creating promises
|
||||
|
||||
- The function passed to the `Promise` constructor will execute synchronously.
|
||||
- Use `resolve()` or `reject()` to create promises from values.
|
||||
- `Promise.resolve(val)` will fulfill the promise with `val`.
|
||||
- `Promise.reject(err)` will reject the promise with `err`.
|
||||
- If you put a fulfilled promise into a fulfilled promise, they will collapse into one.
|
||||
|
||||
```js
|
||||
// Resolving with a value, rejecting with an error
|
||||
new Promise((resolve, reject) => {
|
||||
performOperation((err, val) => {
|
||||
if (err) reject(err);
|
||||
else resolve(val);
|
||||
});
|
||||
});
|
||||
|
||||
// Resolving without value, no need for reject
|
||||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
```
|
||||
|
||||
### Handling promises
|
||||
|
||||
- `Promise.prototype.then()` accepts two optional arguments (`onFulfilled`, `onRejected`).
|
||||
- `Promise.prototype.then()` will call `onFulfilled` once the promise is fulfilled.
|
||||
- `Promise.prototype.then()` will call `onRejected` if the promise is rejected.
|
||||
- `Promise.prototype.then()` passes errors through if `onRejected` in undefined.
|
||||
|
||||
- `Promise.prototype.catch()` accepts one argument (`onRejected`).
|
||||
- `Promise.prototype.catch()` behaves like `Promise.prototype.then()` when `onFulfilled` is omitted.
|
||||
- `Promise.prototype.catch()` passes fulfilled values through.
|
||||
|
||||
- `Promise.prototype.finally()` accepts one argument (`onFinally`).
|
||||
- `Promise.prototype.finally()` calls `onFinally` with no arguments once any outcome is available.
|
||||
- `Promise.prototype.finally()` passes through input promise.
|
||||
|
||||
```js
|
||||
promisedOperation()
|
||||
.then(
|
||||
val => value + 1, // Called once the promise is fulfilled
|
||||
err => { // Called if the promise is rejected
|
||||
if (err === someKnownErr) return defaultVal;
|
||||
else throw err;
|
||||
}
|
||||
)
|
||||
.catch(
|
||||
err => console.log(err); // Called if the promise is rejected
|
||||
)
|
||||
.finally(
|
||||
() => console.log('Done'); // Called once any outcome is available
|
||||
);
|
||||
```
|
||||
|
||||
- All three of the above methods will not be executed at least until the next tick, even for promises that already have an outcome.
|
||||
|
||||
### Combining promises
|
||||
|
||||
- `Promise.all()` turns an array of promises into a promise of an array.
|
||||
- If any promise is rejected, the error will pass through.
|
||||
- `Promise.race()` passes through the first settled promise.
|
||||
|
||||
```js
|
||||
Promise
|
||||
.all([ p1, p2, p3 ])
|
||||
.then(([ v1, v2, v3 ]) => {
|
||||
// Values always correspond to the order of promises,
|
||||
// not the order they resolved in (i.e. v1 corresponds to p1)
|
||||
});
|
||||
|
||||
Promise
|
||||
.race([ p1, p2, p3 ])
|
||||
.then(val => {
|
||||
// val will take the value of the first resolved promise
|
||||
});
|
||||
```
|
||||
|
||||
### async/await
|
||||
|
||||
- Calling an `async` function always results in a promise.
|
||||
- `(async () => value)()` will resolve to `value`.
|
||||
- `(async () => throw err)()` will reject with an error.
|
||||
- `await` waits for a promise to be fulfilled and returns its value.
|
||||
- `await` can only be used in `async` functions.
|
||||
- `await` also accepts non-promise values.
|
||||
- `await` always waits at least until the next tick before resolving, even when waiting already fulfilled promises or non-promise values.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
let val = await promisedValue();
|
||||
// Do stuff here
|
||||
} catch (err) {
|
||||
// Handle error
|
||||
}
|
||||
}
|
||||
```
|
||||
19
snippets/articles/s/bash-alias-dollar-sign.md
Normal file
19
snippets/articles/s/bash-alias-dollar-sign.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
title: "Tip: A bash alias for copying from the web"
|
||||
shortTitle: Bash alias for copying from the web
|
||||
type: tip
|
||||
language: git
|
||||
tags: [configuration]
|
||||
author: chalarangelo
|
||||
cover: capsule-coffee
|
||||
excerpt: Many online resources prefix their terminal commands with a dollar sign. Luckily, we've got a solution to this small annoyance.
|
||||
dateModified: 2023-03-05T05:00:00-04:00
|
||||
---
|
||||
|
||||
If you've ever copied a terminal command from the web, chances are you've also copied the dollar sign (`$`) that precedes it. This is a small annoyance that many developers encounter almost daily, but it can be easily dealt with. All you need to do is add an `alias` for the dollar sign to your `.bashrc` or `.zshrc` file:
|
||||
|
||||
```shell
|
||||
alias '$'=''
|
||||
```
|
||||
|
||||
But what about variables? Aren't these prefixed with a dollar sign? This alias won't break your scripts, as aliases replace only the first word of each simple command. Simply put, the only dollar sign that will be replaced is the one at the beginning of the line.
|
||||
29
snippets/articles/s/benefits-of-writing.md
Normal file
29
snippets/articles/s/benefits-of-writing.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: The benefits of writing
|
||||
type: story
|
||||
tags: [webdev,career,programming,jobs]
|
||||
author: chalarangelo
|
||||
cover: laptop-view
|
||||
excerpt: Writing about a topic can often sound boring or worthless. But there are significant benefits to doing it from time to time.
|
||||
dateModified: 2022-11-05T14:00:07+03:00
|
||||
---
|
||||
|
||||
I was recently tasked with writing a design document at work. What first seemed like a tiresome chore I had to endure turned out to be both enlightening and beneficial both to me and my team. This isn't just a case of making the best of a bad situation, but rather a case of rediscovering the reason behind why writing down your thoughts isn't an exercise in futility. Instead of boring you with a long-winded story about the task, I'll just present the takeaways and hopefully do some storytelling in the process. So why should you write then?
|
||||
|
||||
### You learn more about the topic at hand
|
||||
|
||||
Presenting a topic requires meticulous **research** in order for you to be able to write down the most interesting and relevant findings. This process will uncover any **gaps in your knowledge** of said topic, gaps your research will in turn have to cover. I've seen this numerous times in the past while doing research both for this website as well as tasks at work and it always holds true. If nothing else, you will gain a **deeper understanding** of whatever it is you are presenting, which will be useful sometime in the future. In the case of my task, I understood more about the business logic, how premature optimizations got in the way of stability and why it was so hard to consistently reach the desired state, as I discovered a hidden layer of complexity I didn't know about before.
|
||||
|
||||
### You discover the root of the problem
|
||||
|
||||
Most of the time we assume to know the problem well enough to try and solve it. A lot of the time, I've found out, I know a lot less than I originally thought about the domain, the problem and, more importantly, the root cause of it. Again, **presenting the problem to a reader** will put you through the process of having to **explain it** and boil it down to the key details. This more often than not results in a concise description of whatever is at the **root of the problem**. Working on my design document, I figured out that the root of the problem was essentially a set of bad assumptions that kept piling up and had some mismatches between the backend and frontend code.
|
||||
|
||||
### You understand past decisions
|
||||
|
||||
This one applies to problems you aren't the first one to tackle, but might still be relevant in other situations. As you go you will come across whatever **implementation details** you didn't know about. If you take a closer look, you'll figure out how those came to be in the first place and **why those decisions were made** in the past. Understanding the reasons behind past decisions is crucial in figuring out if the original set of needs, restrictions and assumptions is still valid and how to amend them if necessary. As I was already knee-deep in this task, I realized that the decisions we made almost one year prior were valid at the time, decisions I didn't fully understand back then. A couple of them, however, didn't seem to stand the test of time as business needs changed and we'd have to update them accordingly.
|
||||
|
||||
### You come up with more varied solutions
|
||||
|
||||
Understanding past decisions, covering the gaps in your knowledge and boiling down the problem to a concise description you should already be a lot more informed about the topic you are researching than before. Hopefully at this stage, you will have an itch to try **a different solution** from the one you originally envisioned or you'll have come up with a solution where there was none. More varied solutions are always better because you can **compare tradeoffs** and get a better understanding of what's best. In my case, I ended up considering a solution I had mostly dismissed originally. And it was that solution which made the most sense to me in the end, even though it was more radical than the one I was planning to propose originally.
|
||||
|
||||
As a closing thought, try writing even if nobody is going to read it. Write it for you. It will help you understand, explain, reason about and tackle the task all at once. It's not a waste of time, it's a powerful tool you could leverage from time to time.
|
||||
64
snippets/articles/s/big-o-cheatsheet.md
Normal file
64
snippets/articles/s/big-o-cheatsheet.md
Normal file
@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Big-O Cheat Sheet
|
||||
type: cheatsheet
|
||||
language: javascript
|
||||
tags: [algorithm]
|
||||
author: chalarangelo
|
||||
cover: light-ring
|
||||
excerpt: Learn everything you need to know about Big-O notation with this handy cheatsheet.
|
||||
dateModified: 2023-01-08T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Definition
|
||||
|
||||
Big-O notation, represents an algorithm's **worst-case complexity**. It uses algebraic terms to describe the complexity of an algorithm, allowing you to measure its efficiency and performance. Below you can find a chart that illustrates Big-O complexity:
|
||||
|
||||

|
||||
|
||||
Simply put, `O(1)` stands for **constant time complexity**, which is the most efficient, while `O(n!)` stands for **factorial time complexity**, which is the least efficient. The `n` in the complexity represents the size of the input, so `O(n)` means that the algorithm's time complexity will grow linearly with the size of the input.
|
||||
|
||||
Apart from Big-O notation, there are other notations that are used to describe the complexity of an algorithm, such as `Ω` (Omega) and `Θ` (Theta). `Ω` describes the **best-case complexity** of an algorithm, while `Θ` describes the **average-case complexity** of an algorithm.
|
||||
|
||||
### Common Data Structure operations
|
||||
|
||||
Different data structures have different time complexities for the same operations. For example, a linked list has `O(1)` time complexity for `insert` and `delete` operations, while an array has `O(n)` time complexity for the same operations. Below you can find average and worst time complexities for data structures used commonly in web development.
|
||||
|
||||
#### Average time complexity
|
||||
|
||||
| Data Structure | Access | Search | Insertion | Deletion |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [**Array**](/articles/s/js-native-data-structures) | Θ(1) | Θ(n) | Θ(n) | Θ(n) |
|
||||
| [**Queue**](/articles/s/js-data-structures-queue) | Θ(n) | Θ(n) | Θ(1) | Θ(1) |
|
||||
| [**Stack**](/articles/s/js-data-structures-stack) | Θ(n) | Θ(n) | Θ(1) | Θ(1) |
|
||||
| [**Linked List**](/articles/s/js-data-structures-linked-list) | Θ(n) | Θ(n) | Θ(1) | Θ(1) |
|
||||
| [**Doubly Linked List**](/articles/s/js-data-structures-doubly-linked-list) | Θ(n) | Θ(n) | Θ(1) | Θ(1) |
|
||||
| **Skip List** | Θ(log n) | Θ(log n) | Θ(log n) | Θ(log n) |
|
||||
| **Hash Table** | N/A | Θ(1) | Θ(1) | Θ(1) |
|
||||
| [**Binary Search Tree**](/articles/s/js-data-structures-binary-search-tree) | Θ(log n) | Θ(log n) | Θ(log n) | Θ(log n) |
|
||||
|
||||
#### Worst time complexity
|
||||
|
||||
| Data Structure | Access | Search | Insertion | Deletion |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| [**Array**](/articles/s/js-native-data-structures) | O(1) | O(n) | O(n) | O(n) |
|
||||
| [**Queue**](/articles/s/js-data-structures-queue) | O(n) | O(n) | O(1) | O(1) |
|
||||
| [**Stack**](/articles/s/js-data-structures-stack) | O(n) | O(n) | O(1) | O(1) |
|
||||
| [**Linked List**](/articles/s/js-data-structures-linked-list) | O(n) | O(n) | O(1) | O(1) |
|
||||
| [**Doubly Linked List**](/articles/s/js-data-structures-doubly-linked-list) | O(n) | O(n) | O(1) | O(1) |
|
||||
| **Skip List** | O(n) | O(n) | O(n) | O(n) |
|
||||
| **Hash Table** | N/A | O(n) | O(n) | O(n) |
|
||||
| [**Binary Search Tree**](/articles/s/js-data-structures-binary-search-tree) | O(n) | O(n) | O(n) | O(n) |
|
||||
|
||||
### Array sorting algorithms
|
||||
|
||||
Similar to data structures, different array sorting algorithms have different time complexities. Below you can find the best, average and worst time complexities for the most common array sorting algorithms.
|
||||
|
||||
| Algorithm | Best | Average | Worst |
|
||||
| --- | --- | --- | --- |
|
||||
| [**Quick sort**](/js/s/quick-sort) | Ω(n log n) | Θ(n log n) | O(n^2) |
|
||||
| [**Merge sort**](/js/s/merge-sort) | Ω(n log n) | Θ(n log n) | O(n log n) |
|
||||
| [**Heap sort**](/js/s/heapsort) | Ω(n log n) | Θ(n log n) | O(n log n) |
|
||||
| [**Bubble sort**](/js/s/bubble-sort) | Ω(n) | Θ(n^2) | O(n^2) |
|
||||
| [**Insertion sort**](/js/s/insertion-sort) | Ω(n) | Θ(n^2) | O(n^2) |
|
||||
| [**Selection sort**](/js/s/selection-sort) | Ω(n^2) | Θ(n^2) | O(n^2) |
|
||||
| [**Bucket sort**](/js/s/bucket-sort) | Ω(n+k) | Θ(n+k) | O(n^2) |
|
||||
51
snippets/articles/s/breaking-react.md
Normal file
51
snippets/articles/s/breaking-react.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: Breaking React - a common pattern to avoid
|
||||
type: story
|
||||
language: react
|
||||
tags: [debugging]
|
||||
author: chalarangelo
|
||||
cover: broken-screen
|
||||
excerpt: As powerful as React is, it is also quite fragile at places. Did you know that a few lines can easily break your entire React application?
|
||||
dateModified: 2021-11-06T20:51:47+03:00
|
||||
---
|
||||
|
||||
I am by no means an expert React engineer, but I have a couple years of experience under my belt. React is a powerful library for building user interfaces, but it's also quite fragile at places. A common bug I have encountered is caused by **direct DOM manipulation in combination with React**. This is sort of an anti-pattern, as it can break your entire React application under the right circumstances and it's hard to debug.
|
||||
|
||||
Here's [a minimal example](https://codepen.io/chalarangelo/pen/jOEojVJ?editors=0010) of how to reproduce this bug, before we dive into explaining the problem and how to fix it:
|
||||
|
||||
```jsx
|
||||
const destroyElement = () =>
|
||||
document.getElementById('app').removeChild(document.getElementById('my-div'));
|
||||
|
||||
const App = () => {
|
||||
const [elementShown, updateElement] = React.useState(true);
|
||||
|
||||
return (
|
||||
<div id='app'>
|
||||
<button onClick={() => destroyElement()}>
|
||||
Delete element via querySelector
|
||||
</button>
|
||||
<button onClick={() => updateElement(!elementShown)}>
|
||||
Update element and state
|
||||
</button>
|
||||
{ elementShown ? <div id="my-div">I am the element</div> : null }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<App />
|
||||
);
|
||||
```
|
||||
|
||||
This is a pretty simple React application, with a container, two buttons and a state variable. The problem is it will crash if you click the button that calls `destroyElement()` and then click the other one. _Why?_ you might ask. The issue here might not be immediately obvious, but if you look at your browser console you will notice the following exception:
|
||||
|
||||
```
|
||||
Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
|
||||
```
|
||||
|
||||
This might still be cryptic, so let me explain what's going on. React uses its own representation of the DOM, called a **virtual DOM**, in order to figure out what to render. Usually, the virtual DOM will match the current DOM structure and React will process changes in props and state. It will then update the virtual DOM and then batch and send the necessary changes to the real DOM.
|
||||
|
||||
However, in this case React's virtual DOM and the real DOM are different, because of `destroyElement()` removing the `#my-div` element. As a result, when React tries to update the real DOM with the changes from the virtual DOM, the element cannot be removed as it doesn't exist anymore. This results in the above exception being thrown and your application breaking.
|
||||
|
||||
You can refactor `destroyElement()` to be part of the `App` component and interact with its state to fix the issue in this example. Regardless of the simplicity of the problem or the fix, it showcases how fragile React can be under circumstances. This is only compounded in a large codebase where many developers contribute code daily in different areas. In such a setting, issues like this can be easily introduced and tracking them down can be rather tricky. This is why I would advice you to be very careful when directly manipulating the DOM in combination with React.
|
||||
77
snippets/articles/s/code-anatomy-chaining-reduce-for-loop.md
Normal file
77
snippets/articles/s/code-anatomy-chaining-reduce-for-loop.md
Normal file
@ -0,0 +1,77 @@
|
||||
---
|
||||
title: Code Anatomy - For loops, array reduce and method chaining
|
||||
shortTitle: For loops, array reduce and method chaining
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [array,iterator]
|
||||
author: chalarangelo
|
||||
cover: case-study
|
||||
excerpt: There are many ways to iterate and transform array data in JavaScript. Learn how each one works and where you should use them.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### For loops
|
||||
|
||||
```js
|
||||
const files = [ 'foo.txt ', '.bar', ' ', 'baz.foo' ];
|
||||
let filePaths = [];
|
||||
|
||||
for (let file of files) {
|
||||
const fileName = file.trim();
|
||||
if(fileName) {
|
||||
const filePath = `~/cool_app/${fileName}`;
|
||||
filePaths.push(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
// filePaths = [ '~/cool_app/foo.txt', '~/cool_app/.bar', '~/cool_app/baz.foo']
|
||||
```
|
||||
|
||||
- Any `for` loop can be used - [read more about the different JavaScript loops](/blog/s/javascript-for-in-for-of-foreach/).
|
||||
- Less common nowadays, due to functional programming being more popular.
|
||||
- Control over the iteration, such as skipping over elements or early `return`s.
|
||||
- Resulting array needs to be declared beforehand, outside the loop.
|
||||
- Uses `Array.prototype.push()` or the spread (`...`) operator to add elements.
|
||||
- `O(N)` complexity, each element will be iterated over only once.
|
||||
|
||||
### Array reduce
|
||||
|
||||
```js
|
||||
const files = [ 'foo.txt ', '.bar', ' ', 'baz.foo' ];
|
||||
const filePaths = files.reduce((acc, file) => {
|
||||
const fileName = file.trim();
|
||||
if(fileName) {
|
||||
const filePath = `~/cool_app/${fileName}`;
|
||||
acc.push(filePath);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// filePaths = [ '~/cool_app/foo.txt', '~/cool_app/.bar', '~/cool_app/baz.foo']
|
||||
```
|
||||
|
||||
- Uses `Array.prototype.reduce()` with an empty array as the initial value.
|
||||
- More common nowadays, due to functional programming being more popular.
|
||||
- Less control over the iteration, cannot skip elements or `return` early.
|
||||
- Can be chained with other methods, if necessary.
|
||||
- Uses `Array.prototype.push()` or the spread (`...`) operator to add elements.
|
||||
- `O(N)` complexity, each element will be iterated over only once.
|
||||
|
||||
### Method chaining
|
||||
|
||||
```js
|
||||
const files = [ 'foo.txt ', '.bar', ' ', 'baz.foo' ];
|
||||
const filePaths = files
|
||||
.map(file => file.trim())
|
||||
.filter(Boolean)
|
||||
.map(fileName => `~/cool_app/${fileName}`);
|
||||
|
||||
// filePaths = [ '~/cool_app/foo.txt', '~/cool_app/.bar', '~/cool_app/baz.foo']
|
||||
```
|
||||
|
||||
- Uses `Array.prototype.map()` and `Array.prototype.filter()`.
|
||||
- More common nowadays, due to functional programming being more popular.
|
||||
- Less control over the iteration, cannot skip elements or `return` early.
|
||||
- Declarative, easier to read and refactor, chain can grow as necessary.
|
||||
- Does not use `Array.prototype.push()` or the spread (`...`) operator.
|
||||
- `O(cN)` complexity, `c` iterations per element, (`c`: length of the chain).
|
||||
122
snippets/articles/s/code-anatomy-optimizing-recursion.md
Normal file
122
snippets/articles/s/code-anatomy-optimizing-recursion.md
Normal file
@ -0,0 +1,122 @@
|
||||
---
|
||||
title: Code Anatomy - Optimizing recursive functions
|
||||
shortTitle: Optimizing recursive functions
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [recursion,performance]
|
||||
author: chalarangelo
|
||||
cover: case-study
|
||||
excerpt: Recursive code tends to be inefficient or in need of optimization. Learn a couple of tricks we use to speed up our recursive functions.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Recursive functions
|
||||
|
||||
Recursion is a programming technique where the final solution is computed by breaking down the problem into smaller instances of the same problem and computing the solution for each one. The most common implementation is a function that calls itself, reducing the problem every time until it reaches an instance of the problem whose solution is either trivial to compute or already known. Let's look at a very well-known example, calculating the `n`th term of the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number), implemented using recursion in JavaScript:
|
||||
|
||||
```js
|
||||
const fibonacciNumber = n =>
|
||||
n < 2 ? fibonacciNumber(n - 1) + fibonacciNumber(n - 2) : n;
|
||||
```
|
||||
|
||||
To understand recursion better, let's add a `console.log()` call before each `return` and figure out what exactly is happening:
|
||||
|
||||
```js
|
||||
const fibonacciNumber = n => {
|
||||
console.log(`[CALLED] fibonacciNumber(${n})`);
|
||||
const r = n >= 2 ? fibonacciNumber(n - 1) + fibonacciNumber(n - 2) : n;
|
||||
console.log(`[RETURN] ${r} for n=${n}`);
|
||||
return r;
|
||||
}
|
||||
|
||||
fibonacciNumber(4);
|
||||
// [CALLED] fibonacciNumber(4)
|
||||
// [CALLED] fibonacciNumber(3)
|
||||
// [CALLED] fibonacciNumber(2)
|
||||
// [CALLED] fibonacciNumber(1)
|
||||
// [RETURN] 1 for n=1
|
||||
// [CALLED] fibonacciNumber(0)
|
||||
// [RETURN] 0 for n=0
|
||||
// [RETURN] 1 for n=2
|
||||
// [CALLED] fibonacciNumber(1)
|
||||
// [RETURN] 1 for n=1
|
||||
// [RETURN] 2 for n=3
|
||||
// [CALLED] fibonacciNumber(2)
|
||||
// [CALLED] fibonacciNumber(1)
|
||||
// [RETURN] 1 for n=1
|
||||
// [CALLED] fibonacciNumber(0)
|
||||
// [RETURN] 0 for n=0
|
||||
// [RETURN] 1 for n=2
|
||||
// [RETURN] 3 for n=4
|
||||
```
|
||||
|
||||
As you can see, for each value of `n`, `fibonacciNumber` will be called twice, once with `n - 1` and once with `n - 2` and this will continue until it's called with either `1` or `0`. While this is straightforward to write and understand, it is inefficient as it will have to calculate the same value more than once.
|
||||
|
||||
### Calculation memoization
|
||||
|
||||
The solution to this problem, and the first trick that you can use to speed up recursive functions, is to use memoization. We already published [a great blog post on memoization](/articles/s/javascript-memoization/) a little while back, so be sure to check it out to learn more about the subject. Here's our `fibonacciNumber` function, using memoization:
|
||||
|
||||
```js
|
||||
const fibonacciCache = new Map();
|
||||
|
||||
const fibonacciNumber = n => {
|
||||
console.log(`[CALL] fibonacciNumber(${n})`);
|
||||
const cacheKey = `${n}`;
|
||||
let r;
|
||||
if(fibonacciCache.has(cacheKey)) {
|
||||
r = fibonacciCache.get(cacheKey);
|
||||
console.log(`[MEMO] Cache hit for ${n}: ${r}`);
|
||||
}
|
||||
else {
|
||||
r = n >= 2 ? fibonacciNumber(n - 1) + fibonacciNumber(n - 2) : n;
|
||||
fibonacciCache.set(cacheKey, r);
|
||||
console.log(`[CALC] Computed and stored value for ${n}: ${r}`);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
fibonacciNumber(4);
|
||||
// [CALL] fibonacciNumber(4)
|
||||
// [CALL] fibonacciNumber(3)
|
||||
// [CALL] fibonacciNumber(2)
|
||||
// [CALL] fibonacciNumber(1)
|
||||
// [CALC] Computed and stored value for 1: 1
|
||||
// [CALL] fibonacciNumber(0)
|
||||
// [CALC] Computed and stored value for 0: 0
|
||||
// [CALC] Computed and stored value for 2: 1
|
||||
// [CALL] fibonacciNumber(1)
|
||||
// [MEMO] Cache hit for 1: 1
|
||||
// [CALC] Computed and stored value for 3: 2
|
||||
// [CALL] fibonacciNumber(2)
|
||||
// [MEMO] Cache hit for 2: 1
|
||||
// [CALC] Computed and stored value for 4: 3
|
||||
```
|
||||
|
||||
As you can see in the example above, the value for each `n` is only computed once. While the Fibonacci sequence doesn't require any costly calculations, this could make a huge difference for a more computationally expensive problem. It will also be a lot more noticeable for higher values of `n` where the number of calculations will increase significantly.
|
||||
|
||||
### Using iteration
|
||||
|
||||
The second and final trick stems from the very definition of recursive programming turned on its head. If we can solve a smaller instance of the problem and use it for the solution of a larger instance of the problem, it should be possible to work iteratively from the smaller problem to the larger one, instead of recursively. Here's this idea in practice for our `fibonacciNumber` function:
|
||||
|
||||
```js
|
||||
const fibonacciNumber = n => {
|
||||
let r = 0, l = 1, s = 0;
|
||||
for(let i = 0; i < n; i++) {
|
||||
r = l;
|
||||
l = s;
|
||||
s = r + l;
|
||||
console.log(`[CALC] i = ${i}: r = ${r}, l = ${l}, s = ${s}`);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
fibonacciNumber(4);
|
||||
// [CALC] i = 0: r = 1, l = 0, s = 1
|
||||
// [CALC] i = 1: r = 0, l = 1, s = 1
|
||||
// [CALC] i = 2: r = 1, l = 1, s = 2
|
||||
// [CALC] i = 3: r = 1, l = 2, s = 3
|
||||
```
|
||||
|
||||
The iterative solution above makes the same calculations as the memoized one, however it performs better due to two key reasons. First of all, there is no cache, which would take up space in memory, making the latter implementation require fewer resources. Similarly, as there are no recursive calls or checks for cache hits, the code performs better and requires fewer resources to execute.
|
||||
|
||||
However, you have to bear in mind what the actual use cases of your recursive code are and be very careful how you optimize them. Memoization can be a more powerful tool if a recursive function is called multiple times with different arguments, as its cache persists between calls, while iteration can be faster for recursive computations that are used less frequently. Always pay attention to your code and optimize for the cases you know or anticipate to be more common.
|
||||
63
snippets/articles/s/code-anatomy-performant-python.md
Normal file
63
snippets/articles/s/code-anatomy-performant-python.md
Normal file
@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Code Anatomy - Writing high performance Python code
|
||||
shortTitle: Performant Python code
|
||||
type: story
|
||||
language: python
|
||||
tags: [list,performance]
|
||||
cover: walking-on-top
|
||||
excerpt: Writing efficient Python code can be tricky. Read how we optimize our list snippets to increase performance using a couple of simple tricks.
|
||||
dateModified: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
Writing short and efficient Python code is not always easy or straightforward. However, it's often that we see a piece of code and we don't realize the thought process behind the way it was written. We will be taking a look at the [difference](/python/s/difference) snippet, which returns the difference between two iterables, in order to understand its structure.
|
||||
|
||||
Based on the description of the snippet's functionality, we can naively write it like this:
|
||||
|
||||
```py
|
||||
def difference(a, b):
|
||||
return [item for item in a if item not in b]
|
||||
```
|
||||
|
||||
This implementation may work well enough, but doesn't account for duplicates in `b`. This makes the code take more time than necessary in cases with many duplicates in the second list. To solve this issue, we can make use of the `set()` method, which will only keep the unique values in the list:
|
||||
|
||||
```py
|
||||
def difference(a, b):
|
||||
return [item for item in a if item not in set(b)]
|
||||
```
|
||||
|
||||
This version, while it seems like an improvement, may actually be slower than the previous one. If you look closely, you will see that `set()` is called for every `item` in `a` causing the result of `set(b)` to be evaluated every time. Here's an example where we wrap `set()` with another method to better showcase the problem:
|
||||
|
||||
```py
|
||||
def difference(a, b):
|
||||
return [item for item in a if item not in make_set(b)]
|
||||
|
||||
def make_set(itr):
|
||||
print('Making set...')
|
||||
return set(itr)
|
||||
|
||||
print(difference([1, 2, 3], [1, 2, 4]))
|
||||
# Making set...
|
||||
# Making set...
|
||||
# Making set...
|
||||
# [3]
|
||||
```
|
||||
|
||||
The solution to this issue is to call `set()` once before the list comprehension and store the result to speed up the process:
|
||||
|
||||
```py
|
||||
def difference(a, b):
|
||||
_b = set(b)
|
||||
return [item for item in a if item not in _b]
|
||||
```
|
||||
|
||||
Another option worth mentioning in terms of performance is the use of a list comprehension versus `filter()` and `list()`. Implementing the same code using the latter option would result in something like this:
|
||||
|
||||
```py
|
||||
def difference(a, b):
|
||||
_b = set(b)
|
||||
return list(filter(lambda item: item not in _b, a))
|
||||
```
|
||||
|
||||
Using `timeit` to analyze the performance of the last two code examples, it's pretty clear that using list comprehension can be up to ten times faster than the alternative. This is due to it being a native language feature that works very similar to a simple `for` loop without the overhead of the extra function calls. This explains why we prefer it, apart from readability.
|
||||
|
||||
This pretty much applies to most mathematical list operation snippets, such as [difference](/python/s/difference), [symmetric_difference](/python/s/symmetric-difference) and [intersection](/python/s/intersection).
|
||||
133
snippets/articles/s/common-regexp-cheatsheet.md
Normal file
133
snippets/articles/s/common-regexp-cheatsheet.md
Normal file
@ -0,0 +1,133 @@
|
||||
---
|
||||
title: Common regular expressions
|
||||
type: cheatsheet
|
||||
language: javascript
|
||||
tags: [string,regexp]
|
||||
author: chalarangelo
|
||||
cover: rocky-beach
|
||||
excerpt: A collection of regular expressions that can be used to solve common problems.
|
||||
dateModified: 2022-11-09T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Exact string match
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Add the string you want to match in-between the two anchors.
|
||||
|
||||
```js
|
||||
const regexp = /^abc$/;
|
||||
// Where 'abc' is the exact string you want to match
|
||||
```
|
||||
|
||||
### Match empty string
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Do not add any characters in-between to match an empty string.
|
||||
|
||||
```js
|
||||
const regexp = /^$/;
|
||||
```
|
||||
|
||||
### Match whitespace sequences
|
||||
|
||||
- Use the `\s` meta-sequence to match any whitespace character, including spaces, tabs, newlines, etc.
|
||||
- Use the `+` quantifier to match one or more occurrences of the previous character.
|
||||
- Add the global flag (`g`) to match all occurrences of the pattern in the string.
|
||||
|
||||
```js
|
||||
const regexp = /\s+/g;
|
||||
```
|
||||
|
||||
### Match line breaks
|
||||
|
||||
- Depending on the environment, line breaks can be represented in different ways.
|
||||
- Use the `\r` character to match carriage returns, the `\n` character to match newlines, and the `\r\n` sequence to match carriage returns followed by newlines.
|
||||
- Add the global (`g`) and multiline (`m`) flags to match all occurrences of the pattern in the string.
|
||||
|
||||
```js
|
||||
const regexp = /\r|\n|\r\n/gm;
|
||||
```
|
||||
|
||||
### Match non-word characters
|
||||
|
||||
- Use negation (`^`) to match any character that is not a word character (`\w`) or a whitespace character (`\s`).
|
||||
- Add the global flag (`g`) to match all occurrences of the pattern in the string.
|
||||
- Add the ignore case flag (`i`) to match both uppercase and lowercase characters.
|
||||
|
||||
```js
|
||||
const regexp = /[^\w\s]/gi;
|
||||
```
|
||||
|
||||
### Match alphanumeric, dashes and hyphens
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Use the `a-zA-Z0-9-` pattern to match any alphanumeric character, dashes and hyphens.
|
||||
- Use the `+` quantifier to match one or more occurrences of the previous character.
|
||||
- Particularly useful when matching URL slugs.
|
||||
|
||||
```js
|
||||
const regexp = /^[a-zA-Z0-9-_]+$/;
|
||||
```
|
||||
|
||||
### Match letters and whitespaces
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Use the `a-zA-Z\s` pattern to match any letter and whitespace character.
|
||||
- Use the `+` quantifier to match one or more occurrences of the previous pattern.
|
||||
|
||||
```js
|
||||
const regexp = /^[A-Za-z\s]+$/;
|
||||
```
|
||||
|
||||
### Pattern not included
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Use a negative lookahead (`?!`) to match any character that is not followed by the pattern you want to exclude.
|
||||
- Add the global flag (`g`) to match all occurrences of the pattern in the string.
|
||||
- To ensure more than one pattern is not included, use the `|` character to separate them.
|
||||
|
||||
```js
|
||||
const regexp = /^((?!(abc|bcd)).)*$/;
|
||||
// Where 'abc' and 'bcd' are pattern you want to exclude
|
||||
```
|
||||
|
||||
### Text inside brackets
|
||||
|
||||
- Use the `\(` and `\)` characters to match the opening and closing brackets, respectively.
|
||||
- Use a capturing group between the two and exclude the closing parenthesis character.
|
||||
- Use the `+` quantifier to match one or more characters, as needed.
|
||||
- Add the global flag (`g`) to match all occurrences of the pattern in the string.
|
||||
- Replace `\(` and `\)` with `\[` and `\]` to match square brackets and with `\{` and `\}` to match curly brackets.
|
||||
|
||||
```js
|
||||
const regexp = /\(([^)]+)\)/g;
|
||||
```
|
||||
|
||||
### Validate GUID/UUID
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Validate each segment of the GUID/UUID separately using numeric character ranges and quantifiers.
|
||||
|
||||
```js
|
||||
const regexp = /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$/;
|
||||
```
|
||||
|
||||
### Validate date format (DD/MM/YYYY)
|
||||
|
||||
- Use the `^` and `$` anchors to match the start and end of the string, respectively.
|
||||
- Validate each segment of the date separately using numeric character ranges and quantifiers.
|
||||
- Alter the order of the segments and separators to match different formats.
|
||||
|
||||
```js
|
||||
const regexp = /^(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}$/;
|
||||
```
|
||||
|
||||
### Chunk string into n-size chunks
|
||||
|
||||
- Use the `.{1,n}` quantifier to match any character between `1` and `n` times.
|
||||
- Add the global flag (`g`) to match all occurrences of the pattern in the string.
|
||||
|
||||
```js
|
||||
const regexp = /.{1,2}/g;
|
||||
// Where '2' is the number of characters per chunk
|
||||
```
|
||||
119
snippets/articles/s/console-log-cheatsheet.md
Normal file
119
snippets/articles/s/console-log-cheatsheet.md
Normal file
@ -0,0 +1,119 @@
|
||||
---
|
||||
title: JavaScript console.log() tips & tricks
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [browser,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: terminal
|
||||
excerpt: Level up your JavaScript logging with these `console.log()` tips and tricks.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Everyone uses the JavaScript console for logging or debugging every once in a while. But there is a lot more to the [console](https://developer.mozilla.org/en-US/docs/Web/API/Console) object than `console.log()`.
|
||||
|
||||
### Computed property names
|
||||
|
||||
ES6 computed property names are particularly useful, as they can help you identify logged variables by adding a pair of curly braces around them.
|
||||
|
||||
```js
|
||||
const x = 1, y = 2, z = 3;
|
||||
|
||||
console.log({x, y, z}); // {x: 1, y: 2, z: 3}
|
||||
```
|
||||
|
||||
### console.trace()
|
||||
|
||||
[`console.trace()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/trace) works the exact same as `console.log()`, but it also outputs the entire stack trace so you know exactly what's going on.
|
||||
|
||||
```js
|
||||
const outer = () => {
|
||||
const inner = () => console.trace('Hello');
|
||||
inner();
|
||||
};
|
||||
|
||||
outer();
|
||||
/*
|
||||
Hello
|
||||
inner @ VM207:3
|
||||
outer @ VM207:5
|
||||
(anonymous) @ VM228:1
|
||||
*/
|
||||
```
|
||||
|
||||
### console.group()
|
||||
|
||||
[`console.group()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/group) allows you to group logs into collapsable structures and is particularly useful when you have multiple logs.
|
||||
|
||||
```js
|
||||
console.group('Outer'); // Create a group labelled 'Outer'
|
||||
console.log('Hello'); // Log inside 'Outer'
|
||||
console.groupCollapsed('Inner'); // Create a group labelled 'Inner', collapsed
|
||||
console.log('Hellooooo'); // Log inside 'Inner'
|
||||
console.groupEnd(); // End of current group, 'Inner'
|
||||
console.groupEnd(); // End of current group, 'Outer'
|
||||
console.log('Hi'); // Log outside of any groups
|
||||
```
|
||||
|
||||
### Logging levels
|
||||
|
||||
There are a few more logging levels apart from `console.log()`, such as [`console.debug()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/debug), [`console.info()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/info), [`console.warn()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/warn) and [`console.error()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/error).
|
||||
|
||||
```js
|
||||
console.debug('Debug message');
|
||||
console.info('Useful information');
|
||||
console.warn('This is a warning');
|
||||
console.error('Something went wrong!');
|
||||
```
|
||||
|
||||
### console.assert()
|
||||
|
||||
[`console.assert()`](https://developer.mozilla.org/en-US/docs/Web/API/console/assert) provides a handy way to only log something as an error when an assertion fails (i.e. when the first argument is `false`), otherwise skip the log entirely.
|
||||
|
||||
```js
|
||||
const value = 10;
|
||||
|
||||
console.assert(value === 10, 'Value is not 10!'); // Nothing is logged
|
||||
console.assert(value === 20, 'Value is not 20!'); // Logs "Value is not 20!"
|
||||
```
|
||||
|
||||
### console.count()
|
||||
|
||||
You can use [`console.count()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/count) to count how many times a piece of code has executed.
|
||||
|
||||
```js
|
||||
Array.from({ length: 4 }).forEach(
|
||||
() => console.count('items') // Call the counter labelled 'items'
|
||||
);
|
||||
/*
|
||||
items: 1
|
||||
items: 2
|
||||
items: 3
|
||||
items: 4
|
||||
*/
|
||||
console.countReset('items'); // Reset the counter labelled 'items'
|
||||
```
|
||||
|
||||
### console.time()
|
||||
|
||||
[`console.time()`](https://developer.mozilla.org/en-US/docs/Web/API/Console/time) gives you a quick way to check the performance of your code, but should not be used for real benchmarking due to its low accuracy.
|
||||
|
||||
```js
|
||||
console.time('slow comp'); // Start the 'slow comp' timer
|
||||
console.timeLog('slow comp'); // Log the value of the 'slow comp' timer
|
||||
console.timeEnd('slow comp'); // Stop and log the 'slow comp' timer
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
Last but not least, you can use the `%c` string substitution expression in `console.log()` to apply CSS to parts of a log.
|
||||
|
||||
```js
|
||||
console.log(
|
||||
'CSS can make %cyour console logs%c %cawesome%c!', // String to format
|
||||
// Each string is the CSS to apply for each consecutive %c
|
||||
'color: #fff; background: #1e90ff; padding: 4px', // Apply styles
|
||||
'', // Clear any styles
|
||||
'color: #f00; font-weight: bold', // Apply styles
|
||||
'' // Clear any styles
|
||||
);
|
||||
```
|
||||
57
snippets/articles/s/cookies-local-storage-session.md
Normal file
57
snippets/articles/s/cookies-local-storage-session.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: What is the difference between cookies, local storage, and session storage?
|
||||
shortTitle: Cookies, local storage, and session storage
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,webdev]
|
||||
author: chalarangelo
|
||||
cover: three-vases
|
||||
excerpt: Learn the difference between cookies, local storage and session storage and start using the correct option for your needs.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Cookies
|
||||
|
||||
Cookies store small amounts of data that has to be sent back to the server with subsequent requests and their expiration can be set from either server or client. They are primarily used for server-side reading.
|
||||
|
||||
- Capacity: 4KB
|
||||
- Accessible from: Any window
|
||||
- Expiration: Manually set
|
||||
- Storage location: Browser and server
|
||||
- Sent with requests: Yes
|
||||
- Blockable by users: Yes
|
||||
- Editable by users: Yes
|
||||
|
||||
### Local storage
|
||||
|
||||
Local storage stores a larger amount of data on the client's computer in a key-value pair format and has no expiration date. Data is never transferred to the server and is accessible via JavaScript and HTML5.
|
||||
|
||||
- Capacity: 10MB
|
||||
- Accessible from: Any window
|
||||
- Expiration: Never
|
||||
- Storage location: Browser only
|
||||
- Sent with requests: No
|
||||
- Blockable by users: Yes
|
||||
- Editable by users: Yes
|
||||
|
||||
### Session storage
|
||||
|
||||
Session storage stores a larger amount of data on the client's computer only for the current session, expiring the data on tab close. Data is never transferred to the server and is accessible client-side from the same tab.
|
||||
|
||||
- Capacity: 5MB
|
||||
- Accessible from: Same tab
|
||||
- Expiration: On tab close
|
||||
- Storage location: Browser only
|
||||
- Sent with requests: No
|
||||
- Blockable by users: Yes
|
||||
- Editable by users: Yes
|
||||
|
||||
| | Cookies | Local storage | Session storage |
|
||||
| -- | -- | -- | -- |
|
||||
| Capacity | 4KB | 10MB | 5MB |
|
||||
| Accessible from | Any window | Any window | Same tab |
|
||||
| Expiration | Manually set | Never | On tab close |
|
||||
| Storage location | Browser and server | Browser only | Browser only |
|
||||
| Sent with requests | Yes | No | No |
|
||||
| Blockable by users | Yes | Yes | Yes |
|
||||
| Editable by users | Yes | Yes | Yes |
|
||||
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: How can I copy text to clipboard with JavaScript?
|
||||
shortTitle: Copy text to clipboard
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser]
|
||||
author: chalarangelo
|
||||
cover: typing
|
||||
excerpt: Learn how to programmatically copy text to clipboard with a few lines of JavaScript and level up your web development skills.
|
||||
dateModified: 2022-01-11T09:47:54+03:00
|
||||
---
|
||||
|
||||
### Asynchronous Clipboard API
|
||||
|
||||
A very common need when building websites is the ability to copy text to clipboard with a single button click. If you only need to support modern browsers, it's highly recommended to use the asynchronous [Clipboard API](https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API). It's supported in all modern browsers and provides an easy and secure way to update the clipboard's contents.
|
||||
|
||||
All you have to do is ensure `Navigator`, `Navigator.clipboard` and `Navigator.clipboard.writeText` are truthy and then call `Clipboard.writeText()` to copy the value to clipboard. In case anything goes wrong, you can use `Promise.reject()` to return a promise that rejects immediately and keep the return type consistent.
|
||||
|
||||
```js
|
||||
const copyToClipboard = str => {
|
||||
if (navigator && navigator.clipboard && navigator.clipboard.writeText)
|
||||
return navigator.clipboard.writeText(str);
|
||||
return Promise.reject('The Clipboard API is not available.');
|
||||
};
|
||||
```
|
||||
|
||||
This is pretty much how the [copyToClipboardAsync snippet](/js/s/copy-to-clipboard-async) is implemented and should work across all modern browsers.
|
||||
|
||||
### Document.execCommand('copy')
|
||||
|
||||
While support for the Clipboard API is pretty high across the board, you might need a fallback if you have to support older browsers. If that's the case, you can use `Document.execCommand('copy')` to do so. Here's a quick step-by-step guide:
|
||||
|
||||
1. Create a` <textarea>` element to be appended to the document. Set its value to the string you want to copy to the clipboard.
|
||||
2. Append the `<textarea>` element to the current HTML document and use CSS to hide it to prevent flashing.
|
||||
3. Use `HTMLInputElement.select()` to select the contents of the `<textarea>` element.
|
||||
4. Use `Document.execCommand('copy')` to copy the contents of the `<textarea>` to the clipboard.
|
||||
5. Remove the `<textarea>` element from the document.
|
||||
|
||||
```js
|
||||
const copyToClipboard = str => {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = str;
|
||||
el.setAttribute('readonly', '');
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
};
|
||||
```
|
||||
|
||||
Bear in mind that this method will not work everywhere, but only as a result of a user action (e.g. inside a `click` event listener), due to the way `Document.execCommand()` works.
|
||||
|
||||
There are a couple of other considerations, such as restoring the user's previous selection on the document, which can be easily handled with modern JavaScript. You can find the final code with these improvements implemented in the [copyToClipboard snippet](/js/s/copy-to-clipboard/).
|
||||
29
snippets/articles/s/cors-explained.md
Normal file
29
snippets/articles/s/cors-explained.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: What is CORS?
|
||||
shortTitle: CORS explained
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,webdev]
|
||||
author: chalarangelo
|
||||
cover: chill-surfing
|
||||
excerpt: CORS (Cross-Origin Resource Sharing) trips up many developers, but it's pretty easy to wrap your head around.
|
||||
dateModified: 2023-05-07T05:00:00-04:00
|
||||
---
|
||||
|
||||
When it comes to HTTP, an **origin** is defined by several different aspects of a URL. As mentioned in a [previous article](/articles/s/js-window-location-cheatsheet/), the origin is composed of the following:
|
||||
|
||||
- The **protocol** (e.g. `http` or `https`)
|
||||
- The **hostname** (e.g. `30secondsofcode.org`)
|
||||
- The **port** (e.g. `80` or `3000`)
|
||||
|
||||
As long as **all three** of these match, the browser considers the two URLs to be **same-origin**. If any of these aspects differ, the browser considers the two URLs to be **cross-origin**. It might be helpful to look at some examples of different origins to clarify:
|
||||
|
||||
- `http://30secondsofcode.org` and `https://www.30secondsofcode.org` (different protocols)
|
||||
- `http://www.30secondsofcode.org` and `http://dev.30secondsofcode.org` (different hostnames)
|
||||
- `https://30secondsofcode.org` and `https://30secondsofcode.org:3000` (different ports)
|
||||
|
||||
It's also important to note that the **path** (everything that comes after the hostname) is **not part of the origin**. This means that `https://30secondsofcode.org` and `https://30secondsofcode.org/articles` are considered to be the same origin.
|
||||
|
||||
When **CORS** (Cross-Origin Resource Sharing) is mentioned, it's usually in the context of **Same-Origin Policy**, a security feature implemented by web browsers. It **blocks web pages from making cross-origin requests** with the purpose of preventing malicious websites from making unauthorized requests to sensitive resources on other domains.
|
||||
|
||||
As this can be quite restrictive, CORS allows the server to specify which other domains are allowed to make requests to its resources. This is done through the use of **CORS headers**, `Origin` in the request and `Access-Control-Allow-Origin` in the response. This way, for example, API servers can allow requests from specific web pages, while still blocking requests from other domains.
|
||||
27
snippets/articles/s/css-centering.md
Normal file
27
snippets/articles/s/css-centering.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: 4 ways to center content with CSS
|
||||
shortTitle: Centering content with CSS
|
||||
type: story
|
||||
language: css
|
||||
tags: [layout]
|
||||
author: chalarangelo
|
||||
cover: mountain-lake
|
||||
excerpt: Centering content with CSS might often feel tricky. Here are 4 easy tricks you can use in your code today.
|
||||
dateModified: 2021-09-28T19:35:49+03:00
|
||||
---
|
||||
|
||||
### Flexbox
|
||||
|
||||
Using flexbox to vertically and horizontally center content is usually the **preferred method**. All it takes is three lines of code in the container element to set `display: flex` and then center the child element vertically and horizontally using `align-items: center` and `justify-content: center` respectively. You can view the [Flexbox centering snippet](/css/s/flexbox-centering) for the code and examples.
|
||||
|
||||
### Grid
|
||||
|
||||
Using the grid module is very similar to flexbox and also a common technique, especially if you are **already using grid in your layout**. The only difference from the previous technique is the `display` which is set to `grid` instead. You can view the [Grid centering snippet](/css/s/grid-centering) for the code and examples.
|
||||
|
||||
### Transform
|
||||
|
||||
Transform centering uses, as the name implies, CSS transforms to center an element. It depends on the container element having a `position: relative`, allowing the child element to utilize `position: absolute` to position itself. Then `left: 50%` and `top: 50%` are used to offset the child element and `transform: translate(-50%, -50%)` to negate its position. You can view the [Transform centering snippet](/css/s/transform-centering) for the code and examples.
|
||||
|
||||
### Table
|
||||
|
||||
Last but not least, table centering is an older technique which you might favor when working with **older browsers**. It depends on the use of `display: table` in the container element. This allows the child element to use `display: table-cell` in combination with `text-align: center` and `vertical-align: middle` to center itself horizontally and vertically. You can view the [Display table centering snippet](/css/s/display-table-centering) for the code and examples.
|
||||
21
snippets/articles/s/css-clamp.md
Normal file
21
snippets/articles/s/css-clamp.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Tip: Use clamp() in CSS for responsive typography"
|
||||
shortTitle: CSS clamp()
|
||||
type: tip
|
||||
language: css
|
||||
tags: [visual]
|
||||
author: chalarangelo
|
||||
cover: strawberries
|
||||
excerpt: Implement responsive typography with the CSS clamp() function.
|
||||
dateModified: 2022-12-28T05:00:00-04:00
|
||||
---
|
||||
|
||||
Responsive typography has been in fashion for a while now, but some developers find it hard to implement. This is usually due to confusing algebraic formulas or complex hacks. Luckily, CSS has introduced the `clamp()` function, which makes it easy to create responsive typography with a single line of code. All you need to do is set the minimum, maximum, and preferred value and the browser will do the rest.
|
||||
|
||||
```css
|
||||
h2 {
|
||||
font-size: clamp(1.5rem, 5vw, 3rem);
|
||||
}
|
||||
```
|
||||
|
||||
For a more complex example, take a look at the [Fluid typography snippet](/css/s/fluid-typography).
|
||||
34
snippets/articles/s/css-code-reviews.md
Normal file
34
snippets/articles/s/css-code-reviews.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: How do you review CSS code in Pull Requests?
|
||||
shortTitle: CSS code reviews
|
||||
type: story
|
||||
language: css
|
||||
tags: [webdev]
|
||||
author: chalarangelo
|
||||
cover: green-css
|
||||
excerpt: Reviewing CSS code is a skill that takes time to master. Here are some tips from my personal experience to help you get started.
|
||||
dateModified: 2023-05-21T05:00:00-04:00
|
||||
---
|
||||
|
||||
Reviewing CSS code is a bit different than reviewing JavaScript code. In fact, many developers pay little attention to CSS, which can lead to problems down the line. Yet, many developers don't know how to review CSS code properly. In this post, we'll go over some of the things you should look for when reviewing CSS code in Pull Requests.
|
||||
|
||||
### Visual check
|
||||
|
||||
The very first step when reviewing CSS is to check if the **result looks right**. This might mean comparing the end result to a **design mockup** or checking if every interaction behaves according to the animation principles the team has agreed upon. **Responsiveness** is also a key factor to consider, so you should check if the page looks right on **different screen sizes**. The visual check is probably the most straightforward and important step, but unfortunately many developers stop there.
|
||||
|
||||
### Code style
|
||||
|
||||
Your team should have set up a **linter and formatter** for CSS and, if you haven't, you should do it as soon as possible. This will help you enforce a consistent code style and make the code easier to read and maintain. Provided that's the case, you should check for **conventions** that the linter can't automatically enforce. These often include naming conventions, proper documentation or the use of CSS custom properties in place of hard-coded values.
|
||||
|
||||
### Specificity
|
||||
|
||||
CSS selectors can be easily abused, causing headaches later down the line. Having clear conventions usually resolves a lot of issues, but things can slip through the cracks. Ensuring specificity is **as low as possible** and that selectors are **not too generic or overly complex** will help increase the code's maintainability.
|
||||
|
||||
### Leftovers
|
||||
|
||||
In an ideal scenario, the Pull Request author has a clear vision of the CSS they are writing and everything works out perfectly the first time. As you know, that's rarely the case, meaning experimentation and changes will happen during development. As the code changes, some **old code** might hang around without contributing anything to the page. One of the most common examples I've stumbled upon are flexbox-related properties for non-flexbox elements. These take a bit of time to spot, but they can be easily removed, saving you problems in the future.
|
||||
|
||||
### Performance
|
||||
|
||||
CSS performance is very often overlooked. Simple rules, deduplication and minimum overrides are some of the things that can be done to improve performance. Understandably, this sort of opportunity is **hard to spot**, but it's worth keeping an eye out for. That being said, don't go overboard with performance optimizations. If you're not sure if something is worth it, you should probably leave it as is.
|
||||
|
||||
36
snippets/articles/s/css-current-color.md
Normal file
36
snippets/articles/s/css-current-color.md
Normal file
@ -0,0 +1,36 @@
|
||||
---
|
||||
title: The currentColor CSS keyword
|
||||
type: tip
|
||||
language: css
|
||||
tags: [visual]
|
||||
author: chalarangelo
|
||||
cover: picking-berries
|
||||
excerpt: The `currentColor` CSS keyword is a nifty alternative to custom properties for simple use cases.
|
||||
dateModified: 2022-11-30T05:00:00-04:00
|
||||
---
|
||||
|
||||
Modern CSS supports custom properties, yet the `currentColor` keyword precedes them by a few years. Thus, you might still find it in the wild and it is worth knowing what it does and how it works.
|
||||
|
||||
```html
|
||||
<p>My <span>background</span> is the same color as my <a href="#">text</a>.</p>
|
||||
```
|
||||
|
||||
```css
|
||||
p {
|
||||
color: #101010;
|
||||
}
|
||||
|
||||
p, p > * {
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077ff;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #fd203a;
|
||||
}
|
||||
```
|
||||
|
||||
`currentColor` contains the current value of the `color` property of the element. It is useful when you want to use the same color for multiple properties, such as `border-color` or `background-color`. It also respects the cascade, so if no value is provided for `color`, it will use the value of the `color` property of the parent element.
|
||||
43
snippets/articles/s/css-easing-variables.md
Normal file
43
snippets/articles/s/css-easing-variables.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "Tip: CSS easing variables"
|
||||
shortTitle: CSS easing variables
|
||||
type: tip
|
||||
language: css
|
||||
tags: [animation]
|
||||
author: chalarangelo
|
||||
cover: curve
|
||||
excerpt: Learn how to use the `cubic-bezier()` class of easing functions and create beautiful animations that stand out.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Most web developers use the built-in `ease`, `ease-in`, `ease-out` or `ease-in-out` functions for most use-cases of `transition-timing-function` in their designs. While these are perfectly fine for everyday use, there's a far more powerful, yet intimidating option available, the [`bezier-curve()`](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) function.
|
||||
|
||||
Using the `bezier-curve()` we can easily define custom easing variables that can help our designs pop out. In fact the built-in functions mentioned above can also be written using the `bezier-curve()` function. Here's a handful of useful easing functions stored in CSS variables for ease of use:
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* ease-in corresponds to cubic-bezier(0.42, 0, 1.0, 1.0) */
|
||||
--ease-in-quad: cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
||||
--ease-in-cubic: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||
--ease-in-quart: cubic-bezier(0.895, 0.03, 0.685, 0.22);
|
||||
--ease-in-quint: cubic-bezier(0.755, 0.05, 0.855, 0.06);
|
||||
--ease-in-expo: cubic-bezier(0.95, 0.05, 0.795, 0.035);
|
||||
--ease-in-circ: cubic-bezier(0.6, 0.04, 0.98, 0.335);
|
||||
|
||||
/* ease-out corresponds to cubic-bezier(0, 0, 0.58, 1.0) */
|
||||
--ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||
--ease-out-quart: cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
--ease-out-quint: cubic-bezier(0.23, 1, 0.32, 1);
|
||||
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
|
||||
--ease-out-circ: cubic-bezier(0.075, 0.82, 0.165, 1);
|
||||
|
||||
/* ease-in-out corresponds to cubic-bezier(0.42, 0, 0.58, 1.0) */
|
||||
--ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
|
||||
--ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
|
||||
--ease-in-out-quart: cubic-bezier(0.77, 0, 0.175, 1);
|
||||
--ease-in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
|
||||
--ease-in-out-expo: cubic-bezier(1, 0, 0, 1);
|
||||
--ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.15, 0.86);
|
||||
}
|
||||
```
|
||||
53
snippets/articles/s/css-footer-at-the-bottom.md
Normal file
53
snippets/articles/s/css-footer-at-the-bottom.md
Normal file
@ -0,0 +1,53 @@
|
||||
---
|
||||
title: How can I ensure the footer is always at the bottom of the page?
|
||||
shortTitle: Footer at the bottom
|
||||
type: question
|
||||
language: css
|
||||
tags: [layout]
|
||||
author: chalarangelo
|
||||
cover: flower-shape-sunset
|
||||
excerpt: Make sure the footer stays at the bottom of the page, instead of floating up when the content is too short.
|
||||
dateModified: 2022-10-30T05:00:00-04:00
|
||||
---
|
||||
|
||||
Preventing the footer from floating up the page is important when trying to create a polished website. Pages with **short content** can run into this issue, but it's easy to fix with a few lines of CSS. Assuming your HTML looks something like the snippet below, here are two modern ways to ensure the footer is always at the bottom of the page:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<main><!-- Main content --></main>
|
||||
<footer><!-- Footer content --></footer>
|
||||
</body>
|
||||
```
|
||||
|
||||
### Using Flexbox
|
||||
|
||||
You can use flexbox to ensure that the footer is always at the bottom of the page. This is done by setting the giving the `body` element `min-height: 100vh`, `display: flex` and `flex-direction: column`. Then, give the `footer` element a `margin-top: auto` to make its margin fill the remaining space between it and its previous sibling. Note that this technique will not stretch the previous sibling, but rather **push the footer to the bottom of the page**.
|
||||
|
||||
|
||||
```css
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
```
|
||||
|
||||
### Using Grid
|
||||
|
||||
You can also use grid in a very similar fashion. Simply swap `display: flex` for `display: grid` and `flex-direction: column` for `grid-template-rows: 1fr auto` in the `body` element. No additional attributes are needed for the `footer` element. In this case, the `fr` unit is leveraged to stretch the `main` element to **fill the remaining space**.
|
||||
|
||||
```css
|
||||
body {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
}
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
As you can see, both techniques are straightforward to implement. Depending on your needs one might be more suitable than the other. Generally speaking, grid is more flexible in most cases and can help if you have more complex layouts, which can include a header or sidebar.
|
||||
51
snippets/articles/s/css-inherited-properties-cheatsheet.md
Normal file
51
snippets/articles/s/css-inherited-properties-cheatsheet.md
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
title: CSS inherited properties cheatsheet
|
||||
shortTitle: Inherited properties
|
||||
type: cheatsheet
|
||||
language: css
|
||||
tags: [layout]
|
||||
author: chalarangelo
|
||||
cover: half-trees
|
||||
excerpt: A quick reference for inherited CSS properties.
|
||||
dateModified: 2022-11-20T05:00:00-04:00
|
||||
---
|
||||
|
||||
The [CSS specification](https://www.w3.org/TR/CSS21/propidx.html) clearly states which CSS properties are inherited but the related appendix is not the most user-friendly resource. Here's a quick reference of the inherited properties that you are most likely to run into:
|
||||
|
||||
- `border-collapse`
|
||||
- `border-spacing`
|
||||
- `caption-side`
|
||||
- `color`
|
||||
- `cursor`
|
||||
- `direction`
|
||||
- `empty-cells`
|
||||
- `font-family`
|
||||
- `font-size`
|
||||
- `font-style`
|
||||
- `font-variant`
|
||||
- `font-weight`
|
||||
- `font-size-adjust`
|
||||
- `font-stretch`
|
||||
- `font`
|
||||
- `letter-spacing`
|
||||
- `line-height`
|
||||
- `list-style-image`
|
||||
- `list-style-position`
|
||||
- `list-style-type`
|
||||
- `list-style`
|
||||
- `orphans`
|
||||
- `quotes`
|
||||
- `tab-size`
|
||||
- `text-align`
|
||||
- `text-align-last`
|
||||
- `text-decoration-color`
|
||||
- `text-indent`
|
||||
- `text-justify`
|
||||
- `text-shadow`
|
||||
- `text-transform`
|
||||
- `visibility`
|
||||
- `white-space`
|
||||
- `widows`
|
||||
- `word-break`
|
||||
- `word-spacing`
|
||||
- `word-wrap`
|
||||
28
snippets/articles/s/css-nested-border-radius.md
Normal file
28
snippets/articles/s/css-nested-border-radius.md
Normal file
@ -0,0 +1,28 @@
|
||||
---
|
||||
title: "Tip: Perfect nested border radius in CSS"
|
||||
shortTitle: Perfect nested border radius in CSS
|
||||
type: tip
|
||||
language: css
|
||||
tags: [visual]
|
||||
author: chalarangelo
|
||||
cover: rocky-beach-waves
|
||||
excerpt: Nesting elements with rounded borders can look very wrong if not done correctly. Here's a quick tip on how to do it right.
|
||||
dateModified: 2022-04-03T05:00:00-04:00
|
||||
---
|
||||
|
||||
Nesting elements with rounded borders can look very wrong if not done correctly. Luckily, there's a simple math trick to make it look right. All you need to do is **calculate the border radius of one of the elements and the distance between them**. The border radius of the outer element should be equal to the sum of the border radius of the inner element and the distance between the two elements. This can be mathematically expressed as `innerRadius + distance = outerRadius` or more tersely `R1 + D = R2`.
|
||||
|
||||

|
||||
|
||||
Let's take a look at a simple CSS example. Say we want to style two nested boxes with rounded borders. The outer box has a `border-radius` of `24px` and a `padding` of `8px`. Using the previous formula, we can deduce that the inner box should have a `border-radius` of `16px`.
|
||||
|
||||
```css
|
||||
.outer {
|
||||
border-radius: 24px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.inner {
|
||||
border-radius: 16px;
|
||||
}
|
||||
```
|
||||
25
snippets/articles/s/css-pseudo-classes.md
Normal file
25
snippets/articles/s/css-pseudo-classes.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: What are CSS pseudo-classes?
|
||||
shortTitle: CSS pseudo-classes
|
||||
type: question
|
||||
language: css
|
||||
tags: [webdev]
|
||||
author: chalarangelo
|
||||
cover: orange-flower
|
||||
excerpt: Learn how to use CSS pseudo-classes to style an element based on changes to its state.
|
||||
dateModified: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
CSS pseudo-classes provide a way to style elements, based on changes to their state. For example, `:hover` can be used to apply additional styles to an element when the user's pointer hovers over it.
|
||||
|
||||
Pseudo-classes let you apply styles to elements in relation to the content of the document tree (e.g. `:first-child`), external factors such as the history of the navigator (e.g. `:visited`), the status of their content (e.g. `:checked`) or the position of the mouse (e.g. `:hover`).
|
||||
|
||||
### Commonly used pseudo-classes
|
||||
|
||||
Below is a list of the top 5 most commonly used pseudo-classes and their usage. This list is by no means complete. You should always refer to relevant documentation from authoritative sources, such as [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Pseudo-classes) for more information.
|
||||
|
||||
- `:hover`, `:focus` and `:active` are used to provide feedback for user interaction (e.g. changing a button's color on hover)
|
||||
- `:link` and `:visited` are useful for styling links based on navigation history (e.g. changing the color of visited links)
|
||||
- `:first-child`, `:last-child`, `:nth-child()` and `nth-last-child()` are useful when working with collections of elements
|
||||
- `:not()` is used to match everything except the given selector and can be useful in styling hard to select elements
|
||||
- `:lang()` allows you to apply special styles based on the language of the document and is useful for multilingual websites
|
||||
87
snippets/articles/s/css-reset.md
Normal file
87
snippets/articles/s/css-reset.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: CSS Reset
|
||||
type: story
|
||||
language: css
|
||||
tags: [visual]
|
||||
author: chalarangelo
|
||||
cover: pink-flower-tree
|
||||
excerpt: A short, opinionated CSS reset to make your websites look great everywhere.
|
||||
dateModified: 2022-10-16T05:00:00-04:00
|
||||
---
|
||||
|
||||
Browsers nowadays are much better at presenting HTML in a consistent manner, making CSS resets of the past largely unnecessary. However, **default browser styles are not particularly great** in most cases, which is why there are tons of CSS resets out there. Drawing inspiration from some of them, here's my opinionated CSS reset, along with an explanation of why I chose to include each rule.
|
||||
|
||||
```css
|
||||
html {
|
||||
max-width: 70ch;
|
||||
margin: auto;
|
||||
padding: 3em 1em;
|
||||
font-family: system-ui, 'Segoe UI', Roboto, Cantarell,
|
||||
'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 1.25em;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 3em 0 1em;
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
margin-bottom: 2em;
|
||||
color: #1d1d1d;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub, sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
pre, code, kbd {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
```
|
||||
|
||||
- `html`:
|
||||
- `max-width` - Use `ch` units to set the maximum width in the middle of the optimal readable range (60-80 characters).
|
||||
- `margin` - Center content on the page.
|
||||
- `padding` - Prevent edge-to-edge text on smaller viewports.
|
||||
- `font-family` - Use the [system font stack](/css/s/system-font-stack) to ensure the best possible font rendering. `system-ui` is a new generic font family that replaces `-apple-system` and `BlinkMacSystemFont`.
|
||||
- `font-size` - Use a larger font size for better readability and to keep up with recent design trends.
|
||||
- `line-height` - Use a larger line height to increase visual clarity.
|
||||
- `body`:
|
||||
`margin` - Remove the default margin in all browsers.
|
||||
- `h1`-`h6`:
|
||||
- `margin` - Use larger margins for headers to improve visual hierarchy.
|
||||
- `p`, `ul`, `ol`:
|
||||
- `margin-bottom` - Add spacing between textual elements.
|
||||
- `color` - Soften text color to improve readability.
|
||||
- `small`:
|
||||
- `font-size` - Correct font size discrepancies between browsers.
|
||||
- `sub`, `sup`:
|
||||
- `font-size` - Correct font size discrepancies between browsers.
|
||||
- `line-height` - Prevent elements from affecting line height.
|
||||
- `position` - Position elements relative to parent.
|
||||
- `vertical-align` - Align elements to the baseline.
|
||||
- `bottom`, `top` - Correctly position elements.
|
||||
- `pre`, `code`, `kbd`:
|
||||
- `font-family` - Use monospace fonts for code elements.
|
||||
- `font-size` - Correct font size discrepancies between browsers.
|
||||
33
snippets/articles/s/css-root-vs-html.md
Normal file
33
snippets/articles/s/css-root-vs-html.md
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
title: What's the difference between :root and html in CSS?
|
||||
shortTitle: :root vs html
|
||||
type: story
|
||||
language: css
|
||||
tags: [layout,selector]
|
||||
author: chalarangelo
|
||||
cover: tree-roots
|
||||
excerpt: The CSS selectors used to target the root element of an HTML share some similarities, but they also have some differences.
|
||||
dateModified: 2022-05-22T05:00:00-04:00
|
||||
---
|
||||
|
||||
CSS has two ways to target the root element of an HTML document - the `:root` pseudo-class and the `html` selector. While these are very similar to each other, they have a couple of differences you should know.
|
||||
|
||||
### Selector specificity
|
||||
|
||||
The `:root` selector has a higher specificity than the `html` selector. This is because `:root` is a pseudo-class selector, while `html` is a type selector.
|
||||
|
||||
```css
|
||||
:root {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: blue;
|
||||
}
|
||||
|
||||
/* The HTML document's root element will have a red background-color. */
|
||||
```
|
||||
|
||||
### Targeting the root element
|
||||
|
||||
CSS can be used to style other types of documents, apart from HTML. This is where the `:root` element comes in to play, allowing you to style the root element of a document. This can be especially important when styling SVG documents, where the `html` selector will not work.
|
||||
21
snippets/articles/s/css-select-any-link.md
Normal file
21
snippets/articles/s/css-select-any-link.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Tip: Select any link with CSS"
|
||||
shortTitle: "CSS :any-link pseudo-class"
|
||||
type: tip
|
||||
language: css
|
||||
tags: [visual,interactivity]
|
||||
author: chalarangelo
|
||||
cover: round-leaves
|
||||
excerpt: You can use a CSS pseudo-class selector to style all links in a page, without worrying if they have been visited or not.
|
||||
dateModified: 2022-03-06T05:00:00-04:00
|
||||
---
|
||||
|
||||
Styling links with CSS is considered straightforward, with most developers using the `:link` and `:visited` pseudo-classes. While this solution is very common, there's a less verbose alternative in the form of the [`:any-link`](https://developer.mozilla.org/en-US/docs/Web/CSS/:any-link) pseudo-class. This pseudo-class selects all links, regardless of whether they have been visited or not. Thus, it acts as a catch-all for all links on the page.
|
||||
|
||||
```css
|
||||
:any-link {
|
||||
color: #0444f6;
|
||||
}
|
||||
```
|
||||
|
||||
One important note is that using `:any-link` is different to using the `[href]` attribute selector. The `:any-link` pseudo-class does not select empty links, whereas the `[href]` attribute selector does.
|
||||
22
snippets/articles/s/css-style-default-links.md
Normal file
22
snippets/articles/s/css-style-default-links.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "Tip: Style links without a class"
|
||||
shortTitle: Style default links
|
||||
type: tip
|
||||
language: css
|
||||
tags: [visual,interactivity]
|
||||
author: chalarangelo
|
||||
cover: citrus-drink
|
||||
excerpt: A short summary of your story up to 180 characters long.
|
||||
dateModified: 2022-11-23T05:00:00-04:00
|
||||
---
|
||||
|
||||
When styling injected or generated HTML content, you might not have access to the classes or IDs of the elements you want to style. This can become especially annoying when dealing with link elements. Luckily, you can use the `:not()` selector with an appropriate attribute selector to check for the absence of a class and style links accordingly.
|
||||
|
||||
```css
|
||||
a[href]:not([class]) {
|
||||
color: #0077ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
```
|
||||
|
||||
As a bonus tip, you can use [the previous tip about selecting any link](/articles/s/css-select-any-link) to further enhance this solution.
|
||||
25
snippets/articles/s/css-unitless-line-height.md
Normal file
25
snippets/articles/s/css-unitless-line-height.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: Why should line-height be unitless in CSS?
|
||||
shortTitle: Unitless line height
|
||||
type: tip
|
||||
language: css
|
||||
tags: [layout,visual]
|
||||
author: chalarangelo
|
||||
cover: gold-typewriter
|
||||
excerpt: You might have heard that `line-height` should be unitless, but do you know why?
|
||||
dateModified: 2022-11-27T05:00:00-04:00
|
||||
---
|
||||
|
||||
I've often heard that `line-height` should always be **unitless**. In my earlier coding years, I didn't question it much, but lately I've come to wonder why that is. In my mind `1.5` and `1.5em` should produce the same result, right? Turns out, they don't.
|
||||
|
||||
There's a **subtle difference** between the two and it has to do with the fact that `line-height` is an inherited property. A unitless value will be inherited as-is, meaning the actual value will be recalculated for each element, accounting for the `font-size` of the element. However, a `line-height` with any unit will be calculated once and then inherited as a fixed value. This can cause vastly different results, especially if the declaration is in the `body` element or something similar.
|
||||
|
||||
Speaking of the `body` element, it could be a good idea to define your base `line-height` as a unitless value there to minimize repetition:
|
||||
|
||||
```css
|
||||
body {
|
||||
line-height: 1.5;
|
||||
}
|
||||
```
|
||||
|
||||
So, is `line-height` with units prohibited and should we always use unitless values? Not necessarily. Factors such as codebase conventions, design systems and personal preference play a role here. For example, maintaining an exact, perfect vertical rhythm with unitless `line-height` values can be a bit tricky. In such cases, using `line-height` with units can be a good idea, but remember to **be consistent** to avoid headaches.
|
||||
25
snippets/articles/s/css-units-cheatsheet.md
Normal file
25
snippets/articles/s/css-units-cheatsheet.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: CSS units Cheat Sheet
|
||||
type: cheatsheet
|
||||
language: css
|
||||
tags: [layout,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: measuring
|
||||
excerpt: Learn everything you need to know about CSS units with this handy cheatsheet.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
- `px`: Absolute pixel value
|
||||
- `rem`: Relative to the `font-size` of the root element
|
||||
- `em`: Relative to the `font-size` of the element
|
||||
- `%`: Relative to the parent element
|
||||
- `vw`: Relative to the viewport's width, `1vw` = `1%` * viewport width
|
||||
- `vh`: Relative to the viewport's height, `1vh` = `1%` * viewport height
|
||||
- `vmin`: Relative to the viewport's smaller dimension, `1vmin` = min(`1vh`, `1vw`)
|
||||
- `vmax`: Relative to the viewport's larger dimension, `vmax` = max(`1vh`, `1vw`)
|
||||
- `ch`: Relative to the width of the glyph "0" of the element's font
|
||||
- `in`: Inches `1in` = `2.54cm` = `96px`
|
||||
- `pc`: Picas `1pc` = `1in` / `6` = `16px`
|
||||
- `pt`: Points `1pt` = `1in` / `72` = `1.333px` (approximately)
|
||||
- `cm`: Centimeters `1cm` = `96px` / `2.54` = `37.8px` (approximately)
|
||||
- `mm`: Millimeters `1mm` = `1cm` / `10` = `3.78px` (approximately)
|
||||
67
snippets/articles/s/css-variables.md
Normal file
67
snippets/articles/s/css-variables.md
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: What are CSS variables and where can I use them?
|
||||
shortTitle: CSS variables
|
||||
type: question
|
||||
language: css
|
||||
tags: [visual,layout]
|
||||
author: chalarangelo
|
||||
cover: css-variables
|
||||
excerpt: Learn how CSS custom properties (CSS variables) work and what you can use them for in your code and designs.
|
||||
dateModified: 2021-09-28T19:52:58+03:00
|
||||
---
|
||||
|
||||
CSS variables (officially called CSS **custom properties**) behave much like variables in other programming languages. They allow you to define named variables that contain specific values that can be reused within the CSS document. As specified in the custom property notation, CSS variables are prefixed with two dashes (e.g. `--my-color: black`). To access them, you can use the `var()` function (e.g. `color: var(--my-color)`). CSS variables are exceptionally useful for **sharing styles** between different elements and components. Examples include but are not limited to vertical rhythm, typography variables and color palettes.
|
||||
|
||||
One of their most common use-cases is **theming and dark mode**. CSS variables can be used to create a shared palette across the whole website and easily swap it for a different one. This is often accomplished by applying a class to a common ancestor (e.g. the `<body>` element). This example demonstrates global variables defined in the `:root` element and cascading, as elements inherit values from their parents:
|
||||
|
||||
```css
|
||||
/* Global variables are defined in the :root element. */
|
||||
:root {
|
||||
--bg-color: #fff;
|
||||
--main-color: #000;
|
||||
--secondary-color: #222;
|
||||
}
|
||||
/* Elements inherit variables from their parents. */
|
||||
body {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--main-color);
|
||||
}
|
||||
small {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
/* Elements can define their own values and variables, overriding inherited ones.*/
|
||||
body.dark {
|
||||
--bg-color: #080808;
|
||||
--main-color: #fff;
|
||||
--secondary-color: #ccc;
|
||||
}
|
||||
```
|
||||
|
||||
Another useful example is **defining shared customized styles** for certain variants of an element. This allows the customization of whole trees of components without having to repeat any styles. The following example demonstrates cascading even better than the previous one. It also introduces the idea of sharing styles between different elements:
|
||||
|
||||
```css
|
||||
.btn {
|
||||
--bg-color: #002299;
|
||||
--text-color: #fff;
|
||||
--highlight-color: #669900;
|
||||
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
/* --highlight-color is also available to the children of .btn */
|
||||
.btn .highlight {
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
/* .btn.danger .highlight will use the --highlight-color defined in .btn-danger */
|
||||
.btn-danger {
|
||||
--bg-color: #dd4a68;
|
||||
--text-color: #000;
|
||||
--highlight-color: #990055;
|
||||
}
|
||||
```
|
||||
|
||||
Finally, keep in mind the following useful tips for working with CSS variables:
|
||||
|
||||
- You can define **fallback values**, by providing a second argument to the `var()` function (e.g. `var(--text-color, black)` will default to `black` if `--text-color` is not defined).
|
||||
- CSS variables are **case sensitive**, so mind your capitalization. They can also be inlined in HTML like any other style (e.g. `<div style="--text-color: red">`).
|
||||
- You can nest `var()` calls, using another variable as fallback (e.g. `var(--main-color, var(--other-color))`), pass them to other functions such as `calc()` or even assign one variable to another (e.g. `--text-color: var(--main-color)`).
|
||||
18
snippets/articles/s/custom-file-download-names.md
Normal file
18
snippets/articles/s/custom-file-download-names.md
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
title: "Tip: Customize the names of downloadable files"
|
||||
shortTitle: Customize the names of downloadable files
|
||||
type: tip
|
||||
language: html
|
||||
tags: [webdev,browser]
|
||||
author: chalarangelo
|
||||
cover: hard-disk
|
||||
excerpt: Learn what HTML5 attribute you can use to customize the names of your downloadable files with this quick tip.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
HTML5 introduced a variety of convenient features that many of us use every day. As downloadable links aren't something I work with very often, I recently found out that you can use the `download` attribute on an `<a>` element for much more than just making it trigger a download. In fact, you can pass it a string value that will act as the name of the downloadable file, effectively allowing you to customize its name:
|
||||
|
||||
```html
|
||||
<!-- The downloaded file will be named 'June-2020.csv' -->
|
||||
<a href="/data/2020/06/report.csv" download="June-2020.csv">June 2020</a>
|
||||
```
|
||||
37
snippets/articles/s/detect-caps-lock-is-on.md
Normal file
37
snippets/articles/s/detect-caps-lock-is-on.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
title: How can I detect if Caps Lock is on with JavaScript?
|
||||
shortTitle: Detect Caps Lock
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,event]
|
||||
author: chalarangelo
|
||||
cover: keyboard
|
||||
excerpt: If you need to check if Caps Lock is on when the user is typing in the browser, JavaScript's got you covered.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Oftentimes, especially when creating password inputs, you need to check if the Caps Lock key is on and inform the user. You can do that using the [`KeyboardEvent.getModifierState()`](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/getModifierState) method with a value of `'CapsLock'`. This means that you have to listen for a keyboard event on an element in order to check the state of the Caps Lock key:
|
||||
|
||||
```html
|
||||
<form>
|
||||
<label for="username">Username:</label>
|
||||
<input id="username" name="username">
|
||||
|
||||
<label for="password">Password:</label>
|
||||
<input id="password" name="password" type="password">
|
||||
<span id="password-message" style="display: none">Caps Lock is on</span>
|
||||
</form>
|
||||
```
|
||||
|
||||
```js
|
||||
const el = document.getElementById('password');
|
||||
const msg = document.getElementById('password-message');
|
||||
|
||||
el.addEventListener('keyup', e => {
|
||||
msg.style = e.getModifierState('CapsLock')
|
||||
? 'display: block'
|
||||
: 'display: none';
|
||||
});
|
||||
```
|
||||
|
||||
As you can see from the above example, the `'keyup'` event is used on our element of choice to then call `KeyboardEvent.getModifierState()` and determine the state of the `'CapsLock'` key. `'keydown'` and `'keypress'` might also work. However, after testing on multiple devices, it seems that using `'keyup'` is the preferred method as it works better across different OSes and browsers.
|
||||
25
snippets/articles/s/dns-record-basics.md
Normal file
25
snippets/articles/s/dns-record-basics.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: DNS Record Basics
|
||||
type: cheatsheet
|
||||
tags: [webdev,dns,server,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: sparkles
|
||||
excerpt: DNS records may not be a thing you work too often with. Regardless, some basic knowledge can go a long way.
|
||||
dateModified: 2022-01-09T05:00:00-04:00
|
||||
---
|
||||
|
||||
Most web developers go about their day-to-day without having to deal with DNS records most of the time. Regardless, knowing what DNS stands for and the types of DNS records are pretty useful.
|
||||
|
||||
### DNS Definition
|
||||
|
||||
The Domain Name System (abbreviated to DNS), translates human-readable domain names (e.g www.google.com to machine-readable IP addresses (e.g. 142.250.186.46).
|
||||
|
||||
### DNS Records
|
||||
|
||||
A DNS is made up of multiple records of different types, each one with its own purpose. Here's a breakdown of the most commonly-used ones:
|
||||
|
||||
- **A record**: The address record. Used to map a domain name to an IPv4 address. Similarly, the **AAAA record** is used to map a domain name to an IPv6 address.
|
||||
- **CNAME records**: A canonical name record. Creates an alias that points to another domain or subdomain, but never an IP address.
|
||||
- **ANAME record**: Allows you to point the root of your domain to a hostname or a domain name.
|
||||
- **TXT records**: Allow the addition of limited text notes and is often used for ownership verification purposes, validation or security.
|
||||
- **MX record**: Specifies the mail server responsible for accepting the incoming and outgoing emails for a domain. Should point to a mail server name, not an IP address.
|
||||
39
snippets/articles/s/escaping-tutorial-hell.md
Normal file
39
snippets/articles/s/escaping-tutorial-hell.md
Normal file
@ -0,0 +1,39 @@
|
||||
---
|
||||
title: Escaping tutorial hell
|
||||
type: story
|
||||
tags: [webdev,career,programming,jobs]
|
||||
author: chalarangelo
|
||||
cover: campfire
|
||||
excerpt: Many beginners get stuck in tutorial hell without even realizing. Here are 4 simple ways to escape tutorial hell and get back to learning.
|
||||
dateModified: 2022-05-26T05:00:00-04:00
|
||||
---
|
||||
|
||||
Tutorial hell refers to the stage in your learning journey, where you are following along with tutorials, not truly learning that much. Tutorials are by nature a curated experience. While that's not definitively bad, it's **not always the best way** to explore the landscape and familiarize with concepts and tools. Neither is it the best way to learn how to think for yourself. So how can you escape it?
|
||||
|
||||
### Debug
|
||||
|
||||
The most common exit ramp comes up when you end up in debugging mode. Oftentimes a tutorial can be outdated or something in the setup can be just a tiny bit different. At this point, you have to figure out how to solve the issue to proceed.
|
||||
|
||||
Entering debugging mode, you realize that **you know only a part of the big picture**. What you don't know might not be immediately obvious, but with perseverance you will be able to figure it out. At that point, you can start looking for answers to solve the issue.
|
||||
|
||||
Debugging a problem offers a little bit of **exposure to the underlying technologies and concepts**. This way you can dip your toe into whatever it is you are working with, without getting overwhelmed.
|
||||
|
||||
### Be curious
|
||||
|
||||
Curiosity killed the cat, but not you. Stop every step of the way to ask what it is you are learning. Do you understand the concepts? Do you know what it is you are building? Is this tool your only option? What problem does it solve? Are there others?
|
||||
|
||||
Questions are a valuable tool to help you explore the landscape. You don't necessarily need to answer them all, nor do you need to read tons of documentation or articles about a topic. Simply **taking a peek at the big picture** and **asking questions** is enough. You can always come back later and dive deeper.
|
||||
|
||||
### Experiment
|
||||
|
||||
The third and best way to escape tutorial hell is to experiment. After you finish a couple of tutorials, **put your skills to the test**. Try to build something from scratch, looking up anything you need to, as you go. It won't be as easy or as fast as the tutorial was, but you'll learn a lot more.
|
||||
|
||||
After you build a project, **take another look**. Figure out what you could have done differently or what parts of it you don't understand well enough. Refine it, polish up parts of it, add new features. The more time you put into it, the better you will become.
|
||||
|
||||
Then do it a few more times and experiment with a few more ideas. As soon as you start working on your own projects and get into a rhythm, it's a lot easier to learn new things. Some slopes will be steeper and others will be more gentle. But you will feel much **more confident in your skills** than blindly following tutorials.
|
||||
|
||||
### Learn your own way
|
||||
|
||||
As a closing suggestion, I want to urge you to **find your own learning style**. Tutorials might be right for some and I don't suggest you dismiss them entirely. After all, we all have to start somewhere. But if they don't work for you, **mix it up a bit**. Try reading the documentation, solving coding exercises or tinkering with an existing project. Everyone is different and there's no one size fits all when it comes to learning.
|
||||
|
||||
For some, much like myself, teaching is the best learning experience. After all, if you can't explain something simply, you probably don't understand it well enough. Explaining, then, can lead you to researching, debugging, experimenting and being curious all at once. And teaching, much like learning, can be done in many many different ways.
|
||||
50
snippets/articles/s/eslint-refactor-for-in.md
Normal file
50
snippets/articles/s/eslint-refactor-for-in.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: "Tip: Refactoring your for...in loops to avoid ESLint warnings"
|
||||
shortTitle: Refactoring for...in loops to avoid ESLint warnings
|
||||
type: tip
|
||||
language: javascript
|
||||
tags: [array,iterator,eslint]
|
||||
author: chalarangelo
|
||||
cover: typing
|
||||
excerpt: ESLint is a really useful tool, but sometimes it gets in the way. Learn how to refactor code to get rid of a common warning.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
ESLint is one of my tools of choice, but oftentimes it gets in the way of work, due to the way it prefers me to do things. One of the warnings I have seen more times than I care to admit is the following:
|
||||
|
||||
> for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.eslint(no-restricted-syntax)
|
||||
|
||||
And here are three refactoring options to deal with it:
|
||||
|
||||
### Object.keys()
|
||||
|
||||
`Object.keys()` has the exact same behavior as a `for...in` loop, so it can be used as a drop-in replacement:
|
||||
|
||||
```js
|
||||
const data = [3, 4];
|
||||
// Same as for (let k in data) console.log(k)
|
||||
Object.keys(data).forEach(k => console.log(k));
|
||||
// 0 1
|
||||
```
|
||||
|
||||
### Object.values()
|
||||
|
||||
`Object.values()` is very similar to `Object.keys()`, but returns the values instead of the keys, which might be what you are really using the keys for:
|
||||
|
||||
```js
|
||||
const data = [3, 4];
|
||||
// Iterate over the values
|
||||
Object.values(data).forEach(v => console.log(v));
|
||||
// 3 4
|
||||
```
|
||||
|
||||
### Object.entries()
|
||||
|
||||
Finally, if you need both key and value, `Object.entries()` has you covered:
|
||||
|
||||
```js
|
||||
const data = [3, 4];
|
||||
// Iterate over the data, returning key-value pairs
|
||||
Object.entries(data).forEach(e => console.log(e[0], e[1]));
|
||||
// [0, 3] [1, 4]
|
||||
```
|
||||
80
snippets/articles/s/flexbox-cheatsheet.md
Normal file
80
snippets/articles/s/flexbox-cheatsheet.md
Normal file
@ -0,0 +1,80 @@
|
||||
---
|
||||
title: Flexbox Cheat Sheet
|
||||
type: cheatsheet
|
||||
language: css
|
||||
tags: [layout,flexbox,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: frames
|
||||
excerpt: Flexbox allows you to create fluid layouts easily. If you are constantly looking up how it works, this handy cheatsheet is all you need.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Container
|
||||
|
||||
- `display: flex` or `display: inline-flex`: creates a flex context (or an inline flex context) for direct children of this element
|
||||
- `flex-direction` determines the main and cross axis for the container, valid values are:
|
||||
- `row` (default): horizontal, in the direction of writing (left to right for English)
|
||||
- `row-reverse`: horizontal, in the opposite direction of writing (right to left for English)
|
||||
- `column`: vertical, top to bottom
|
||||
- `column-reverse`: vertical, bottom to top
|
||||
- `flex-wrap` determines if flex items will try to fit in one line, valid values are:
|
||||
- `nowrap` (default): all flex items will be on one line
|
||||
- `wrap`: flex items will wrap onto multiple lines, top to bottom
|
||||
- `wrap-reverse`: flex items will wrap onto multiple lines, bottom to top
|
||||
- `flex-flow`: shorthand combining `flex-direction` and `flex-wrap`
|
||||
- Formal syntax: `flex-flow: <'flex-direction'> || <'flex-wrap'>`
|
||||
- `justify-content` defines the alignment along the main axis, valid values are:
|
||||
- `flex-start` (default): pack flex items from the start
|
||||
- `flex-end`: pack flex items from the end
|
||||
- `start`: pack items from the start
|
||||
- `end`: pack items from the end
|
||||
- `left`: pack items from the left
|
||||
- `right`: pack items from the right
|
||||
- `center`: pack items around the center
|
||||
- `space-around`: distribute items evenly with equal space around them
|
||||
- `space-between`: distribute items evenly with equal space between them
|
||||
- `space-evenly`: distribute items evenly, ensuring equal space between any two items
|
||||
- `stretch`: distribute items evenly, stretching auto-sized items to fit the container
|
||||
- `align-items` defines the alignment along the cross axis, valid values are:
|
||||
- `flex-start` (default): pack flex items from the start
|
||||
- `flex-end`: pack flex items from the end
|
||||
- `start`: pack items from the start
|
||||
- `end`: pack items from the end
|
||||
- `center`: pack items around the center
|
||||
- `baseline`: align items based on their baselines
|
||||
- `stretch`: stretch items to fill the container
|
||||
- `align-content` defines the alignment of extra space along the cross axis, valid values are:
|
||||
- `flex-start` (default): pack flex items from the start
|
||||
- `flex-end`: pack flex items from the end
|
||||
- `start`: pack items from the start
|
||||
- `end`: pack items from the end
|
||||
- `center`: pack items around the center
|
||||
- `space-around`: distribute items evenly with equal space around them
|
||||
- `space-between`: distribute items evenly with equal space between them
|
||||
- `space-evenly`: distribute items evenly, ensuring equal space between any two items
|
||||
- `stretch`: distribute items evenly, stretching auto-sized items to fit the container
|
||||
|
||||

|
||||
|
||||
### Items
|
||||
|
||||
- `flex-grow` determines how much the item can grow if necessary
|
||||
- Accepts a single positive number (unitless), default value is `0`
|
||||
- Specifies how much of the remaining space in the flex container should be assigned to the item
|
||||
- The remaining space is the size of the flex container minus the size of all flex items' sizes together
|
||||
- If all items have the same `flex-grow`, all items will receive an equal share of the remaining space
|
||||
- If not all items have the same `flex-grow`, the remaining space is distributed according to the ratio defined by these values
|
||||
- `flex-shrink` determines how much the items can shrink if necessary
|
||||
- Accepts a single positive number (unitless), default value is `1`
|
||||
- If the size of all flex items is larger than the flex container, items shrink to fit according to `flex-shrink`
|
||||
- `flex-basis` determines the initial size of a flex item before the remaining space is distributed
|
||||
- Can use any valid `width` value, intrinsic size values, `auto` (default) or `content`
|
||||
- `auto` means "look at my `width` or `height` property", whereas `content` is used for automatic sizing
|
||||
- `flex`: shorthand combining `flex-grow`, `flex-shrink` and `flex-basis`
|
||||
- Formal syntax: `flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]`
|
||||
- `align-self` allows the item to override the default `align-items` specified by the container
|
||||
- Valid values are the same as those of the `align-items` property in the container
|
||||
- `order` determines the ordering of the item
|
||||
- Accepts an integer value
|
||||
- Items in a container are sorted by ascending `order` value and then by their source code order
|
||||
- Might cause accessibility issues if used incorrectly
|
||||
52
snippets/articles/s/git-aliases.md
Normal file
52
snippets/articles/s/git-aliases.md
Normal file
@ -0,0 +1,52 @@
|
||||
---
|
||||
title: Git aliases
|
||||
type: cheatsheet
|
||||
language: git
|
||||
tags: [configuration,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: organizer
|
||||
excerpt: Increase your productivity by creating aliases for many common git operations.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Creating aliases
|
||||
|
||||
Use the command below to create aliases, replacing `<alias>` with the name of the alias and `<command>` with the command to be aliased:
|
||||
|
||||
```shell
|
||||
git config --global alias.<alias> <command>
|
||||
```
|
||||
|
||||
Additionally, you can use [edit the configuration file](/git/s/edit-config) and add many aliases all at once.
|
||||
|
||||
### Useful aliases
|
||||
|
||||
```editorconfig
|
||||
[alias]
|
||||
co = checkout
|
||||
cob = checkout -b
|
||||
coo = !git fetch && git checkout
|
||||
br = branch
|
||||
brd = branch -d
|
||||
st = status
|
||||
aa = add -A .
|
||||
unstage = reset --soft HEAD^
|
||||
cm = commit -m
|
||||
amend = commit --amend -m
|
||||
fix = commit --fixup
|
||||
undo = reset HEAD~1
|
||||
rv = revert
|
||||
cp = cherry-pick
|
||||
pu = !git push origin `git branch --show-current`
|
||||
fush = push -f
|
||||
mg = merge --no-ff
|
||||
rb = rebase
|
||||
rbc = rebase --continue
|
||||
rba = rebase --abort
|
||||
rbs = rebase --skip
|
||||
rom = !git fetch && git rebase -i origin/master --autosquash
|
||||
save = stash push
|
||||
pop = stash pop
|
||||
apply = stash apply
|
||||
rl = reflog
|
||||
```
|
||||
21
snippets/articles/s/git-commit-different-date.md
Normal file
21
snippets/articles/s/git-commit-different-date.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Tip: Create a commit with a different date"
|
||||
shortTitle: Create a commit with a different date
|
||||
type: tip
|
||||
language: git
|
||||
tags: [commit]
|
||||
author: chalarangelo
|
||||
cover: ice
|
||||
excerpt: Ever needed to create a git commit with a different date? Here's a quick and easy way to do it.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Sometimes, you might run into a situation where you need to create a commit with a different date than the current one. Luckily, you can handle this using `GIT_AUTHOR_DATE` and `GIT_COMMITTER_DATE`:
|
||||
|
||||
```shell
|
||||
GIT_AUTHOR_DATE='Mon May 18 19:32:10 2020 -0400' \
|
||||
GIT_COMMITTER_DATE='Mon May 18 19:32:10 2020 -0400'\
|
||||
git commit -m 'Commit from the past'
|
||||
```
|
||||
|
||||
As shown in the example above, you can set both values to any date you like and your code will be committed on that date. Note that the format for the values above is `'date +"%s %z"'`, also referred to as internal raw git format, but you can also use other formats, such as RFC 2822 (`'Mon, 18 May 2020 19:32:10 -0400'`), ISO 8601 (`'2020-05-18 19:32:10 -0400'`), local (`'Mon May 18 19:32:10 2020'`), short (`'2020-05-18'`) or relative (`5.seconds.ago`, `2.years.3.months.ago`, `'6am yesterday'`).
|
||||
25
snippets/articles/s/git-fast-forward.md
Normal file
25
snippets/articles/s/git-fast-forward.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: How does Git's fast-forward mode work?
|
||||
shortTitle: Git fast-forward
|
||||
type: question
|
||||
language: git
|
||||
tags: [branch]
|
||||
author: chalarangelo
|
||||
cover: boats
|
||||
excerpt: Learn about Git's fast-forward mode works and its benefits when mergin branches, so you can decide if it's a good fit for you and your team.
|
||||
dateModified: 2021-07-15T05:00:00-04:00
|
||||
---
|
||||
|
||||
Merging a branch is one of the most common operations when working with Git. Depending on your team and projects you've been a part of, you might have heard of or even used Git's **fast-forward** mode when merging. Fast-forward mode is the default in Git, however GitHub will essentially override this by default and create a merge commit instead.
|
||||
|
||||

|
||||
|
||||
### Fast-forward merge
|
||||
|
||||
As stated above, Git's default is to use fast-forward merge. It will take the commits from the branch being merged and place them at the tip of the branch you're merging into. This creates a **linear history**, which is also the main advantage of using fast-forward merge. If you want to emulate fast-forward merge on GitHub, you can use the "Rebase and merge" option.
|
||||
|
||||
### Non fast-forward merge
|
||||
|
||||
GitHub, on the other hand, uses non fast-forward merge by default. It will create a merge commit at the tip of the branch you're merging into, optionally referencing the branch being merged in the commit message. This has the advantage of **keeping track of branches** more explicitly than fast-forward merge. If you want to get the same behavior in a Git terminal, you can use the `--no-ff` flag.
|
||||
|
||||
As a side note, you can configure the default Git merge behavior, using `git config`. To learn how to do so, you can take a look at the [relevant snippet](/git/s/disable-fast-forward).
|
||||
27
snippets/articles/s/github-co-authors.md
Normal file
27
snippets/articles/s/github-co-authors.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "Tip: How to add multiple authors to a commit"
|
||||
shortTitle: Add multiple authors to a commit
|
||||
type: tip
|
||||
language: git
|
||||
tags: [github,programming,webdev]
|
||||
author: chalarangelo
|
||||
cover: book-chair
|
||||
excerpt: Learn how to add multiple authors to a git commit with this quick and easy tip.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
You can add multiple authors to a git commit, by adding one or more `Co-authored-by` trailers to the commit's message:
|
||||
|
||||
```shellsession
|
||||
$ git commit -m "Refactor usability tests.
|
||||
>
|
||||
>
|
||||
Co-authored-by: name <name@example.com>
|
||||
Co-authored-by: another-name <another-name@example.com>"
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
- To correctly attribute a commit to a co-author, you must use the email associated with their GitHub account.
|
||||
- If a person's email is private, you can use their GitHub-provided `no-reply` email.
|
||||
- Leave one or preferably two empty lines before any `Co-authored-by` trailers.
|
||||
23
snippets/articles/s/html-head-icons.md
Normal file
23
snippets/articles/s/html-head-icons.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: Recommended HTML head icon tags
|
||||
shortTitle: HTML favicons template
|
||||
type: story
|
||||
language: html
|
||||
tags: [webdev,browser]
|
||||
author: chalarangelo
|
||||
cover: boutique-home-office-3
|
||||
excerpt: Ensure your HTML documents have a proper favicon by including these lines in your `<head>` element.
|
||||
dateModified: 2023-01-24T05:00:00-04:00
|
||||
---
|
||||
|
||||
Over the years, I've seen many different and often conflicting guidelines about favicons and which tags are essential. Nowadays, I think you can get away with a very **minimal set of meta tags** and tailor them to your needs as you go. Here's my recommendation for the bare minimum you should include in your `<head>` element:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<link rel="icon" sizes="192x192" href="favicon.png">
|
||||
<link rel="apple-touch-icon" href="favicon.png">
|
||||
<link rel="mask-icon" href="favicon.svg" color="#000000">
|
||||
</head>
|
||||
```
|
||||
|
||||
By creating a single 192x192 PNG asset, you can cover almost all modern devices and browsers, without too much hassle. If you want to go the extra mile, you can include a few more quite easily. Simply downscale the image and include more `rel="icon"` meta tags with different `sizes` attributes.
|
||||
27
snippets/articles/s/html-head-links.md
Normal file
27
snippets/articles/s/html-head-links.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: Recommended HTML head links
|
||||
shortTitle: HTML head links template
|
||||
type: story
|
||||
language: html
|
||||
tags: [webdev,browser]
|
||||
author: chalarangelo
|
||||
cover: boutique-home-office-4
|
||||
excerpt: Make your HTML documents more SEO-friendly by including these lines in your `<head>` element.
|
||||
dateModified: 2023-01-26T05:00:00-04:00
|
||||
---
|
||||
|
||||
The `<head>` element of an HTML document is where you can include links to external resources such as CSS stylesheets and JavaScript files. Some `<link>` elements, however, are important for SEO and metadata purposes. Here's a list of a few really important ones I like to include:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<link rel="canonical" href="https://samplesite.com/page.html">
|
||||
<link rel="sitemap" type="application/xml" href="https://samplesite.com/sitemap.xml">
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="https://samplesite.com/rss.xml">
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Search" href="https://samplesite.com/search.xml">
|
||||
</head>
|
||||
```
|
||||
|
||||
- The `canonical` link element tells search engines which URL is the **canonical version** of the page. This helps prevent duplicate content issues and ensures that the correct page is indexed.
|
||||
- The `sitemap` link element tells search engines where to find the **sitemap** for the website. Sitemaps are XML files that contain a list of all the pages on the website and their metadata. They are used by search engines to index the website and display it in search results.
|
||||
- The `alternate` link element tells search engines where to find the **RSS feed** for the website. RSS feeds are XML files that contain a list of the most recent posts on the website. They are used by search engines to display the website's content in search results, as well as by RSS readers to display the website's content in a more convenient format.
|
||||
- The `search` link element is used by browsers to display a **search box** in the browser's address bar. This allows users to search the website directly from the address bar, instead of having to navigate to the search page.
|
||||
38
snippets/articles/s/html-head-social-tags.md
Normal file
38
snippets/articles/s/html-head-social-tags.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Recommended social tags for HTML head
|
||||
shortTitle: HTML social tags template
|
||||
type: story
|
||||
language: html
|
||||
tags: [webdev,browser]
|
||||
author: chalarangelo
|
||||
cover: boutique-home-office-2
|
||||
excerpt: Ensure your HTML documents can be shared on social media by including these lines in your `<head>` element.
|
||||
dateModified: 2023-01-22T05:00:00-04:00
|
||||
---
|
||||
|
||||
Social media play an important role to any content's success. To ensure your content is properly shared on social media, you should include some essential tags in your `<head>` element:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<meta property="og:title" content="Page Title">
|
||||
<meta property="og:description" content="Page description. No longer than 155 characters.">
|
||||
<meta property="og:image" content="https://samplesite.com/image.jpg">
|
||||
<meta property="og:site_name" content="Sample Site">
|
||||
<meta property="og:url" content="https://samplesite.com/page.html">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="Page Title">
|
||||
<meta name="twitter:description" content="Page description. No longer than 155 characters.">
|
||||
<meta name="twitter:image" content="https://samplesite.com/image.jpg">
|
||||
<meta name="twitter:site" content="@samplesite">
|
||||
</head>
|
||||
```
|
||||
|
||||
The above snippet contains OpenGraph and Twitter tags. **OpenGraph tags** are used by Facebook and other social media platforms to display a preview of the page when it's shared. Similarly, Twitter uses **Twitter tags** for the same information. Here's a breakdown of each one:
|
||||
|
||||
- The `og:title` and `twitter:title` meta tags are used to display the page's title in the preview.
|
||||
- The `og:description` and `twitter:description` meta tags are used to display a short description of the page in the preview.
|
||||
- The `og:image` and `twitter:image` meta tags are used to display an image in the preview.
|
||||
- The `og:site_name` meta tag is used to display the name of the site in the preview.
|
||||
- The `og:url` meta tag is used to display the URL of the page in the preview.
|
||||
- The `twitter:card` meta tag is used to display a preview of the page when it's shared. Available values are `summary`, `summary_large_image`, `app` and `player`.
|
||||
- The `twitter:site` meta tag is used to display the Twitter handle of the site in the preview.
|
||||
29
snippets/articles/s/html-recommended-minimum-head.md
Normal file
29
snippets/articles/s/html-recommended-minimum-head.md
Normal file
@ -0,0 +1,29 @@
|
||||
---
|
||||
title: Recommended minimum HTML head
|
||||
shortTitle: HTML head template
|
||||
type: story
|
||||
language: html
|
||||
tags: [webdev,browser]
|
||||
author: chalarangelo
|
||||
cover: boutique-home-office-1
|
||||
excerpt: Ensure your HTML documents are properly structured by including these lines in your `<head>` element.
|
||||
dateModified: 2023-01-18T05:00:00-04:00
|
||||
---
|
||||
|
||||
An essential part of an HTML document is the `<head>` element, which contains metadata about the document. Some vital information, such as the document's title and character encoding are stored in the `<head>` element. It's also where you can include links to external resources such as CSS stylesheets and JavaScript files.
|
||||
|
||||
More often than not, this sort of metadata can grow in complexity with time. However, there are a few important things that should never be omitted. Here's a list of the bare minimum you should include in your `<head>` element:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Page Title</title>
|
||||
<meta name="description" content="Page description. No longer than 155 characters.">
|
||||
</head>
|
||||
```
|
||||
|
||||
- The `charset` meta tag tells the browser what **character encoding** to use when rendering the document.
|
||||
- The `viewport` meta tag tells the browser how to render the page on **mobile devices**.
|
||||
- The `title` element is used by search engines to display the page's **title** in search results.
|
||||
- The `description` meta tag is used by search engines to display a **short description** of the page in search results.
|
||||
40
snippets/articles/s/http-status-codes-cheatsheet.md
Normal file
40
snippets/articles/s/http-status-codes-cheatsheet.md
Normal file
@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Common HTTP status codes Cheat Sheet
|
||||
shortTitle: HTTP status codes
|
||||
type: cheatsheet
|
||||
tags: [webdev,http]
|
||||
author: chalarangelo
|
||||
cover: lake-runner
|
||||
excerpt: Familiarize yourself with the most common HTTP status codes with this handy cheatsheet.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### 1xx - Informational
|
||||
|
||||
- **100**: Continue - Everything is ok so far
|
||||
- **102**: Processing - Request is being processed, no response available yet
|
||||
|
||||
### 2xx - Success
|
||||
|
||||
- **200**: OK - Request successful
|
||||
- **201**: Created - Request fulfilled, new resource has been created
|
||||
|
||||
### 3xx - Redirection
|
||||
|
||||
- **301**: Moved Permanently - Resource permanently moved to a new URL
|
||||
- **302**: Moved Temporarily - Resource temporarily moved to a new URL
|
||||
|
||||
### 4xx - Client Error
|
||||
|
||||
- **400**: Bad Request - Server cannot understand and process the request
|
||||
- **401**: Unauthorized - Authentication required, user not yet authenticated
|
||||
- **403**: Forbidden - Insufficient access permissions to the resource
|
||||
- **404**: Not Found - Requested resource not found
|
||||
- **410**: Gone - Request no longer available due to intentional removal
|
||||
|
||||
### 5xx - Server Error
|
||||
|
||||
- **500**: Internal Server Error - Generic unhandled server error
|
||||
- **502**: Bad Gateway - Gateway server got an invalid response
|
||||
- **503**: Service Unavailable - Server temporarily unable to handle request
|
||||
- **504**: Gateway Timeout - Gateway server didn't get a response in time
|
||||
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: How can I add a key-value pair to a JavaScript object?
|
||||
shortTitle: Add key-value pair to object
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object]
|
||||
author: chalarangelo
|
||||
cover: pineapple-laptop
|
||||
excerpt: Adding a key-value pair to a JavaScript object is straightforward, yet there are multiple ways available to do so.
|
||||
dateModified: 2022-07-21T05:00:00-04:00
|
||||
---
|
||||
|
||||
Adding a key-value pair to a JavaScript object is straightforward, yet there are multiple ways available to do so. While mostly similar, these approaches have some differences that you should be aware of.
|
||||
|
||||
### Dot notation
|
||||
|
||||
The most common and straightforward way to add a key-value pair to an object is to use the dot notation. You have probably already used this in the past, and it's sufficient in most situations you will encounter.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
obj.b = 2;
|
||||
obj.c = 3;
|
||||
// obj = { a: 1, b: 2, c: 3 }
|
||||
```
|
||||
|
||||
### Square bracket notation
|
||||
|
||||
Similar to dot notation, square bracket notation comes in handy when dealing with **dynamic keys**, but can also work with static keys. Apart from that, it's exactly the same as dot notation both in functionality and performance.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
const bKey = 'b';
|
||||
obj[bKey] = 2;
|
||||
obj['c'] = 3;
|
||||
// obj = { a: 1, b: 2, c: 3 }
|
||||
```
|
||||
|
||||
### Object.assign()
|
||||
|
||||
`Object.assign()` is slightly different than the previous two options. It can be used to add multiple properties to an object at once and it can also **shallow merge** two or more objects. It is not as performant, however, so it should only be used when necessary.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
Object.assign(obj, { b: 2 }, { c: 3 });
|
||||
// obj = { a: 1, b: 2, c: 3 }
|
||||
```
|
||||
|
||||
### Object.defineProperty()
|
||||
|
||||
Another, less-common, way to add a key-value pair to an object is to use `Object.defineProperty()`. This is the lest performant way to add a key-value pair to an object, but it allows the new property to be **precisely defined**. This function accepts either a data or accessor descriptor as its second argument, allowing the behavior of the new property to be customized as desired. Bear in mind that you can add multiple properties at once, using `Object.defineProperties()`.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
Object.defineProperty(obj, 'b', {
|
||||
value: 2,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
Object.defineProperty(obj, 'c', {
|
||||
value: 3,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true
|
||||
});
|
||||
// obj = { a: 1, b: 2, c: 3 }
|
||||
```
|
||||
|
||||
### Object spread operator
|
||||
|
||||
Last but not least, there's the object spread operator (`...`). Contrary to previous methods, this one **doesn't mutate the original object**, but instead returns a new object with the added properties. As expected, the performance of this approach is significantly worse than previous ones, due to the need to create a new object.
|
||||
|
||||
```js
|
||||
const obj = { a: 1 };
|
||||
const newObj = { ...obj, b: 2, c: 3 };
|
||||
// obj = { a: 1 }
|
||||
// newObj = { a: 1, b: 2, c: 3 }
|
||||
```
|
||||
89
snippets/articles/s/javascript-array-comparison.md
Normal file
89
snippets/articles/s/javascript-array-comparison.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: How do I compare two arrays in JavaScript?
|
||||
shortTitle: JavaScript array comparison
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array,comparison]
|
||||
author: chalarangelo
|
||||
cover: coconuts
|
||||
excerpt: Learn how you can compare two arrays in JavaScript using various different techniques.
|
||||
dateModified: 2021-09-27T16:36:32+03:00
|
||||
---
|
||||
|
||||
### Equality comparison
|
||||
|
||||
Comparing two arrays in JavaScript using either the loose or strict equality operators (`==` or `===`) will most often result in `false`, even if the two arrays contain the same elements in the same order. This is due to the fact that arrays and objects are compared by reference and not by value in JavaScript, which means this solution does not produce the desired result:
|
||||
|
||||
```js
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3];
|
||||
|
||||
a === b; // false
|
||||
```
|
||||
|
||||
### JSON.stringify
|
||||
|
||||
A common solution that many people suggest is to use `JSON.stringify()`. This allows us to serialize each array and then compare the two serialized strings. A simple implementation of this might look something like this:
|
||||
|
||||
```js
|
||||
const equals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3];
|
||||
|
||||
equals(a, b); // true
|
||||
```
|
||||
|
||||
While this seems like a great, short and easily understandable solution, it falls short on some edge cases where different values' serialized string is the same. For example:
|
||||
|
||||
```js
|
||||
const str = 'a';
|
||||
const strObj = new String('a');
|
||||
str === strObj; // false
|
||||
equals([str], [strObj]); // true, should be false
|
||||
|
||||
null === undefined; // false
|
||||
equals([null], [undefined]); // true, should be false
|
||||
```
|
||||
|
||||
While these cases seem rather uncommon, they might cause some very annoying issues that are hard to track down and fix. This is the reason why this solution is not recommended for most use-cases.
|
||||
|
||||
### A better way
|
||||
|
||||
A better approach would be to compare the two arrays' `length`s and use `Array.prototype.every()` to compare the values of the two:
|
||||
|
||||
```js
|
||||
const equals = (a, b) =>
|
||||
a.length === b.length &&
|
||||
a.every((v, i) => v === b[i]);
|
||||
|
||||
const a = [1, 2, 3];
|
||||
const b = [1, 2, 3];
|
||||
const str = 'a';
|
||||
const strObj = new String('a');
|
||||
|
||||
equals(a, b); // true
|
||||
equals([str], [strObj]); // false
|
||||
equals([null], [undefined]); // false
|
||||
```
|
||||
|
||||
This approach safeguards against the serialization issue described above. However it does not take into account nested arrays or objects, which need to be checked recursively. For a robust solution that handles this and other issues, you should use the [equals snippet](/js/s/equals).
|
||||
|
||||
### Comparing out of order
|
||||
|
||||
Finally, there are cases where the order of the elements in each array is not important and we only care about the same values existing in both arrays. For these cases, you can use `Set` and `Array.prototype.filter()` in combination with a loop to iterate over unique values and check if each one appears the same amount of times in each array:
|
||||
|
||||
```js
|
||||
const equalsIgnoreOrder = (a, b) => {
|
||||
if (a.length !== b.length) return false;
|
||||
const uniqueValues = new Set([...a, ...b]);
|
||||
for (const v of uniqueValues) {
|
||||
const aCount = a.filter(e => e === v).length;
|
||||
const bCount = b.filter(e => e === v).length;
|
||||
if (aCount !== bCount) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
For a more detailed explanation, you should check out the [haveSameContents snippet](/js/s/have-same-contents).
|
||||
38
snippets/articles/s/javascript-array-includes-value.md
Normal file
38
snippets/articles/s/javascript-array-includes-value.md
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
title: How can I check if a JavaScript array includes a specific value?
|
||||
shortTitle: JavaScript array includes value
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array]
|
||||
author: chalarangelo
|
||||
cover: bridge-drop
|
||||
excerpt: Checking if an array includes a specific value is pretty straightforward, except when it comes to objects.
|
||||
dateModified: 2022-09-18T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Primitive values
|
||||
|
||||
You can use `Array.prototype.includes()` to check if an array contains a primitive value. This is the most convenient option when working with strings, numbers, booleans, symbols, `null` or `undefined`. You can even specify an index as a secondary parameter to start searching from.
|
||||
|
||||
```js
|
||||
const array = [1, 2, 3, 4, 5];
|
||||
|
||||
array.includes(3); // true
|
||||
array.includes(6); // false
|
||||
array.includes(3, 3); // false
|
||||
```
|
||||
|
||||
### Objects
|
||||
|
||||
Unlike primitive values, you can't use `Array.prototype.includes()` to check if an array includes an object. This comes down to how JavaScript compares values and the fact that [objects are reference types](/articles/s/javascript-pass-by-reference-or-pass-by-value). I highly recommend reading the previous article about [object comparison](/articles/s/javascript-object-comparison), as I won't be going into detail on how to compare objects here.
|
||||
|
||||
Due to this difference between primitive values and objects, you can't use `Array.prototype.includes()` to check if an array includes an object. However, provided you implement a [deep equality function](/js/s/equals), you can use `Array.prototype.some()` to check if any object matches the shape of another object.
|
||||
|
||||
```js
|
||||
const array = [{ a: 1 }, { a: 2 }, { a: 3 }];
|
||||
|
||||
const equals = (a, b) => Object.keys(a).every(key => a[key] === b[key]);
|
||||
|
||||
array.some(item => equals(item, { a: 2 })); // true
|
||||
array.some(item => equals(item, { a: 4 })); // false
|
||||
```
|
||||
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Can I use an arrow function as the callback for an event listener in JavaScript?
|
||||
shortTitle: Arrow function as callback for event listener
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,event,function]
|
||||
author: chalarangelo
|
||||
cover: coffee-float
|
||||
excerpt: Learn the differences between JavaScript ES6 arrow functions and regular functions and how they affect event listener callbacks.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Arrow functions
|
||||
|
||||
JavaScript ES6 introduced the concept of arrow functions, a new way to define and write functions. While they might seem like a syntactic sugar on top of regular functions, they have a key difference which lies in the way the `this` context is bound. I strongly suggest you read [Understanding the "this" keyword in JavaScript](/blog/s/javascript-this), as I will not go into detail about the topic in this article. To summarize:
|
||||
|
||||
> Arrow functions do not have their own bindings for `this`, resulting in `this` retaining the value of the enclosing lexical context's `this`.
|
||||
|
||||
### Event listener callbacks
|
||||
|
||||
A common task when writing browser-side JavaScript is creating event listeners. For example:
|
||||
|
||||
```js
|
||||
const toggleElements = document.querySelectorAll('.toggle');
|
||||
toggleElements.forEach(el => {
|
||||
el.addEventListener('click', function() {
|
||||
this.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
In the example above, we use `NodeList.prototype.forEach()` to iterate over matching nodes and `EventTarget.addEventListener()` with a regular function as the callback for the `'click'` event to swap between an active and inactive state for the clicked element. We are using a regular function, so the `this` context inside the callback will be bound to the event target.
|
||||
|
||||
### Arrow functions as callbacks
|
||||
|
||||
As we have already explained, arrow functions do not have their own bindings for `this`. So what happens if we convert the previous code snippet's callback to an arrow function? Its `this` context refers to the global one, which in this case is the `Window` object.
|
||||
|
||||
```js
|
||||
const toggleElements = document.querySelectorAll('.toggle');
|
||||
toggleElements.forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
this.classList.toggle('active'); // `this` refers to `Window`
|
||||
// Error: Cannot read property 'toggle' of undefined
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
This code will fire the event listener and execute the callback anytime the matching element is clicked. It will, however, throw an error, due to the `Window` object not having a `classList` property. Oftentimes, the code could even fail silently. An example would be a condition that always evaluates to `false` for `Window`, but could evaluate to `true` for a given element. Issues like that result in many headaches and wasted hours until you can uncover and fix them.
|
||||
|
||||
To deal with this, one could simply use the first argument of the callback function and `Event.target` or `Event.currentTarget` depending on their needs:
|
||||
|
||||
```js
|
||||
const toggleElements = document.querySelectorAll('.toggle');
|
||||
toggleElements.forEach(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
e.currentTarget.classList.toggle('active'); // works correctly
|
||||
});
|
||||
});
|
||||
```
|
||||
109
snippets/articles/s/javascript-arrow-functions.md
Normal file
109
snippets/articles/s/javascript-arrow-functions.md
Normal file
@ -0,0 +1,109 @@
|
||||
---
|
||||
title: Introduction to arrow functions in JavaScript
|
||||
shortTitle: Arrow functions introduction
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: arrow-functions
|
||||
excerpt: JavaScript arrow functions are a very useful tool to learn and master. Here's a complete introduction to everything you need to know.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Syntax
|
||||
|
||||
In order to understand arrow function syntax, we should start by refactoring a regular function step by step:
|
||||
|
||||
```js
|
||||
function square(a) {
|
||||
return a * a;
|
||||
}
|
||||
```
|
||||
|
||||
We can start by refactoring the function declaration to use a variable assignment:
|
||||
|
||||
```js
|
||||
const square = function (a) {
|
||||
return a * a;
|
||||
}
|
||||
```
|
||||
|
||||
Then, we can refactor the regular `function` to an arrow function:
|
||||
|
||||
```js
|
||||
const square = (a) => {
|
||||
return a * a;
|
||||
}
|
||||
```
|
||||
|
||||
If there's only one argument, we can omit the parentheses around it:
|
||||
|
||||
```js
|
||||
const square = a => {
|
||||
return a * a;
|
||||
}
|
||||
```
|
||||
|
||||
If the function is a single expression, you can omit the curly braces and `return` statement and use an implicit return:
|
||||
|
||||
```js
|
||||
const square = a => a * a;
|
||||
```
|
||||
|
||||
### Execution context
|
||||
|
||||
The main difference between arrow functions and regular functions is execution context (i.e. the value of `this`). Technically speaking, most other differences often mentioned either stem from this one or are side effects of it.
|
||||
|
||||
In a regular function, `this` is dynamic and depends on how the function was invoked:
|
||||
|
||||
```js
|
||||
function simple() { return this; }
|
||||
const object = {
|
||||
method() { return this; }
|
||||
};
|
||||
class Class {
|
||||
classMethod() { console.log(this); }
|
||||
}
|
||||
const instance = new Class();
|
||||
|
||||
simple(); // `this` refers to the global object
|
||||
new simple(); // `this` refers to the newly created instance
|
||||
|
||||
object.method(); // `this` refers to `object`
|
||||
simple.call(object); // `this` refers to `object`
|
||||
|
||||
instance.classMethod(); // `this` refers to `instance`
|
||||
setTimeout(
|
||||
instance.classMethod, 0 // `this` refers to the global object
|
||||
);
|
||||
```
|
||||
|
||||
Arrow functions, unlike regular ones, don't define their own execution context therefore `this` inside an arrow function always refers to the lexical `this` (i.e. the scope in which the arrow function was defined).
|
||||
|
||||
```js
|
||||
const simple = () => this;
|
||||
const object = {
|
||||
method: () => this
|
||||
};
|
||||
class Class {
|
||||
classMethod = () => { console.log(this); }
|
||||
}
|
||||
const instance = new Class();
|
||||
|
||||
simple(); // `this` refers to the global object
|
||||
new simple(); // Uncaught TypeError: simple is not a constructor
|
||||
|
||||
object.method(); // `this` refers to the global object
|
||||
simple.call(object); // `this` refers to the global object
|
||||
|
||||
instance.classMethod(); // `this` refers to `instance`
|
||||
setTimeout(
|
||||
instance.classMethod, 0 // `this` refers to `instance`
|
||||
);
|
||||
```
|
||||
|
||||
As you can see from these examples, there's a difference in how constructors work due to the execution context and how it's resolved. Regular functions can be used as constructors in contrast to arrow functions which will throw a `TypeError` instead.
|
||||
|
||||
Moreover, arrow functions and regular functions present some differences when used to define class methods. A regular function method will end up with a different execution context when passed as a callback. This can be handled using `Function.prototype.bind()` or by using an arrow function which doesn't have this issue.
|
||||
|
||||
If you want to read more about the `this` keyword, you should check out our [previous article on the subject](/blog/s/javascript-this).
|
||||
@ -0,0 +1,125 @@
|
||||
---
|
||||
title: What are the differences between arrow functions and regular functions in JavaScript?
|
||||
shortTitle: Arrow functions vs regular functions
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: fallen-leaves
|
||||
excerpt: JavaScript's arrow functions are seemingly the same as regular functions, but there are some important differences you need to know.
|
||||
dateModified: 2021-10-17T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript's arrow functions might seem the same as regular functions on the surface, but they have some very important differences:
|
||||
|
||||
- Syntactical differences
|
||||
- `this` value (execution context)
|
||||
- Usage as methods
|
||||
- Usage as constructors
|
||||
- `arguments` binding
|
||||
|
||||
### Syntax
|
||||
|
||||
The first and most obvious difference between arrow functions and regular functions is their syntax. Not only do they look different, but arrow functions also provide an implicit return shorthand and allow parenthesis around a single argument to be omitted.
|
||||
|
||||
```js
|
||||
const square = a => a * a;
|
||||
|
||||
// Equivalent regular function
|
||||
function square(a) {
|
||||
return a * a;
|
||||
}
|
||||
```
|
||||
|
||||
### Execution context
|
||||
|
||||
Inside a regular function, execution context (i.e. the value of `this`) is dynamic. This means that the value of `this` depends on how the function was invoked (simple invocation, method invocation, indirect invocation or constructor invocation). On the other hand, an arrow function does not define its own execution context. This results in an arrow function's `this` being resolved lexically (i.e. the scope in which the arrow function was defined).
|
||||
|
||||
```js
|
||||
function logThis() {
|
||||
console.log(this);
|
||||
}
|
||||
document.addEventListener('click', logThis);
|
||||
// `this` refers to the document
|
||||
|
||||
const logThisArrow = () => {
|
||||
console.log(this);
|
||||
};
|
||||
document.addEventListener('click', logThisArrow);
|
||||
// `this` refers to the global object
|
||||
```
|
||||
|
||||
`Function.prototype.call()`, `Function.prototype.bind()` and `Function.prototype.apply()` do not work correctly with arrow functions either. Their purpose is to allow methods to execute within different scopes, but the `this` value of an arrow function cannot be changed, as it's resolved lexically.
|
||||
|
||||
```js
|
||||
function logThis() {
|
||||
console.log(this);
|
||||
}
|
||||
logThis.call(42); // Logs: 42
|
||||
|
||||
const logThisArrow = () => {
|
||||
console.log(this);
|
||||
};
|
||||
logThisArrow.call(42); // Logs the global object
|
||||
```
|
||||
|
||||
### Methods
|
||||
|
||||
Due to arrow functions not defining their own execution context, they're not well-suited for usage as methods. However, thanks to the [Class fields proposal](https://github.com/tc39/proposal-class-fields), arrow functions can be used as methods inside classes, if your environment supports it.
|
||||
|
||||
```js
|
||||
const obj = {
|
||||
x: 42,
|
||||
logThisX: function() {
|
||||
console.log(this.x, this);
|
||||
},
|
||||
logThisXArrow: () => {
|
||||
console.log(this.x, this);
|
||||
}
|
||||
};
|
||||
|
||||
obj.logThisX(); // Logs: 42, Object {...}
|
||||
obj.logThisXArrow(); // Logs: undefined, the global object
|
||||
```
|
||||
|
||||
### Constructors
|
||||
|
||||
Regular functions can be used as constructors, using the `new` keyword. Yet another consequence of the lexical resolution of `this` inside arrow functions is that they cannot be used as constructors. Using `new` with an arrow function results in a `TypeError`.
|
||||
|
||||
```js
|
||||
function Foo(bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
const a = new Foo(42); // Foo {bar: 42}
|
||||
|
||||
const Bar = foo => {
|
||||
this.foo = foo;
|
||||
};
|
||||
const b = new Bar(42); // TypeError: Bar is not a constructor
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
Another difference is the binding of the `arguments` object. Unlike regular functions, arrow functions don't have their own `arguments` object. A modern alternative that circumvents this limitation is the usage of rest parameters.
|
||||
|
||||
```js
|
||||
function sum() {
|
||||
return arguments[0] + arguments[1];
|
||||
};
|
||||
sum(4, 6); // 10
|
||||
|
||||
const arguments = [1, 2, 3];
|
||||
const sumArrow = () => {
|
||||
return arguments[0] + arguments[1];
|
||||
};
|
||||
sumArrow(4, 6); // 3 (resolves to 1 + 2)
|
||||
|
||||
const sumRest = (...arguments) => {
|
||||
return arguments[0] + arguments[1];
|
||||
}
|
||||
sumRest(4, 6); // 10
|
||||
```
|
||||
|
||||
### Other differences
|
||||
|
||||
Finally, there are a couple of other differences that are not as important, but worth mentioning. These include the lack of a `prototype` property in arrow functions, as well as the fact that the `yield` keyword may not be used in an arrow function's body. A consequence of the latter is that arrow functions cannot be used as generators.
|
||||
94
snippets/articles/s/javascript-async-array-loops.md
Normal file
94
snippets/articles/s/javascript-async-array-loops.md
Normal file
@ -0,0 +1,94 @@
|
||||
---
|
||||
title: Asynchronous array loops in JavaScript
|
||||
shortTitle: Asynchronous array loops
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [array,function,promise]
|
||||
author: chalarangelo
|
||||
cover: sunflowers
|
||||
excerpt: Asynchronously looping over arrays in JavaScript comes with a few caveats you should watch out for.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Asynchronous operations seem to trip up a lot of developers. This is especially true when combined with looping over arrays, as there are some caveats that come with each option available.
|
||||
|
||||
### For loops
|
||||
|
||||
Combining `async` with a `for` (or a `for...of`) loop is possibly the most straightforward option when performing asynchronous operations over array elements. Using `await` inside a `for` loop will cause the code to stop and wait for the asynchronous operation to complete before continuing. This means that all promises will be run sequentially.
|
||||
|
||||
```js
|
||||
const asyncUppercase = item =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(
|
||||
() => resolve(item.toUpperCase()),
|
||||
Math.floor(Math.random() * 1000)
|
||||
)
|
||||
);
|
||||
|
||||
const uppercaseItems = async () => {
|
||||
const items = ['a', 'b', 'c'];
|
||||
for (item of items) {
|
||||
const uppercaseItem = await asyncUppercase(item);
|
||||
console.log(uppercaseItem);
|
||||
}
|
||||
|
||||
console.log('Items processed');
|
||||
};
|
||||
|
||||
uppercaseItems();
|
||||
// LOGS: 'A', 'B', 'C', 'Items processed'
|
||||
```
|
||||
|
||||
### Promises
|
||||
|
||||
`Promise.all()` provides another option for asynchronous loops over arrays. The main difference with the previous one is that `Promise.all()` executes all asynchronous operations in parallel. This means that promises will execute out of order, which might be an issue in some cases. Most often than not, this is my preferred solution as it's quite uncommon to want promises to execute sequentially.
|
||||
|
||||
```js
|
||||
const asyncUppercase = item =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(
|
||||
() => resolve(item.toUpperCase()),
|
||||
Math.floor(Math.random() * 1000)
|
||||
)
|
||||
);
|
||||
|
||||
const uppercaseItems = () => {
|
||||
const items = ['a', 'b', 'c'];
|
||||
return Promise.all(
|
||||
items.map(async item => {
|
||||
const uppercaseItem = await asyncUppercase(item);
|
||||
console.log(uppercaseItem);
|
||||
})
|
||||
).then(() => {
|
||||
console.log('Items processed');
|
||||
});
|
||||
};
|
||||
// LOGS: 'A', 'C', 'B', 'Items processed'
|
||||
```
|
||||
|
||||
### Array methods
|
||||
|
||||
Unfortunately, array methods such as `Array.prototype.forEach()` do not work well with `async`/`await`. The only viable solution is to use `Promise.all()` as shown in the previous example. Using an `async` callback with `Array.prototype.forEach()` will result in the rest of the code executing and the asynchronous operations not being awaited for.
|
||||
|
||||
```js
|
||||
const asyncUppercase = item =>
|
||||
new Promise(resolve =>
|
||||
setTimeout(
|
||||
() => resolve(item.toUpperCase()),
|
||||
Math.floor(Math.random() * 1000)
|
||||
)
|
||||
);
|
||||
|
||||
const uppercaseItems = async () => {
|
||||
const items = ['a', 'b', 'c'];
|
||||
await items.forEach(async item => {
|
||||
const uppercaseItem = await asyncUppercase(item);
|
||||
console.log(uppercaseItem);
|
||||
});
|
||||
|
||||
console.log('Items processed');
|
||||
};
|
||||
|
||||
uppercaseItems();
|
||||
// LOGS: ''Items processed', 'B', 'A', 'C'
|
||||
```
|
||||
107
snippets/articles/s/javascript-await-timeout.md
Normal file
107
snippets/articles/s/javascript-await-timeout.md
Normal file
@ -0,0 +1,107 @@
|
||||
---
|
||||
title: How can I add a timeout to a promise in JavaScript?
|
||||
shortTitle: Promise timeout
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [promise,timeout,class]
|
||||
author: chalarangelo
|
||||
cover: walking
|
||||
excerpt: Oftentimes you might need to add a timeout to a promise in JavaScript. Learn how to do this and more in this short guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Many times in the past I've found myself needing to add a timeout to a promise in JavaScript. `setTimeout()` is not exactly a perfect tool for the job, but it's easy enough to wrap it into a promise:
|
||||
|
||||
```js
|
||||
const awaitTimeout = delay =>
|
||||
new Promise(resolve => setTimeout(resolve, delay));
|
||||
|
||||
awaitTimeout(300).then(() => console.log('Hi'));
|
||||
// Logs 'Hi' after 300ms
|
||||
|
||||
const f = async () => {
|
||||
await awaitTimeout(300);
|
||||
console.log('Hi'); // Logs 'Hi' after 300ms
|
||||
};
|
||||
```
|
||||
|
||||
There's nothing particularly complicated about this code sample, really. All it does is use the `Promise` constructor to wrap `setTimeout()` and resolve the promise after `delay` ms. This can be a useful tool when some code has to stall for a given amount of time.
|
||||
|
||||
In order to add a timeout to another promise, however, there are two additional needs this utility has to satisfy. The first one is allowing the timeout promise to reject instead of resolving when provided a reason as a second argument. The other one is to create a wrapper function which will add the timeout to the promise:
|
||||
|
||||
```js
|
||||
const awaitTimeout = (delay, reason) =>
|
||||
new Promise((resolve, reject) =>
|
||||
setTimeout(
|
||||
() => (reason === undefined ? resolve() : reject(reason)),
|
||||
delay
|
||||
)
|
||||
);
|
||||
|
||||
const wrapPromise = (promise, delay, reason) =>
|
||||
Promise.race([promise, awaitTimeout(delay, reason)]);
|
||||
|
||||
wrapPromise(fetch('https://cool.api.io/data.json'), 3000, {
|
||||
reason: 'Fetch timeout',
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data.message);
|
||||
})
|
||||
.catch(data => console.log(`Failed with reason: ${data.reason}`));
|
||||
// Will either log the `message` if `fetch` completes in under 3000ms
|
||||
// or log an error message with the reason 'Fetch timeout' otherwise
|
||||
```
|
||||
|
||||
As you can see in this example, `reason` is used to determine if the timeout promise will resolve or reject. `awaitTimeout()` is then used to create a new promise and passed to `Promise.race()` along with the other promise to create a timeout.
|
||||
|
||||
This implementation definitely works, but we can take it a couple steps further. An obvious improvement is the addition of a way to clear a timeout, which requires storing the ids of any active timeouts. This, along with the need to make this utility self-contained both make a great case for using a `class`:
|
||||
|
||||
```js
|
||||
class Timeout {
|
||||
constructor() {
|
||||
this.ids = [];
|
||||
}
|
||||
|
||||
set = (delay, reason) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const id = setTimeout(() => {
|
||||
if (reason === undefined) resolve();
|
||||
else reject(reason);
|
||||
this.clear(id);
|
||||
}, delay);
|
||||
this.ids.push(id);
|
||||
});
|
||||
|
||||
wrap = (promise, delay, reason) =>
|
||||
Promise.race([promise, this.set(delay, reason)]);
|
||||
|
||||
clear = (...ids) => {
|
||||
this.ids = this.ids.filter(id => {
|
||||
if (ids.includes(id)) {
|
||||
clearTimeout(id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const myFunc = async () => {
|
||||
const timeout = new Timeout();
|
||||
const timeout2 = new Timeout();
|
||||
timeout.set(6000).then(() => console.log('Hello'));
|
||||
timeout2.set(4000).then(() => console.log('Hi'));
|
||||
timeout
|
||||
.wrap(fetch('https://cool.api.io/data.json'), 3000, {
|
||||
reason: 'Fetch timeout',
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data.message);
|
||||
})
|
||||
.catch(data => console.log(`Failed with reason: ${data.reason}`))
|
||||
.finally(() => timeout.clear(...timeout.ids));
|
||||
};
|
||||
// Will either log the `message` or log a 'Fetch timeout' error after 3000ms
|
||||
// The 6000ms timeout will be cleared before firing, so 'Hello' won't be logged
|
||||
// The 4000ms timeout will not be cleared, so 'Hi' will be logged
|
||||
```
|
||||
98
snippets/articles/s/javascript-blank-value.md
Normal file
98
snippets/articles/s/javascript-blank-value.md
Normal file
@ -0,0 +1,98 @@
|
||||
---
|
||||
title: How can I check for a blank value in JavaScript?
|
||||
shortTitle: Value is blank
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [type]
|
||||
author: chalarangelo
|
||||
cover: workspace-with-speaker
|
||||
excerpt: JavaScript doesn't have a built-in way to check if a value is blank, but it's easy to create one.
|
||||
dateModified: 2022-09-25T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript doesn't have a built-in way to check if a value is blank, but it's easy to create one. Before we do, however, we should define the behavior of such a method. The inspiration for this comes from Rails' `blank?` method, but modified to fit JavaScript's needs.
|
||||
|
||||
First of all, any falsy values should be considered blank. These include `null`, `undefined`, `0`, `false`, `''`, and `NaN`.
|
||||
|
||||
```js
|
||||
const isFalsy = value => !value;
|
||||
|
||||
isFalsy(null); // true
|
||||
isFalsy(undefined); // true
|
||||
isFalsy(0); // true
|
||||
isFalsy(false); // true
|
||||
isFalsy(''); // true
|
||||
isFalsy(NaN); // true
|
||||
```
|
||||
|
||||
Secondly, empty arrays and objects should also be considered blank. This can be easily checked by using `Object.keys()` for both types of values.
|
||||
|
||||
```js
|
||||
const isEmptyCollection = value =>
|
||||
(Array.isArray(value) || value === Object(value)) &&
|
||||
!Object.keys(value).length;
|
||||
|
||||
isEmptyCollection([]); // true
|
||||
isEmptyCollection({}); // true
|
||||
```
|
||||
|
||||
In addition to the empty string (`''`), whitespace-only strings should be considered blank, too. A regular expression can be used to check for this.
|
||||
|
||||
```js
|
||||
const isWhitespaceString = value =>
|
||||
typeof value === 'string' && /^\s*$/.test(value);
|
||||
|
||||
isWhitespaceString(' '); // true
|
||||
isWhitespaceString('\t\n\r'); // true
|
||||
```
|
||||
|
||||
Finally, we can check for some commonly-used built-in objects. Invalid `Date` instances, as well as empty `Set` and `Map` instances should all be considered blank.
|
||||
|
||||
```js
|
||||
const isInvalidDate = value =>
|
||||
value instanceof Date && Number.isNaN(value.getTime());
|
||||
const isEmptySet = value => value instanceof Set && value.size === 0;
|
||||
const isEmptyMap = value => value instanceof Map && value.size === 0;
|
||||
|
||||
isInvalidDate(new Date('hello')); // true
|
||||
isEmptySet(new Set()); // true
|
||||
isEmptyMap(new Map()); // true
|
||||
```
|
||||
|
||||
Putting everything together, we can finally set up our `isBlank` method.
|
||||
|
||||
```js
|
||||
const isFalsy = value => !value;
|
||||
const isWhitespaceString = value =>
|
||||
typeof value === 'string' && /^\s*$/.test(value);
|
||||
const isEmptyCollection = value =>
|
||||
(Array.isArray(value) || value === Object(value)) &&
|
||||
!Object.keys(value).length;
|
||||
const isInvalidDate = value =>
|
||||
value instanceof Date && Number.isNaN(value.getTime());
|
||||
const isEmptySet = value => value instanceof Set && value.size === 0;
|
||||
const isEmptyMap = value => value instanceof Map && value.size === 0;
|
||||
|
||||
const isBlank = value => {
|
||||
if (isFalsy(value)) return true;
|
||||
if (isWhitespaceString(value)) return true;
|
||||
if (isEmptyCollection(value)) return true;
|
||||
if (isInvalidDate(value)) return true;
|
||||
if (isEmptySet(value)) return true;
|
||||
if (isEmptyMap(value)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
isBlank(null); // true
|
||||
isBlank(undefined); // true
|
||||
isBlank(0); // true
|
||||
isBlank(false); // true
|
||||
isBlank(''); // true
|
||||
isBlank(' \r\n '); // true
|
||||
isBlank(NaN); // true
|
||||
isBlank([]); // true
|
||||
isBlank({}); // true
|
||||
isBlank(new Date('hello')); // true
|
||||
isBlank(new Set()); // true
|
||||
isBlank(new Map()); // true
|
||||
```
|
||||
55
snippets/articles/s/javascript-boolean-function.md
Normal file
55
snippets/articles/s/javascript-boolean-function.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Where and how can I use the Boolean function in JavaScript?
|
||||
shortTitle: Boolean function use-cases
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [function,type]
|
||||
author: chalarangelo
|
||||
cover: rocky-lake
|
||||
excerpt: JavaScript's Boolean function can be used for truth-checking data among other things. Learn how to use it and level up your code today.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript's built-in [`Boolean`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) is one of those things I find myself suggesting in code reviews quite often as of late, so I thought I could share some tips about it with the world.
|
||||
|
||||
### Using Boolean for truth-checking
|
||||
|
||||
The `Boolean()` function is particularly useful when truth-checking data and probably significantly more readable than the double negation (`!!`) operation:
|
||||
|
||||
```js
|
||||
let x = 'some-value';
|
||||
|
||||
// This doesn't look too nice
|
||||
if (!!x) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// This is a lot more readable
|
||||
if (Boolean(x)) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
As you can see in the example above, it serves the exact same purpose and is pretty straightforward to use. Similarly, as `Boolean()` is itself a function returning a boolean value, you can use it for truth-checking collections, filtering arrays etc.:
|
||||
|
||||
```js
|
||||
const values = [0, 0, 2, 0, 3];
|
||||
// Use as the callback for Array.prototype.some()
|
||||
const hasValidValue = values.some(Boolean);
|
||||
// Use as the callback for Array.prototype.filter()
|
||||
const nonEmptyValues = values.filter(Boolean);
|
||||
```
|
||||
|
||||
### Handle Boolean objects with care
|
||||
|
||||
While the `Boolean()` function is pretty useful, you might run into some issues with the `Boolean` object and the `Boolean` constructor. The `Boolean` object is an object wrapper for a boolean value, but the tricky part is that, as an object, it's always truthy even if the contained value is `false`!
|
||||
|
||||
```js
|
||||
let x = new Boolean(false);
|
||||
|
||||
if (x) {
|
||||
// This code is executed
|
||||
}
|
||||
```
|
||||
|
||||
For example, the above code will consider `x` truthy, even if it clearly contains `false` as its value. This might some confusing, but you can easily avoid it if you generally avoid using `Boolean` objects and the `Boolean` constructor, unless you are entirely certain that you need to use it for some reason. I cannot find any scenarios where I would need to use this, to be honest, so it might not be all that common to begin with.
|
||||
78
snippets/articles/s/javascript-boolean-trap.md
Normal file
78
snippets/articles/s/javascript-boolean-trap.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Boolean traps and how to avoid them
|
||||
shortTitle: Boolean traps
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function,type,boolean]
|
||||
author: chalarangelo
|
||||
cover: lighthouse
|
||||
excerpt: Boolean traps can cause readability and maintainability issues in your code. Learn what they are, how to spot and fix them in this article.
|
||||
dateModified: 2021-07-11T05:00:00-04:00
|
||||
---
|
||||
|
||||
I recently came across the concept of **Boolean traps** and it instantly resonated with me due to the volume of Google searches I've performed because of it. In this article, I'll try to explain what it is, why it's somewhat of an anti-pattern, how to spot it in your code and ways to refactor around it.
|
||||
|
||||
### Boolean trap - What's in a name?
|
||||
|
||||
While the name **Boolean trap** might be unfamiliar to some, I'm pretty certain the concept it represents isn't. The simplest form of a boolean trap is a function that takes a boolean argument.
|
||||
|
||||
The **trap** in the name might throw you off if you stick to this definition, but it serves its purpose. Let's look at two simple examples to get a better grasp of things:
|
||||
|
||||
```js
|
||||
// What does `false` stand for?
|
||||
results.reload(false);
|
||||
|
||||
// What does `true` stand for?
|
||||
const user = new User(true);
|
||||
```
|
||||
|
||||
The first example suffers in terms of readability due to an obvious contradiction. A function named `reload` expects a boolean argument. `false` in this context must surely mean that no reloading should happen. Except that might not be the case. This argument might be anything from performing the operation immediately (i.e. `immediate`) to some side effect such as animation to even the no-op we suspected. I've stumbled upon similar cases of ambiguous arguments in many libraries in the past.
|
||||
|
||||
The second example is also hard to decipher without looking at some documentation. Here, the constructor expects a boolean argument that might mean literally anything. Would you have guessed that it's a flag indicating if the user should have administrative privileges? Probably not. The point is there is no way to tell what this argument means without looking at the documentation.
|
||||
|
||||
### Red flag or red herring?
|
||||
|
||||
At this point, you might be asking yourself why this is actually bad. Reading through the documentation is expected. After all, that's what it's there for. Except this starts to become a waste of time on return visits. If you're working with a library and look up a boolean argument over and over because it's not obvious, it becomes a bit of a hassle.
|
||||
|
||||
Moreover, code is read many times by many people. The author might be familiar with the library and API and have no need for documentation altogether. But the next person who comes along will have to visit the same documentation and figure it out for themselves. That harms readability and wastes tons of time in the long run, due to a single boolean argument.
|
||||
|
||||
A bonus point here is the potential of further reducing readability by increasing cognitive load. There are valid use-cases for boolean arguments, but there are situations where the name of the function, being in itself a negative, with a negative (i.e. falsy) value makes the reader stop and pause to parse what's happening. For example:
|
||||
|
||||
```js
|
||||
// Real quick: Is this valid or invalid?
|
||||
input.setInvalid(false);
|
||||
```
|
||||
|
||||
### Not all booleans will trap you
|
||||
|
||||
As with most things, there is no universal best practice here. Even though I often find boolean arguments hard to read, I understand there are cases where you might want to use them.
|
||||
|
||||
```js
|
||||
// It should be obvious that `true` makes the element disabled
|
||||
element.setProperty('disabled', true);
|
||||
// Could be equivalent to `element.disabled = true;`
|
||||
```
|
||||
|
||||
In this example, it's pretty straightforward what `true` does. Notice that the double negative from before might still make this slightly hard to read, but it makes sense to use a boolean in this context. Why? Well, it's essentially a setter function and passing the actual value of the property isn't such a bad idea.
|
||||
|
||||
### Mitigating the problem
|
||||
|
||||
We've already established what a boolean trap is and why it's bad. But how do we fix it? Even if we can spot the anti-pattern, it might be hard to change it before it affects a lot of code and developers. Some languages support named arguments and that usually solves the problem quite easily. JavaScript on the other hand doesn't, but there's always the option to pass an options object.
|
||||
|
||||
Let's take a look at the two examples from before and how that would work:
|
||||
|
||||
```js
|
||||
// Ok, so reload but not immediately
|
||||
results.reload({ immediate: false });
|
||||
|
||||
// Create a new user without administrator privileges
|
||||
const user = new User({ isAdministrator: false });
|
||||
```
|
||||
|
||||
Without huge changes to the API, we could have avoided the boolean trap altogether. All we needed was a plain JavaScript object. This also has the added benefit of making the function more extensible in the future. Objects are quite flexible, so if we want to add a second boolean (e.g. `animate` for `reload` or `active` for `User`), we need only add a key to the object.
|
||||
|
||||
On a side note, while comments seem an appropriate solution, they will inevitably become stale and out of touch with the API. It's best to leave this kind of information to the official documentation or source code, instead.
|
||||
|
||||
### Conclusion
|
||||
|
||||
To summarize, boolean arguments in functions can be the source of a lot of wasted time and the cause for low code readability if used incorrectly. They're sometimes considered an anti-pattern as they increase cognitive load and reduce maintainability of shared code. Luckily, they're very easy to spot and fix using plain JavaScript option objects.
|
||||
60
snippets/articles/s/javascript-callbacks.md
Normal file
60
snippets/articles/s/javascript-callbacks.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: A cautionary tale about JavaScript callbacks
|
||||
shortTitle: Callback pitfalls
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: rabbit-call
|
||||
excerpt: JavaScript callbacks are especially tricky when you're not careful. Take a deeper dive into potential issues and how to avoid them.
|
||||
dateModified: 2021-07-01T05:00:00-04:00
|
||||
---
|
||||
|
||||
A piece of advice I've found myself repeating often as of late is this:
|
||||
|
||||
> When working with callbacks in JavaScript, it's better to err on the side of caution and be more verbose.
|
||||
|
||||
Mind you, I'm mostly repeating this to myself, but I thought it's pretty valuable to share it with the world. The reason is the myriads of issues I've stumbled upon due to seemingly harmless functions used as callbacks. And that's not even the worst part! They usually slip under the radar when you look at the code and might need a second or third look to identify as the culprit behind the issue.
|
||||
|
||||
The most common bug I've encountered is one you might be familiar with: `parseInt()` used as a callback, especially in combination with `Array.prototype.map()`. Consider the following code:
|
||||
|
||||
```js
|
||||
const nums = ['1', '5', '10', '21'];
|
||||
nums.map(parseInt); // [1, NaN, 2, 7]
|
||||
```
|
||||
|
||||
Did you spot the problem? `parseInt()` takes up to two arguments: the `string` to be parsed and an optional `radix` parameter. `Array.prototype.map()` passes three parameters to the callback: the `value`, `index` and `array`. It should be obvious from this breakdown that the index of each element being passed as the radix parameter results in this strange problem.
|
||||
|
||||
The solution is pretty straightforward, too. Creating a function to pass the arguments we want to `parseInt()` would fix this and remove a nasty bug somewhere down the line:
|
||||
|
||||
```js
|
||||
const nums = ['1', '5', '10', '21'];
|
||||
nums.map(num => parseInt(num, 10)); // [1, 5, 10, 21]
|
||||
```
|
||||
|
||||
A corollary to this is that when working with third-party libraries and APIs, it's always best to create a function to pass the data to whatever part of said API is being used rather than using it directly as a callback. The reason for this is that, even though the library or API might not expect any additional arguments now, this might change in a later version. Not accounting for this could be a major risk when updating to a new version of a library marked as having no breaking changes. Take a look at the following example:
|
||||
|
||||
```js
|
||||
// third-party-lib@v1.0.0
|
||||
const parseData = path => {
|
||||
const fileData = fs.readFileSync(path);
|
||||
return fileData || '';
|
||||
};
|
||||
|
||||
const importantFiles = ['id-card.txt', 'bank-number.txt'];
|
||||
importantFiles.map(parseData); // Works fine
|
||||
|
||||
// third-party-lib@v1.1.0 - No breaking changes!
|
||||
const parseData = (path, purge) => {
|
||||
const fileData = fs.readFileSync(path);
|
||||
if (purge) fs.unlinkSync(path);
|
||||
return fileData || '';
|
||||
};
|
||||
|
||||
const importantFiles = ['id-card.txt', 'bank-number.txt'];
|
||||
importantFiles.map(parseData); // 'bank-number.txt'` has been deleted
|
||||
```
|
||||
|
||||
The example above, while a bit unlikely, demonstrates a case where a simple index from `Array.prototype.map()` could wreak havoc on the entire filesystem due to a harmless version bump of an external dependency. This is the kind of bug that is hard to track down and causes a ton of headaches when debugging as you struggle to understand how a version bump without breaking changes could cause this.
|
||||
|
||||
To summarize, be extra careful when working with callbacks. If a function is not explicitly designed to be a callback, if you are using third party code, even if you are uncertain just add a function to pass down the arguments. It will save you time in the long run at the cost of your code looking a tiny bit more verbose. I think it's a worthwhile tradeoff.
|
||||
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: How does JavaScript's prototypal inheritance differ from classical inheritance?
|
||||
shortTitle: Prototypal vs classical inheritance
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object,class]
|
||||
author: chalarangelo
|
||||
cover: last-light
|
||||
excerpt: Understanding the difference between these two object-oriented programming paradigms is key to taking your skills to the next level.
|
||||
dateModified: 2021-11-21T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Object-oriented programming
|
||||
|
||||
Both classical and prototypal inheritance are **object-oriented programming paradigms**. Objects in object-oriented programming are abstractions that encapsulate the properties of an entity. This is known as abstraction.
|
||||
|
||||
When dealing with multiple levels of abstraction, each level is more general or more specific. The more general abstraction of a more specific abstraction is called a generalization.
|
||||
|
||||
As mentioned previously, objects are abstraction of entities. We use either classes (classical inheritance) or prototypes (prototypal inheritance) to create generalizations of these objects. Generalizations are created by inheritance.
|
||||
|
||||
Consider an example:
|
||||
|
||||
- We have two objects representing two pets: Max the dog and Claire the cat. Let's call them `max` and `claire` respectively.
|
||||
- All dogs have common characteristics. Therefore we can create an abstraction, `Dog`, which encapsulates their common characteristics. We can use inheritance to pass characteristics from `Dog` to `max`.
|
||||
- The same applies for cats, allowing us to create an abstraction, `Cat`. Similarly, `claire` will inherit characteristics from `Cat`.
|
||||
- Cats and dogs share some common characteristics. We can create a generalization, `Animal`, to encapsulate those characteristics. `Dog` and `Cat` inherit these common characteristics from `Animal`.
|
||||
|
||||
### Classical inheritance
|
||||
|
||||
In classical object-oriented programming, there are two types of abstractions: objects and classes. An object is an abstractions of an entity, while a class is either an abstraction of an object or another class.
|
||||
|
||||
If we were to model the previous example using classical inheritance, it would look something like this:
|
||||
|
||||
```js
|
||||
class Animal { }
|
||||
|
||||
class Dog extends Animal { }
|
||||
class Cat extends Animal { }
|
||||
|
||||
const max = new Dog();
|
||||
max.name = 'Max';
|
||||
|
||||
const claire = new Cat();
|
||||
claire.name = 'Claire';
|
||||
```
|
||||
|
||||
### Prototypal inheritance
|
||||
|
||||
In prototypal object-oriented programming, there's only one type of abstraction: objects. Objects are either abstractions of entities or other objects, in which case they're called prototypes. Hence a prototype is a generalization.
|
||||
|
||||
Objects can be created out of nothing or from another object, which in turn becomes the prototype of the newly created object.
|
||||
|
||||
If we were to model the previous example using prototypal inheritance, it would look something like this:
|
||||
|
||||
```js
|
||||
const animal = {};
|
||||
|
||||
const dog = Object.create(animal);
|
||||
const cat = Object.create(animal);
|
||||
|
||||
const max = Object.create(dog);
|
||||
max.name = 'Max';
|
||||
|
||||
const claire = Object.create(cat);
|
||||
claire.name = 'Claire';
|
||||
```
|
||||
55
snippets/articles/s/javascript-closures.md
Normal file
55
snippets/articles/s/javascript-closures.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: What are JavaScript closures?
|
||||
shortTitle: Closures introduction
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [function,closure]
|
||||
author: chalarangelo
|
||||
cover: cherry-trees
|
||||
excerpt: Learn and understand closures, a core concept in JavaScript programming, and level up your code.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Closures are a JavaScript concept that comes up quite a lot, especially during interviews. While they sound confusing, they are not all that complicated and you have probably already used them in your code regardless of your expertise level. Let's start with what a closure is:
|
||||
|
||||
> You have a closure when a function accesses variables defined outside of it.
|
||||
|
||||
That doesn't sound too complicated. Let's see an example:
|
||||
|
||||
```js
|
||||
const items = [
|
||||
{ id: 1, title: 'First' },
|
||||
{ id: 2, title: 'Second' },
|
||||
{ id: 3, title: 'Final' }
|
||||
];
|
||||
const matcher = /^F/;
|
||||
const filteringFn = x => matcher.test(x.title);
|
||||
items.filter(filteringFn); // [{ id: 1, title: 'First' }, { id: 3, title: 'Final' }]
|
||||
```
|
||||
|
||||
The above example defines some data to play around with, a regular expression to use for matching and a function, `filteringFn` that is then used in `Array.prototype.filter()` as the filtering function. If you look closely at `filteringFn`, you'll notice it uses a variable defined outside of, namely `matcher`. This is a closure.
|
||||
|
||||
Let's look at another, more complex example:
|
||||
|
||||
```js
|
||||
const initCounter = (start = 0) => {
|
||||
let value = start;
|
||||
return {
|
||||
get: () => value,
|
||||
increment: () => ++value,
|
||||
decrement: () => --value,
|
||||
reset: () => value = start
|
||||
};
|
||||
}
|
||||
|
||||
const counter = initCounter(5);
|
||||
counter.get(); // 5
|
||||
counter.increment(); // 6
|
||||
counter.increment(); // 7
|
||||
counter.decrement(); // 6
|
||||
counter.reset(); // 5
|
||||
```
|
||||
|
||||
In this example, we define a function, `initCounter`, that returns an object, whose properties are functions. All of the returned object's properties use closures to manipulate `initCounter`'s `value` variable in some way. The obvious benefit of this approach is that if you want to define multiple counters via `initCounter`, you do not need to create multiple `value` instances all over the place, but they are safely encapsulated by the returned object, using closures.
|
||||
|
||||
Using closures, as shown in the example above, can help make your code more usable and maintainable, while allowing you to separate concerns and create abstractions as necessary.
|
||||
67
snippets/articles/s/javascript-copy-array.md
Normal file
67
snippets/articles/s/javascript-copy-array.md
Normal file
@ -0,0 +1,67 @@
|
||||
---
|
||||
title: How can I clone an array in JavaScript?
|
||||
shortTitle: Clone an array
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array]
|
||||
author: chalarangelo
|
||||
cover: colorful-plastic
|
||||
excerpt: Pick up a few new tricks which you can use to clone arrays in JavaScript.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript provides quite a few ways to clone an array, most of which are pretty similar in terms of performance and results. Here's a quick rundown of some of the available options.
|
||||
|
||||
#### The spread operator
|
||||
|
||||
ES6 introduced the spread operator (`...`), which provides probably the easiest and most common way to create a shallow clone of an array.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = [...x];
|
||||
```
|
||||
|
||||
#### Array.from()
|
||||
|
||||
`Array.from()` has a very powerful API that can be used for many different things, including creating a copy of an array.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = Array.from(x);
|
||||
```
|
||||
|
||||
#### Array.prototype.slice()
|
||||
|
||||
Similarly to the spread operator, `Array.prototype.slice()` can be used to create a shallow copy of an array.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = x.slice();
|
||||
```
|
||||
|
||||
#### Array.prototype.map()
|
||||
|
||||
Looking into one of the more unorthodox options, `Array.prototype.map()` can be used to map each element of an array to itself to create a new array.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = x.map(i => i);
|
||||
```
|
||||
|
||||
#### Array.prototype.filter()
|
||||
|
||||
Similarly, `Array.prototype.filter()` can be used to return `true` for each and every element, resulting in a new array with all of the original array's elements.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = x.filter(() => true);
|
||||
```
|
||||
|
||||
#### Object.assign()
|
||||
|
||||
Finally, `Object.assign()` can be used in the exact same way as it's used to create a clone of an object, but for an array instead.
|
||||
|
||||
```js
|
||||
let x = [1, 2, 3, 4];
|
||||
let y = Object.assign([], x);
|
||||
```
|
||||
43
snippets/articles/s/javascript-date-comparison.md
Normal file
43
snippets/articles/s/javascript-date-comparison.md
Normal file
@ -0,0 +1,43 @@
|
||||
---
|
||||
title: How do I compare two dates in JavaScript?
|
||||
shortTitle: Date comparison
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [date,comparison]
|
||||
author: chalarangelo
|
||||
cover: pineapple-at-work
|
||||
excerpt: Learn how you can compare two dates in JavaScript using various different techniques.
|
||||
dateModified: 2022-01-16T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Equality comparison
|
||||
|
||||
Comparing two dates in JavaScript using the loose or strict equality operators (`==` or `===`) is not recommended for most cases. Equality operators compare the `Date` object references, resulting in `false`, even if the date values are the same:
|
||||
|
||||
```js
|
||||
const a = new Date(2022, 01, 10);
|
||||
const b = new Date(2022, 01, 10);
|
||||
|
||||
a === b; // false
|
||||
```
|
||||
|
||||
### Date.prototype.getTime()
|
||||
|
||||
One way to compare two `Date` values is using the `Date.prototype.getTime()` method. This method returns a number indicating the number of milliseconds elapsed since the Unix Epoch:
|
||||
|
||||
```js
|
||||
const a = new Date(2022, 01, 10);
|
||||
const b = new Date(2022, 01, 10);
|
||||
|
||||
a.getTime() === b.getTime(); // true
|
||||
```
|
||||
|
||||
### Other methods
|
||||
|
||||
As mentioned before, `Date.prototype.getTime()` is one way to compare two `Date` values. It's not the only one way to compare them. Other options are the following:
|
||||
|
||||
- `Date.prototype.toISOString()`
|
||||
- `Date.prototype.toUTCString()`
|
||||
- `Date.prototype.toLocaleDateString()` provided you use the same locale
|
||||
|
||||
All of these methods produce consistent results, but we still recommend `Date.prototype.getTime()` due to its simplicity.
|
||||
81
snippets/articles/s/javascript-deep-freeze-object.md
Normal file
81
snippets/articles/s/javascript-deep-freeze-object.md
Normal file
@ -0,0 +1,81 @@
|
||||
---
|
||||
title: How can I deep freeze an object in JavaScript?
|
||||
shortTitle: Deep freeze object
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object]
|
||||
author: chalarangelo
|
||||
cover: frozen-globe
|
||||
excerpt: Learn how mutability works in JavaScript, its applications to objects and how you can properly freeze them to make them constant.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
Objects in JavaScript are mutable, regardless if you define them as `const` variables or not. In fact, using `const` when defining an object only prevents the variable from being reassigned. However, you can reassign the properties of a `const` object or array, like this:
|
||||
|
||||
```js
|
||||
const myObj = { a: 10, b: 20, c: 30 };
|
||||
myObj.a = 12; // { a: 12, b: 20, c: 30 };
|
||||
|
||||
const myArr = [15, 25, 35];
|
||||
myArr[1] = 28; // [15, 28, 35];
|
||||
```
|
||||
|
||||
To make an object immutable, we can utilize `Object.freeze()`, which will prevent the addition of new properties and prevent deletion and changes to existing properties to some extent. However, while `Object.freeze()` provides somewhat of a solution, it only mitigates the problem to the next nesting level, as in reality it performs a shallow freeze. This means that properties that are objects or arrays can still be mutated:
|
||||
|
||||
```js
|
||||
const myObj = {
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
c: [0, 1, 2],
|
||||
d: { e: 1, f: 2 }
|
||||
};
|
||||
Object.freeze(myObj);
|
||||
|
||||
myObj.a = 10;
|
||||
myObj.b = 'hi';
|
||||
myObj.c[1] = 4;
|
||||
myObj.d.e = 0;
|
||||
/*
|
||||
myObj = {
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
c: [0, 4, 2],
|
||||
d: { e: 0, f: 2 }
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
As you can see, `Object.freeze()` is a step in the right direction, but only shallow freezes the object. To solve the issue we can use recursion, checking if each property is itself an object and, if `Object.isFrozen()` is `false`, apply `Object.freeze()` to it:
|
||||
|
||||
```js
|
||||
const myObj = {
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
c: [0, 1, 2],
|
||||
d: { e: 1, f: 2 }
|
||||
};
|
||||
|
||||
const deepFreeze = obj => {
|
||||
Object.keys(obj).forEach(prop => {
|
||||
if (typeof obj[prop] === 'object' && !Object.isFrozen(obj[prop])) deepFreeze(obj[prop]);
|
||||
});
|
||||
return Object.freeze(obj);
|
||||
};
|
||||
deepFreeze(myObj);
|
||||
|
||||
myObj.a = 10;
|
||||
myObj.b = 'hi';
|
||||
myObj.c[1] = 4;
|
||||
myObj.d.e = 0;
|
||||
|
||||
/*
|
||||
myObj = {
|
||||
a: 1,
|
||||
b: 'hello',
|
||||
c: [0, 1, 2],
|
||||
d: { e: 1, f: 2 }
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
In the above example, we apply the techniques we described previously to ensure that the given object is deeply frozen. You can view the complete code, along with more examples in the [deepFreeze](/js/s/deep-freeze) snippet.
|
||||
71
snippets/articles/s/javascript-destructuring-assignment.md
Normal file
71
snippets/articles/s/javascript-destructuring-assignment.md
Normal file
@ -0,0 +1,71 @@
|
||||
---
|
||||
title: Where and how can I use the destructuring assignment syntax in JavaScript?
|
||||
shortTitle: Destructuring assignment introduction
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array,object]
|
||||
author: chalarangelo
|
||||
cover: building-blocks
|
||||
excerpt: Learn the basics of the destructuring assignment syntax in JavaScript ES6 and improve your code with this easy guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
The destructuring assignment syntax, first introduced in JavaScript ES6, allows the **unpacking of values** from arrays and objects into distinct variables. While it might seem intimidating at first, it's actually quite easy to learn and use. Let's break it down into easier to understand cases.
|
||||
|
||||
### Array destructuring
|
||||
|
||||
Destructuring an array is very straightforward. All you have to do is declare a variable for each value in the sequence. You can define fewer variables than there are indexes in the array (i.e. if you only want to unpack the first few values), skip some indexes or even use the rest pattern to unpack any remaining values into a new array.
|
||||
|
||||
```js
|
||||
const nums = [ 3, 6, 9, 12, 15 ];
|
||||
const [
|
||||
k, // k = 3
|
||||
l, // l = 6
|
||||
, // Skip a value (9)
|
||||
...n // n = [12, 15]
|
||||
] = nums;
|
||||
```
|
||||
|
||||
### Object destructuring
|
||||
|
||||
Object destructuring is pretty similar to array destructuring, the main difference being that you can reference each key in the object by name, creating a variable with the same name. Additionally, you can also unpack a key to a new variable name, unpack only the keys you need and use the rest pattern to unpack remaining keys into a new object.
|
||||
|
||||
```js
|
||||
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
||||
const {
|
||||
a, // a = 1
|
||||
c: d, // d = 3
|
||||
...rest // rest = { b: 2, d: 4 }
|
||||
} = obj;
|
||||
```
|
||||
|
||||
### Nested destructuring
|
||||
|
||||
Nested objects and arrays can be unpacked by following the same rules. The difference here is that you can unpack nested keys or values directly to variables without having to store the parent object in a variable itself.
|
||||
|
||||
```js
|
||||
const nested = { a: { b: 1, c: 2 }, d: [1, 2]};
|
||||
const {
|
||||
a: {
|
||||
b: f, // f = 1
|
||||
...g // g = { c: 2 }
|
||||
},
|
||||
...h // h = { d: [1, 2]}
|
||||
} = nested;
|
||||
```
|
||||
|
||||
### Advanced destructuring
|
||||
|
||||
As arrays act much like objects, it's possible to use the destructuring assignment syntax to get specific values from an array by using the index as a key in an object destructuring assignment. Additionally, using this method, you can get other properties of the array (e.g. its `length`). Finally, you can also define default values for variables in a destructuring assignment, in case the unpacked value is `undefined`.
|
||||
|
||||
```js
|
||||
const arr = [ 5, 'b', 4, 'd', 'e', 'f', 2 ];
|
||||
const {
|
||||
6: x, // x = 2
|
||||
0: y, // y = 5
|
||||
2: z, // z = 4
|
||||
length: count, // count = 7
|
||||
name = 'array', // name = 'array' (not present in arr)
|
||||
...restData // restData = { '1': 'b', '3': 'd', '4': 'e', '5': 'f' }
|
||||
} = arr;
|
||||
```
|
||||
49
snippets/articles/s/javascript-empty-array.md
Normal file
49
snippets/articles/s/javascript-empty-array.md
Normal file
@ -0,0 +1,49 @@
|
||||
---
|
||||
title: How do I empty an array in JavaScript?
|
||||
shortTitle: Empty an array
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array]
|
||||
author: chalarangelo
|
||||
cover: coconuts
|
||||
excerpt: You can use a lot of different techniques to empty an array in JavaScript. See which ones best suits your needs with this quick guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
When working with JavaScript arrays, a pretty common question is how does one empty an array and remove all its elements. As it turns out, there are a few ways you can go about this, each one with its pros and cons.
|
||||
|
||||
### Assign it to an empty array
|
||||
|
||||
You can assign your variable to an empty array (`[]`) in order to clear it. While this option is rather fast, you should be mindful of references to the original array, as they will remain unchanged. Moreover, it doesn't work for arrays declared as `const`.
|
||||
|
||||
```js
|
||||
let a = [1, 2, 3, 4];
|
||||
a = [];
|
||||
```
|
||||
|
||||
### Set its length to 0
|
||||
|
||||
A better option is to set the `length` of the array to `0`. This option is also pretty fast and has the additional benefit of working for `const` variables.
|
||||
|
||||
```js
|
||||
let a = [1, 2, 3, 4];
|
||||
a.length = 0;
|
||||
```
|
||||
|
||||
### Use Array.prototype.splice()
|
||||
|
||||
`Array.prototype.splice()` can also be a useful alternative when trying to empty an array. While it has no other downsides compared to the previous method, it doesn't seem to perform as well, so that might be something to consider.
|
||||
|
||||
```js
|
||||
let a = [1, 2, 3, 4];
|
||||
a.splice(0, a.length);
|
||||
```
|
||||
|
||||
### Use Array.prototype.pop()
|
||||
|
||||
Last but not least, using `Array.prototype.pop()` is another, more old-fashioned option. It's generally more verbose and less performant, so I'd rather use one of the previous methods instead.
|
||||
|
||||
```js
|
||||
let a = [1, 2, 3, 4];
|
||||
while (a.length) a.pop();
|
||||
```
|
||||
@ -0,0 +1,41 @@
|
||||
---
|
||||
title: What is the difference between encodeURI() and encodeURIComponent() in JavaScript?
|
||||
shortTitle: encodeURI() vs encodeURIComponent()
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser]
|
||||
author: chalarangelo
|
||||
cover: laptop-view
|
||||
excerpt: JavaScript provides two methods for encoding characters to URL-safe strings. Do you know when to use each one?
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### encodeURIComponent()
|
||||
|
||||
The [`encodeURIComponent()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) function encodes everything in the given string, except `A-Z a-z 0-9 - _ . ! ~ * ' ( )`. You should use this function if the string you are encoding is only part of a URL.
|
||||
|
||||
```js
|
||||
const partOfURL = 'my-page#with,speci@l&/"characters"?';
|
||||
const fullURL = 'https://my-website.com/my-page?query="a%b"&user=1';
|
||||
|
||||
encodeURIComponent(partOfURL); // Good, escapes special characters
|
||||
// 'my-page%23with%2Cspeci%40l%26%2F%22characters%22%3F'
|
||||
|
||||
encodeURIComponent(fullURL); // Bad, encoded URL is not valid
|
||||
// 'https%3A%2F%2Fmy-website.com%2Fmy-page%3Fquery%3D%22a%25b%22%26user%3D1'
|
||||
```
|
||||
|
||||
### encodeURI()
|
||||
|
||||
The [`encodeURI()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) function encodes everything in the given string, except `A-Z a-z 0-9 ; , / ? : @ & = + $ - _ . ! ~ * ' ( ) #`. You should use this function if the string you are encoding is a full URL.
|
||||
|
||||
```js
|
||||
const partOfURL = 'my-page#with,speci@l&/"characters"?';
|
||||
const fullURL = 'https://my-website.com/my-page?query="a%b"&user=1';
|
||||
|
||||
encodeURI(partOfURL); // Bad, does not escape all special characters
|
||||
// 'my-page#with,speci@l&/%22characters%22?'
|
||||
|
||||
encodeURI(fullURL); // Good, encoded URL is valid
|
||||
// 'https://my-website.com/my-page?query=%22this%25thing%22&user=1'
|
||||
```
|
||||
60
snippets/articles/s/javascript-enum.md
Normal file
60
snippets/articles/s/javascript-enum.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
title: How can I define an enum in JavaScript?
|
||||
shortTitle: Enum implementation
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object,class,symbol,generator]
|
||||
author: chalarangelo
|
||||
cover: book-chair
|
||||
excerpt: Enums are part of TypeScript, but what about defining enums in plain old JavaScript? Here are a few way you can do that.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
TypeScript's enums are a very convenient feature present in many other languages. JavaScript, however, does not have a similar concept at this time. But what JavaScript lacks in syntactic sugar it makes up for in terms of flexibility.
|
||||
|
||||
The easiest way to define an enum would be to use `Object.freeze()` in combination with a plain object. This will ensure that the enum object cannot be mutated.
|
||||
|
||||
```js
|
||||
const daysEnum = Object.freeze({
|
||||
monday: 0,
|
||||
tuesday: 1,
|
||||
wednesday: 2,
|
||||
thursday: 3,
|
||||
friday: 4,
|
||||
saturday: 5,
|
||||
sunday: 6
|
||||
});
|
||||
```
|
||||
|
||||
Taking this one step further, one could extract the logic into a function with a variable number of arguments and producing a frozen object. There is very little benefit to this technique, so a better alternative would be to create a simple `class`. After all, enums are more common in object-oriented programming languages, so this sounds like a great fit.
|
||||
|
||||
An `Enum` class would only require a `constructor` with a variable number of arguments. Its job is to add each key to the enum object and freeze the newly created instance. A potential enhancement would be to provide access to the enum values as strings. Obviously, this can be accomplished using `Object.keys()`, but a named method could result in a conflict with one of the enum's values, let alone the method polluting the result.
|
||||
|
||||
Using an ES6 symbol is the obvious solution here, as it will not pollute the result of `Object.keys()` and it will never conflict with any values in the enum. Taking this one step further, `Symbol.iterator` would be a great choice as it would allow for the enum to be considered iterable and make it even more useful. Putting all of this together, here's my `Enum` class and how to use it:
|
||||
|
||||
```js
|
||||
class Enum {
|
||||
constructor(...keys) {
|
||||
keys.forEach((key, i) => {
|
||||
this[key] = i;
|
||||
});
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
*[Symbol.iterator]() {
|
||||
for (let key of Object.keys(this)) yield key;
|
||||
}
|
||||
}
|
||||
|
||||
const daysEnum = new Enum(
|
||||
'monday',
|
||||
'tuesday',
|
||||
'wednesday',
|
||||
'thursday',
|
||||
'friday',
|
||||
'saturday',
|
||||
'sunday'
|
||||
);
|
||||
|
||||
const days = [...daysEnum]; // Array of the enum values as strings
|
||||
```
|
||||
55
snippets/articles/s/javascript-equality.md
Normal file
55
snippets/articles/s/javascript-equality.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: What is the difference between JavaScript's equality operators?
|
||||
shortTitle: JavaScript equality operators
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [type,comparison]
|
||||
author: chalarangelo
|
||||
cover: beach-pineapple
|
||||
excerpt: Learn all you need to know about the differences between JavaScript's double equals and triple equals operators.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript provides two equality operators used for comparisons:
|
||||
|
||||
- The double equals (`==`), also known as the loose equality operator
|
||||
- The triple equals (`===`), also known as the strict equality operator
|
||||
|
||||
The key difference between the two is that the triple equals (`===`) operator compares both type and value, whereas the double equals (`==`) operator uses type coercion so that both operands are of the same type, then compares only the resulting values.
|
||||
|
||||
Here are some examples to clear up any confusion:
|
||||
|
||||
```js
|
||||
const num = 0;
|
||||
const str = '0';
|
||||
const obj = new String(0);
|
||||
const bool = false;
|
||||
const undef = undefined;
|
||||
const nil = null;
|
||||
|
||||
console.dir([
|
||||
num == str, // 0 == 0, true
|
||||
num == bool, // 0 == 0, true
|
||||
str == obj, // '0' == '0', true
|
||||
obj == num, // 0 == 0, true
|
||||
bool == str, // 0 == 0, true
|
||||
bool == obj, // 0 == 0, true
|
||||
bool == nil, // false
|
||||
undef == nil, // true
|
||||
undef == bool, // false
|
||||
]);
|
||||
|
||||
console.dir([
|
||||
num === str, // types don't match, false
|
||||
num === bool, // types don't match, false
|
||||
str === obj, // types don't match, false
|
||||
obj === num, // types don't match, false
|
||||
bool === str, // types don't match, false
|
||||
bool === obj, // types don't match, false
|
||||
bool === nil, // types don't match, false
|
||||
undef === nil, // types don't match, false
|
||||
undef === bool, // types don't match, false
|
||||
]);
|
||||
```
|
||||
|
||||
As you can see from the examples above, using the triple equals (`===`) operator is far more predictable and intuitive than the double equals (`==`) operator. Therefore, we recommend you use the triple equals (`===`) operator for most cases, unless you are entirely certain you want type coercion to be applied to the comparison's operands.
|
||||
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Understanding event bubbling, capturing and delegation in JavaScript
|
||||
shortTitle: Event bubbling, capturing and delegation
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [browser,event]
|
||||
author: chalarangelo
|
||||
cover: fishermen
|
||||
excerpt: Understand how events work in JavaScript and learn when to use event bubbling, event capturing and event delegation with this short guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Event bubbling
|
||||
|
||||
Bubbling means that the event propagates from the target element (i.e. the `button` the user clicked) up through its ancestor tree, starting from the nearest one. By default, all events bubble.
|
||||
|
||||
To better understand event bubbling, consider the following HTML example, which we will be referring to for most of this article:
|
||||
|
||||
```html
|
||||
<html>
|
||||
<body>
|
||||
<div id="btn-container">
|
||||
<button class="btn">Click me</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```js
|
||||
const ancestors = [
|
||||
window, document, document.documentElement,
|
||||
document.body, document.getElementById('btn-container')
|
||||
];
|
||||
|
||||
// Target phase
|
||||
document.querySelector('.btn').addEventListener('click', e => {
|
||||
console.log(`Hello from ${e.target}`);
|
||||
});
|
||||
// Bubble phase
|
||||
ancestors.forEach(a => {
|
||||
a.addEventListener('click', e => {
|
||||
console.log(`Hello from ${e.currentTarget}`);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
If we add an event listener to each element in the tree, as shown above, we would see a listener fired by the `button` first, then each one of the others firing from the nearest ancestor all the way up to `Window`.
|
||||
|
||||
### Event capturing
|
||||
|
||||
Capturing is the exact opposite of bubbling, meaning that the outer event handlers are fired before the most specific handler (i.e. the one on the `button`). Note that all capturing event handlers are run first, then all the bubbling event handlers.
|
||||
|
||||
You can use event capturing by applying a third argument to [`EventTarget.addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener), setting it to `true`. For example:
|
||||
|
||||
```js
|
||||
// Capture phase
|
||||
ancestors.forEach(a => {
|
||||
a.addEventListener('click', e => {
|
||||
console.log(`Hello from ${e.currentTarget}`);
|
||||
}, true);
|
||||
});
|
||||
```
|
||||
|
||||
Given this code, we would see a listener fired for each ancestor of the `button` first and then the listener of the `button` would fire.
|
||||
|
||||
### Event propagation
|
||||
|
||||
Having explained event bubbling and capturing, we can now explain the three phases of event propagation:
|
||||
|
||||
- During the **capture phase**, the event starts from `Window` and moves down to `Document`, the root element and through ancestors of the target element.
|
||||
- During the **target phase**, the event gets triggered on the event target (e.g. the `button` the user clicked).
|
||||
- During the **bubble phase**, the event bubbles up through ancestors of the target element until the root element, `Document` and, finally, `Window`.
|
||||
|
||||
### Event delegation
|
||||
|
||||
Event delegation refers to the idea of delegating event listening to parent elements instead of adding event listeners directly to the event targets. Using this technique, the parent can catch and handle the bubbling events as necessary.
|
||||
|
||||
```js
|
||||
window.addEventListener('click', e => {
|
||||
if (e.target.className === 'btn') console.log('Hello there!');
|
||||
});
|
||||
```
|
||||
|
||||
In the above example, we delegate event handling from the `button` to `Window` and use `Event.target` to get the original event's target.
|
||||
|
||||
Using the event delegation pattern is advantageous for two reasons:
|
||||
|
||||
- By using event delegation, we can listen for events on a large amount of elements without having to attach event listeners individually, which can provide performance benefits.
|
||||
- By using event delegation, dynamic elements (i.e. added or removed from the DOM over the course of time) can have their events captured and handled without requiring listeners to be registered or removed.
|
||||
117
snippets/articles/s/javascript-event-loop-explained.md
Normal file
117
snippets/articles/s/javascript-event-loop-explained.md
Normal file
@ -0,0 +1,117 @@
|
||||
---
|
||||
title: What is the Event Loop in JavaScript?
|
||||
shortTitle: Event loop explained
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,event]
|
||||
author: chalarangelo
|
||||
cover: tranquility
|
||||
excerpt: The Event Loop is a source of confusion for many developers, but it's a fundamental piece of the JavaScript engine.
|
||||
dateModified: 2022-08-21T05:00:00-04:00
|
||||
---
|
||||
|
||||
The Event Loop is a source of confusion for many developers, but it's a fundamental piece of the JavaScript engine. It's what allows JavaScript to be single-threaded, yet able to execute in a non-blocking fashion. To understand the Event Loop, we first need to explain a few things about the JavaScript engine, such as the Call Stack, Tasks, Microtasks and their respective Queues. Let's break them down one by one.
|
||||
|
||||
### The Call Stack
|
||||
|
||||
The **Call Stack** is a data structure that keeps track of the execution of JavaScript code. As the name suggests, it's a stack, thus a LIFO (Last In, First Out) data structure in memory. Each function that's executed is represented as a frame in the Call Stack and placed on top of the previous function.
|
||||
|
||||
Let's look at a simple example, step by step:
|
||||
|
||||
```js
|
||||
function foo() {
|
||||
console.log('foo');
|
||||
bar();
|
||||
}
|
||||
|
||||
function bar() {
|
||||
console.log('bar');
|
||||
}
|
||||
```
|
||||
|
||||
1. The Call Stack is initially empty.
|
||||
2. The function `foo()` is pushed onto the Call Stack.
|
||||
3. The function `foo()` is executed and popped off the Call Stack.
|
||||
4. The function `console.log('foo')` is pushed onto the Call Stack.
|
||||
5. The function `console.log('foo')` is executed and popped off the Call Stack.
|
||||
6. The function `bar()` is pushed onto the Call Stack.
|
||||
7. The function `bar()` is executed and popped off the Call Stack.
|
||||
8. The function `console.log('bar')` is pushed onto the Call Stack.
|
||||
9. The function `console.log('bar')` is executed and popped off the Call Stack.
|
||||
10. The Call Stack is now empty.
|
||||
|
||||
### Tasks and the Task Queue
|
||||
|
||||
**Tasks** are scheduled, synchronous blocks of code. While executing, they have exclusive access to the Call Stack and can also enqueue other tasks. Between Tasks, the browser can perform rendering updates. Tasks are stored in the **Task Queue**, waiting to be executed by their associated functions. The Task Queue, in turn, is a FIFO (First In, First Out) data structure. Examples of Tasks include the callback function of an event listener associated with an event and the callback of `setTimeout()`.
|
||||
|
||||
### Microtasks an the Microtask Queue
|
||||
|
||||
**Microtasks** are similar to Tasks in that they're scheduled, synchronous blocks of code with exclusive access to the Call Stack while executing. Additionally, they are stored in their own FIFO (First In, First Out) data structure, the **Microtask Queue**. Microtasks differ from Tasks, however, in that the Microtask Queue must be emptied out after a Task completes and before re-rendering. Examples of Microtasks include `Promise` callbacks and `MutationObserver` callbacks.
|
||||
|
||||
Microtasks and the Microtask Queue are also referred to as Jobs and the Job Queue.
|
||||
|
||||
### The Event Loop
|
||||
|
||||
Finally, the **Event Loop** is a loop that keeps running and checks if the Call Stack is empty. It processes Tasks and Microtasks, by placing them in the Call Stack one at a time and also controls the rendering process. It's made up of four key steps:
|
||||
|
||||
1. **Script evaluation:** Synchronously executes the script until the Call Stack is empty.
|
||||
2. **Task processing:** Select the first Task in the Task Queue and run it until the Call Stack is empty.
|
||||
3. **Microtask processing:** Select the first Microtask in the Microtask Queue and run it until the Call Stack is empty, repeating until the Microtask Queue is empty.
|
||||
4. **Rendering:** Re-render the UI and loop back to step 2.
|
||||
|
||||
### A practical example
|
||||
|
||||
To better understand the Event Loop, let's look at a practical example, incorporating all of the above concepts:
|
||||
|
||||
```js
|
||||
console.log('Script start');
|
||||
|
||||
setTimeout(() => console.log('setTimeout()'), 0);
|
||||
|
||||
Promise.resolve()
|
||||
.then(() => console.log('Promise.then() #1'))
|
||||
.then(() => console.log('Promise.then() #2'));
|
||||
|
||||
console.log('Script end');
|
||||
|
||||
// LOGS:
|
||||
// Script start
|
||||
// Script end
|
||||
// Promise.then() #1
|
||||
// Promise.then() #2
|
||||
// setTimeout()
|
||||
```
|
||||
|
||||
Does the output look like what you expected? Let's break down what's happening, step by step:
|
||||
|
||||
1. The Call Stack is initially empty. The Event Loop begins evaluating the script.
|
||||
2. `console.log()` is pushed to the Call Stack and executed, logging `'Script start'`.
|
||||
3. `setTimeout()` is pushed to the Call Stack and executed. This creates a new Task for its callback function in the Task Queue.
|
||||
4. `Promise.prototype.resolve()` is pushed to the Call Stack and executed, calling in turn `Promise.prototype.then()`.
|
||||
5. `Promise.prototype.then()` is pushed to the Call Stack and executed. This creates a new Microtask for its callback function in the Microtask Queue.
|
||||
6. `console.log()` is pushed to the Call Stack and executed, logging `'Script end'`.
|
||||
7. The Event Loops has finished its current Task, evaluating the script. It then begins running the first Microtask in the Microtask Queue, which is the callback of `Promise.prototype.then()` that was queued in step 5.
|
||||
8. `console.log()` is pushed to the Call Stack and executed, logging `'Promise.then() #1'`.
|
||||
9. `Promise.prototype.then()` is pushed to the Call Stack and executed. This creates a new entry for its callback function in the Microtask Queue.
|
||||
10. The Event Loop checks the Microtask Queue. As it’s not empty, it executes the first Microtask, which is the callback of `Promise.prototype.then()` that was queued in step 10.
|
||||
11. `console.log()` is pushed to the Call Stack and executed, logging `'Promise.then() #2'`.
|
||||
12. Re-rendering would occur here, if there was any.
|
||||
13. The Microtask Queue is empty, so the Event Loop moves to the Task Queue and executes the first Task, which is the callback of `setTimeout()` that was queued in step 3.
|
||||
14. `console.log()` is pushed to the Call Stack and executed, logging `'setTimeout()'`.
|
||||
15. Re-rendering would occur here, if there was any.
|
||||
16. The Call Stack is now empty.
|
||||
|
||||
### Summary
|
||||
|
||||
- The **Event Loop** is responsible for executing the JavaScript code. It first evaluates and executes the script, then processes **Tasks** and **Microtasks**.
|
||||
- **Tasks** and **Microtasks** are scheduled, synchronous blocks of code. They are executed one at a time, and are placed in the **Task Queue** and **Microtask Queue**, respectively.
|
||||
- For all of these, the **Call Stack** is used to keep track of function calls.
|
||||
- Whenever **Microtasks** are executed, the **Microtask Queue** must be emptied out before the next **Task** can be executed.
|
||||
- **Rendering** occurs between **Tasks**, but not between **Microtasks**.
|
||||
|
||||
### Notes
|
||||
|
||||
- The script evaluation step of the Event Loop is in itself treated similarly to a Task.
|
||||
- The second argument of `setTimeout()` indicates a minimum time until execution, not a guaranteed time. This is due to the fact that Tasks execute in order and that Microtasks may be executed in-between.
|
||||
- The behavior of the event loop in Node.js is similar, but has some differences. Most notably, there is no rendering step.
|
||||
- Older browser versions did not completely respect the order of operations, so Tasks and Microtasks may execute in different orders.
|
||||
69
snippets/articles/s/javascript-evil-closures.md
Normal file
69
snippets/articles/s/javascript-evil-closures.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Are JavaScript closures inherently evil?
|
||||
shortTitle: Closures and hidden state
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function,closure]
|
||||
author: chalarangelo
|
||||
cover: silver-flat-screen
|
||||
excerpt: Closures are used frequently, yet often misunderstood. Understanding them in depth is crucial to be able to write clean, maintainable code.
|
||||
dateModified: 2022-05-18T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript closures are used frequently, yet often misunderstood. Understanding them in depth is crucial to be able to write clean, maintainable and bug-free code. We previously discussed what they are and how they work.
|
||||
|
||||
I strongly recommend you read the [previous article on closures](/articles/s/javascript-closures) if you haven't already. Instead of rehashing the same information, I would like to discuss the dangers of using closures and present my view on the topic.
|
||||
|
||||
### Hidden state
|
||||
|
||||
The main argument against closures is that of hidden state. Hidden state refers to obscuring the state of an object or, in this case, a function. The argument is that **internal mutable state** can create unpredictable behavior and unexpected results. Due to this, it's often said that hidden state is the root of all evil when it comes to programming.
|
||||
|
||||
While the argument in itself has some merit, I don't much like the generalization. There are perfectly valid cases where hidden state is expected, even practically a necessity. However, it's true that hidden state can create bugs and unmaintainable code.
|
||||
|
||||
An example of hidden state due to closures would be the one presented in my original introduction to the topic:
|
||||
|
||||
```js
|
||||
const initCounter = (start = 0) => {
|
||||
let value = start;
|
||||
return {
|
||||
get: () => value,
|
||||
increment: () => ++value,
|
||||
decrement: () => --value,
|
||||
reset: () => value = start
|
||||
};
|
||||
}
|
||||
|
||||
const counter = initCounter(5);
|
||||
counter.get(); // 5
|
||||
counter.increment(); // 6
|
||||
counter.increment(); // 7
|
||||
counter.decrement(); // 6
|
||||
counter.reset(); // 5
|
||||
```
|
||||
|
||||
In this scenario, the `initCounter` function returns an object that contains hidden mutable state in the form of the `value` variable. Obviously, this is a very simple example, but `counter.get()` or `counter.increment()` in isolation would be considered non-deterministic expressions. There is no way to know the result of a method call like that without analyzing the surrounding code.
|
||||
|
||||
While this is not uncommon, it can get more complicated when shared state comes into play or many pieces of code are interacting with one another. The common remedy to this issue is to use **functional programming** and refactor the hidden mutable state into an argument or a shared global variable.
|
||||
|
||||
### Access to context
|
||||
|
||||
Not all closures are created equal. In fact, there are perfectly valid use-cases of closures that can make life a lot easier. For example, **accessing shared constants** should be considered pretty safe. After all, if you want truly pure functions you shouldn't even access globals and web APIs. This would be pretty impractical, as you would have to pass each global and API as an argument to the function.
|
||||
|
||||
Although reasonably safe, it's important to ensure that constants are initialized before being used and explicitly throw an error, if not. Additionally, adequate documentation of such closures will minimize friction and make sure other developers understand what's going on. Finally, providing an escape hatch, usually in the form of **default arguments** that can be overridden, should be considered if possible.
|
||||
|
||||
Here's an example of a simple random number generator, following these rules:
|
||||
|
||||
```js
|
||||
const randomNumber = (limit = 100, random = Math.random) => random() * limit;
|
||||
|
||||
randomNumber(10); // 4.0
|
||||
randomNumber(10, () => 0.2); // 2.0
|
||||
```
|
||||
|
||||
Another benefit of these practices is that writing tests is a lot easier, as there's no confusion as to what needs to be mocked at any given time. In this example, we can easily replace `Math.random()` with any function that we want and know the resulting value.
|
||||
|
||||
### Conclusion
|
||||
|
||||
Closures in themselves are just another language feature that you have to wrap your head around. As a rule of thumb, use them sparingly, clarify your code's intent and provide escape hatches to reduce potential error surface.
|
||||
|
||||
When used correctly, they can be another tool in your arsenal. Yet, if you use them poorly, they can create some really nasty bugs or unmaintainable code. It takes time to get used to them and be able to spot anti-patterns before they become a problem.
|
||||
27
snippets/articles/s/javascript-expression-statement.md
Normal file
27
snippets/articles/s/javascript-expression-statement.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: What is the difference between an expression and a statement in JavaScript?
|
||||
shortTitle: Expressions and statements
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [type]
|
||||
author: chalarangelo
|
||||
cover: forest-balcony
|
||||
excerpt: JavaScript distinguishes expressions and statements. Learn their differences in this short article.
|
||||
dateModified: 2021-11-07T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript distinguishes expressions and statements. An **expression** is any valid unit of code that resolves to a value. A **statement** is a unit of code that performs an action. Some examples:
|
||||
|
||||
```js
|
||||
// Statements
|
||||
let x = 0;
|
||||
function add(a, b) { return a + b; }
|
||||
if (true) { console.log('Hi'); }
|
||||
|
||||
// Expressions
|
||||
x; // Resolves to 0
|
||||
3 + x; // Resolves to 3
|
||||
add(1, 2); // Resolves to 3
|
||||
```
|
||||
|
||||
Anywhere JavaScript expects a statement, you can also write an expression. This kind of statement is called an **expression statement**. Conversely, you cannot write a statement where JavaScript expects an expression.
|
||||
55
snippets/articles/s/javascript-for-in-for-of-foreach.md
Normal file
55
snippets/articles/s/javascript-for-in-for-of-foreach.md
Normal file
@ -0,0 +1,55 @@
|
||||
---
|
||||
title: What is the difference between JavaScript's for...in, for...of and forEach?
|
||||
shortTitle: JavaScript iteration methods comparison
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array,object,iterator]
|
||||
author: chalarangelo
|
||||
cover: lake-loop
|
||||
excerpt: Learn the differences between the three most commonly used iteration methods in JavaScript, that often confuse beginners and veterans alike.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
`for...in` is used to iterate over all enumerable properties of an object, including inherited enumerable properties. This iteration statement can be used with arrays strings or plain objects, but not with `Map` or `Set` objects.
|
||||
|
||||
```js
|
||||
for (let prop in ['a', 'b', 'c'])
|
||||
console.log(prop); // 0, 1, 2 (array indexes)
|
||||
|
||||
for (let prop in 'str')
|
||||
console.log(prop); // 0, 1, 2 (string indexes)
|
||||
|
||||
for (let prop in {a: 1, b: 2, c: 3})
|
||||
console.log(prop); // a, b, c (object property names)
|
||||
|
||||
for (let prop in new Set(['a', 'b', 'a', 'd']))
|
||||
console.log(prop); // undefined (no enumerable properties)
|
||||
```
|
||||
|
||||
`for...of` is used to iterate over iterable objects, iterating over their values instead of their properties. This iteration statement can be used with arrays, strings, `Map` or `Set` objects, but not with plain objects.
|
||||
|
||||
```js
|
||||
for (let val of ['a', 'b', 'c'])
|
||||
console.log(val); // a, b, c (array values)
|
||||
|
||||
for (let val of 'str')
|
||||
console.log(val); // s, t, r (string characters)
|
||||
|
||||
for (let val of {a: 1, b: 2, c: 3})
|
||||
console.log(prop); // TypeError (not iterable)
|
||||
|
||||
for (let val of new Set(['a', 'b', 'a', 'd']))
|
||||
console.log(val); // a, b, d (Set values)
|
||||
```
|
||||
|
||||
Finally, `forEach()` is a method of the `Array` prototype, which allows you to iterate over the elements of an array. While `forEach()` only iterates over arrays, it can access both the value and the index of each element while iterating.
|
||||
|
||||
```js
|
||||
['a', 'b', 'c'].forEach(
|
||||
val => console.log(val) // a, b, c (array values)
|
||||
);
|
||||
|
||||
['a', 'b', 'c'].forEach(
|
||||
(val, i) => console.log(i) // 0, 1, 2 (array indexes)
|
||||
);
|
||||
```
|
||||
47
snippets/articles/s/javascript-for-loop-early-break.md
Normal file
47
snippets/articles/s/javascript-for-loop-early-break.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "Tip: Use JavaScript for loops if you need to break out early"
|
||||
shortTitle: For loops for early breaking
|
||||
type: tip
|
||||
language: javascript
|
||||
tags: [array,loop]
|
||||
author: chalarangelo
|
||||
cover: armchair
|
||||
excerpt: Iteration in JavaScript can be done a handfuld of ways, most often using array methods, but sometimes a `for` loop is the best option.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
The usefulness of the humble `for` loop in modern JavaScript is rarely talked about. Apart from it being particularly useful in [asynchronous operation scenarios](/blog/s/javascript-async-array-loops), it can also make your code a lot more performant shall you need to break out of a loop early. Consider the following example:
|
||||
|
||||
```js
|
||||
const smallArray = [0, 2];
|
||||
const largeArray = Array.from({ length: 1000 }, (_, i) => i);
|
||||
|
||||
const areEqual = (a, b) => {
|
||||
let result = true;
|
||||
a.forEach((x, i) => {
|
||||
if (!result) return;
|
||||
if (b[i] === undefined || x !== b[i]) result = false;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
areEqual(largeArray, smallArray); // false
|
||||
// Will loop over all items in `largeArray`
|
||||
```
|
||||
|
||||
Obviously, the code isn't optimized, but it highlights the issue of array methods, such as `Array.prototype.forEach()` being unable to break out of the loop early. To counteract this, we could use a `for` loop and an early `return` instead:
|
||||
|
||||
```js
|
||||
const smallArray = [0, 2];
|
||||
const largeArray = Array.from({ length: 1000 }, (_, i) => i);
|
||||
|
||||
const areEqual = (a, b) => {
|
||||
for (let i in a) {
|
||||
if (b[i] === undefined || a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
areEqual(largeArray, smallArray); // false
|
||||
// Will only loop until the first mismatch is encountered
|
||||
```
|
||||
66
snippets/articles/s/javascript-function-call-apply-bind.md
Normal file
66
snippets/articles/s/javascript-function-call-apply-bind.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
title: JavaScript function methods - call(), apply() and bind()
|
||||
shortTitle: Function methods - call(), apply() and bind()
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: canoe
|
||||
excerpt: Learn everything you need to know about JavaScript's `call()`, `apply()` and `bind()` in this short guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Function.prototype.call()
|
||||
|
||||
`Function.prototype.call()` is used to call a function with a given `this` context and any arguments provided individually. For example:
|
||||
|
||||
```js
|
||||
function printThisAndData(...data) {
|
||||
console.log(this.data, ...data);
|
||||
}
|
||||
|
||||
const obj = { data: 0 };
|
||||
const data = [1, 2, 3];
|
||||
|
||||
printThisAndData.call(obj, data); // logs: 0 [1, 2, 3]
|
||||
printThisAndData.call(obj, ...data); // logs: 0 1 2 3
|
||||
```
|
||||
|
||||
### Function.prototype.apply()
|
||||
|
||||
`Function.prototype.apply()` is almost identical to `Function.prototype.call()` in the sense that it calls a function with a given `this` context, however it requires arguments to be provided as an array. For example:
|
||||
|
||||
```js
|
||||
function printThisAndData(...data) {
|
||||
console.log(this.data, ...data);
|
||||
}
|
||||
|
||||
const obj = { data: 0 };
|
||||
const data = [1, 2, 3];
|
||||
|
||||
printThisAndData.apply(obj, data); // logs: 0 1 2 3
|
||||
printThisAndData.apply(obj, ...data); // Throws a TypeError
|
||||
```
|
||||
|
||||
### Function.prototype.bind()
|
||||
|
||||
`Function.prototype.bind()` is slightly different from the previous two methods. Instead of calling a function with the given `this` context and returning the result, it returns a function with its `this` context bound and any arguments provided individually prepended to the arguments at the time of calling the returned function. For example:
|
||||
|
||||
```js
|
||||
function printThisAndData(...data) {
|
||||
console.log(this.data, ...data);
|
||||
}
|
||||
|
||||
const obj = { data: 0 };
|
||||
const data = [1, 2, 3];
|
||||
|
||||
const printObjAndData = printThisAndData.bind(obj);
|
||||
|
||||
printObjAndData(data); // logs: 0 [1, 2, 3]
|
||||
printObjAndData(...data); // logs: 0 1 2 3
|
||||
|
||||
const printObjTwoAndData = printThisAndData.bind(obj, 2);
|
||||
|
||||
printObjTwoAndData(data); // logs: 0 2 [1, 2, 3]
|
||||
printObjTwoAndData(...data); // logs: 0 2 1 2 3
|
||||
```
|
||||
31
snippets/articles/s/javascript-higher-order-functions.md
Normal file
31
snippets/articles/s/javascript-higher-order-functions.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: Understanding higher-order functions in JavaScript
|
||||
shortTitle: Higher-order functions
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: rock-climbing
|
||||
excerpt: Learn everything you need to know about higher-order functions with this short guide and level up your programming skills.
|
||||
dateModified: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
Higher-order functions are **functions that operate on other functions**, either by accepting them as arguments or by returning them as their results. This allows us to create an abstraction layer over actions, not just values.
|
||||
|
||||
The reason we can write higher-order functions in JavaScript is due to the fact that functions are values. This means they can be assigned to variables and passed as values. You might also often hear the term _callback_ when referring to a function that is passed as an argument. This is due to it being called by the higher-order function. Callbacks are particularly common in JavaScript, with event handling, asynchronous code and array operations relying heavily on them.
|
||||
|
||||
The main advantages of this technique are abstraction, composition, code reusability and readability. Most of the 30 seconds of code snippets are built with higher-order functions in mind. They are small, easily digestible functions that are **highly reusable** and **can be composed** to create more complex logic.
|
||||
|
||||
That being said, we can take a look at an example, utilizing some very simple functions:
|
||||
|
||||
```js
|
||||
const add = (a, b) => a + b;
|
||||
const isEven = num => num % 2 === 0;
|
||||
|
||||
const data = [2, 3, 1, 5, 4, 6];
|
||||
|
||||
const evenValues = data.filter(isEven); // [2, 4, 6]
|
||||
const evenSum = data.filter(isEven).reduce(add); // 12
|
||||
```
|
||||
|
||||
In this example, we define two simple functions that we then use as callbacks in `Array.prototype.reduce()` and `Array.prototype.filter()` to get the result we want. Both of these functions are higher-order functions. This allows us to create an **abstraction layer for any action** we might want to perform without having to rewrite how the filtering or reduction algorithm is to be applied every single time.
|
||||
47
snippets/articles/s/javascript-iife.md
Normal file
47
snippets/articles/s/javascript-iife.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: What is an IIFE in JavaScript?
|
||||
shortTitle: IIFE introduction
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [function]
|
||||
author: chalarangelo
|
||||
cover: tropical-waterfall
|
||||
excerpt: An Immediately Invoked Function Expression (IIFE) is a JavaScript trick that trips up many developers. Here's what you need to know.
|
||||
dateModified: 2021-06-14T12:00:00+03:00
|
||||
---
|
||||
|
||||
An **Immediately Invoked Function Expression** (IIFE for short) is a JavaScript function that is immediately invoked as soon as it's evaluated by the JavaScript runtime. Here's what it looks like alongside a roughly equivalent piece of code:
|
||||
|
||||
```js
|
||||
// IIFE
|
||||
(function (message) {
|
||||
console.log(message);
|
||||
})('Hello World');
|
||||
// LOGS: 'Hello World'
|
||||
|
||||
// Equivalent code using named function
|
||||
function logMessage(message) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
logMessage('Hello World'); // LOGS: 'Hello World'
|
||||
```
|
||||
|
||||
Remember the "roughly equivalent" part? The only difference is the use of a named function instead of an anonymous one. From this example, it should be easy to deduce the anatomy of an IIFE, breaking it down into its parts:
|
||||
|
||||
- Optional leading semicolon to avoid `TypeError`s in cases of minification etc.
|
||||
- An anonymous function, containing all the code that we want to invoke.
|
||||
- Opening and closing parentheses to wrap the anonymous function.
|
||||
- Parentheses to call the function and any arguments to invoke it with.
|
||||
|
||||
Note that you can also use arrow functions for IIFEs, if you like. Just make sure to remember the differences between regular and arrow functions in that case. Our previous articles on [arrow functions](/blog/s/javascript-arrow-functions) and [the `this` keyword](/blog/s/javascript-this) should just about cover them.
|
||||
|
||||
```js
|
||||
// All of these are equivalent, leading semicolon is optional
|
||||
;(() => console.log('Hello'))();
|
||||
;(function(){ console.log('Hello'); })();
|
||||
;(function(){ console.log('Hello'); })();
|
||||
;(function(){ console.log('Hello'); }());
|
||||
```
|
||||
|
||||
IIFEs are often used to run some code, while keeping it and its variables out of the global scope. They are often criticized for lack of readability and how they are confusing to beginners. Additionally, they started going out of fashion with the rise of JavaScript modules and transpilers, so they might be less and less common as time goes by.
|
||||
26
snippets/articles/s/javascript-index-for-of-loop.md
Normal file
26
snippets/articles/s/javascript-index-for-of-loop.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
title: "Tip: Get the index of an array item in a JavaScript for...of loop"
|
||||
shortTitle: Array index in for...of loops
|
||||
type: tip
|
||||
language: javascript
|
||||
tags: [array,iterator]
|
||||
author: chalarangelo
|
||||
cover: cave-view
|
||||
excerpt: Did you know you can get the index of an array item in a JavaScript for...of loop? Learn how with this bite-sized tip.
|
||||
dateModified: 2021-07-25T05:00:00-04:00
|
||||
---
|
||||
|
||||
JavaScript's `for...of` loops provide an easy way to iterate over all kinds of iterables from arrays and stings to `Map` and `Set` objects. One supposed limitation over other options (e.g. `Array.prototype.forEach()`) is that you only get the value of each item in the iterable. But that is not necessarily the case, as you can easily leverage `Array.prototype.entries()` to get both the index and value of each array item:
|
||||
|
||||
```js
|
||||
const items = ['a', 'b', 'c'];
|
||||
|
||||
for (let [index, item] of items.entries()) {
|
||||
console.log(`${index}: ${item}`);
|
||||
}
|
||||
// LOGS: 0: a, 1: b, 2: c
|
||||
```
|
||||
|
||||
Moreover, you can use the spread operator (`...`) to convert a string into an array and then use `Array.prototype.entries()` the same way. Finally, both `Map` and `Set` prototypes provide a similar method (`Map.prototype.entries()` and `Set.prototype.entries()` respectively), which can be used the exact same way.
|
||||
|
||||
_If you're not familiar with `for...of` and its syntax, I highly recommending you take a look at [this article about the various iteration methods in JavaScript](/articles/s/javascript-for-in-for-of-foreach)._
|
||||
47
snippets/articles/s/javascript-iterable-to-array.md
Normal file
47
snippets/articles/s/javascript-iterable-to-array.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: How do I convert an iterable to an array in JavaScript?
|
||||
shortTitle: Iterable to array
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object,array,string]
|
||||
author: chalarangelo
|
||||
cover: waves
|
||||
excerpt: Learn how to use the JavaScript ES6 spread syntax to converting iterables to arrays and level up your code today.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript ES6 introduced, among many other things, the [spread operator (`...`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax), which allows an iterable to be expanded in places where zero or more arguments or elements are expected.
|
||||
|
||||
We can use the spread operator to convert iterables or, as they are sometimes referred to, array-likes. Let's take a look at some examples:
|
||||
|
||||
### String
|
||||
|
||||
When the spread operator is applied to a string, the result is an array of strings each one representing a character of the original string:
|
||||
|
||||
```js
|
||||
const name = 'Zelda';
|
||||
const letters = [...name]; // 'Z', 'e', 'l', 'd', 'a'
|
||||
```
|
||||
|
||||
### Set
|
||||
|
||||
A [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) is a collection of unique values. When the spread operator is applied to it, the result is an array of the stored values:
|
||||
|
||||
```js
|
||||
const data = [1, 2, 3, 1, 2, 4]
|
||||
const values = new Set(data);
|
||||
const uniqueValues = [...values]; // [1, 2, 3, 4]
|
||||
```
|
||||
|
||||
Note that the above example is the basis for the [uniqueElements snippet](/js/s/unique-elements).
|
||||
|
||||
### NodeList
|
||||
|
||||
A [NodeList](https://developer.mozilla.org/en-US/docs/Web/API/NodeList) is a collection of nodes, returned by methods such as `Document.childNodes()` or `Document.querySelectorAll()`. While it implements some methods that help manipulate it as an array (e.g. `NodeList.prototype.forEach()`), it's oftentimes desirable to convert it to an array. When the spread operator is applied to it, the result is an array of the contained nodes:
|
||||
|
||||
```js
|
||||
const nodes = document.childNodes;
|
||||
const nodeArray = [...nodes]; // [ <!DOCTYPE html>, html ]
|
||||
```
|
||||
|
||||
Note that the above example is the basis for the [nodeListToArray snippet](js/s/node-list-to-array).
|
||||
96
snippets/articles/s/javascript-iterators.md
Normal file
96
snippets/articles/s/javascript-iterators.md
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
title: What are JavaScript Iterators and where can I use them?
|
||||
shortTitle: JavaScript iterators introduction
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array,object,iterator]
|
||||
author: chalarangelo
|
||||
cover: balloons
|
||||
excerpt: Learn how JavaScript's iterators work and how you can use them to level up your projects by understanding these short code examples.
|
||||
dateModified: 2021-09-26T13:20:57+03:00
|
||||
---
|
||||
|
||||
JavaScript iterators were introduced in ES6 and they are used to loop over a sequence of values, usually some sort of collection. By definition, an iterator must implement a `next()` function, that returns an object in the form of `{ value, done }` where `value` is the next value in the iteration sequence and `done` is a boolean determining if the sequence has already been consumed.
|
||||
|
||||
A very simple iterator with practical use in a real-world project could be as follows:
|
||||
|
||||
```js
|
||||
class LinkedList {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
firstItem() {
|
||||
return this.data.find(i => i.head);
|
||||
}
|
||||
|
||||
findById(id) {
|
||||
return this.data.find(i => i.id === id);
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
let item = { next: this.firstItem().id };
|
||||
return {
|
||||
next: () => {
|
||||
item = this.findById(item.next);
|
||||
if (item) {
|
||||
return { value: item.value, done: false };
|
||||
}
|
||||
return { value: undefined, done: true };
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const myList = new LinkedList([
|
||||
{ id: 'a10', value: 'First', next: 'a13', head: true },
|
||||
{ id: 'a11', value: 'Last', next: null, head: false },
|
||||
{ id: 'a12', value: 'Third', next: 'a11', head: false },
|
||||
{ id: 'a13', value: 'Second', next: 'a12', head: false },
|
||||
]);
|
||||
|
||||
for (let item of myList) {
|
||||
console.log(item); // 'First', 'Second', 'Third', 'Last'
|
||||
}
|
||||
```
|
||||
|
||||
In the above example, we implement a [`LinkedList` data structure](/articles/s/js-data-structures-linked-list), that internally uses a `data` array. Each item in it has a `value` and some implementation-specific properties used to determine its position in the sequence. Objects constructed from this class are not iterable by default. To define an iterator we use `Symbol.iterator` and set it up so that the returned sequence is in order based on the internal implementation of the class, while the returned items only return their `value`.
|
||||
|
||||
On a related note, iterators are just functions, meaning they can be called like any other function (e.g. to delegate the iteration to an existing iterator), while also not being restricted to the `Symbol.iterator` name. This allows us to define multiple iterators for the same object. Here's an example of these concepts at play:
|
||||
|
||||
```js
|
||||
class SpecialList {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.data[Symbol.iterator]();
|
||||
}
|
||||
|
||||
values() {
|
||||
return this.data
|
||||
.filter(i => i.complete)
|
||||
.map(i => i.value)
|
||||
[Symbol.iterator]();
|
||||
}
|
||||
}
|
||||
|
||||
const myList = new SpecialList([
|
||||
{ complete: true, value: 'Lorem ipsum' },
|
||||
{ complete: true, value: 'dolor sit amet' },
|
||||
{ complete: false },
|
||||
{ complete: true, value: 'adipiscing elit' },
|
||||
]);
|
||||
|
||||
for (let item of myList) {
|
||||
console.log(item); // The exact data passed to the SpecialList constructor above
|
||||
}
|
||||
|
||||
for (let item of myList.values()) {
|
||||
console.log(item); // 'Lorem ipsum', 'dolor sit amet', 'adipiscing elit'
|
||||
}
|
||||
```
|
||||
|
||||
In this example, we use the native array iterator of the `data` object to make our `SpecialList` iterable, returning the exact values of the `data` array. Meanwhile, we also define a `values` method, which is an iterator itself, using `Array.prototype.filter()` and `Array.prototype.map()` on the `data` array. Finally, we return the `Symbol.iterator` of the result, allowing iteration only over non-empty objects in the sequence and returning just the `value` for each one.
|
||||
|
||||
58
snippets/articles/s/javascript-json-stringify-pick-keys.md
Normal file
58
snippets/articles/s/javascript-json-stringify-pick-keys.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "Tip: Serialize specific properties from a JSON object"
|
||||
shortTitle: Selective property serialization
|
||||
type: tip
|
||||
language: javascript
|
||||
tags: [object,json]
|
||||
author: chalarangelo
|
||||
cover: coffee-drip
|
||||
excerpt: Have you ever wanted to serialize an object but only include certain keys? Turns out JavaScript provides an easy way to do this!
|
||||
dateModified: 2021-07-06T05:00:00-04:00
|
||||
---
|
||||
|
||||
The default behavior for `JSON.stringify()` is to pull all serializable properties from the given object. However, there are many scenarios where you might want to pick a specific subset of keys from an object. This problem is handled by the second argument of `JSON.stringify()` by passing it either an array of keys or a replacer function.
|
||||
|
||||
### Array of keys
|
||||
|
||||
An array of keys allows you to pick specific keys to be included in the stringified version of the object. This is particularly useful when you know the exact shape of the serialized object you want.
|
||||
|
||||
```js
|
||||
const user = {
|
||||
id: 1234,
|
||||
username: 'johnsmith',
|
||||
name: 'John Smith',
|
||||
age: 39
|
||||
};
|
||||
|
||||
JSON.stringify(user, ['username', 'name']);
|
||||
// '{ "username": "johnsmith", "name": "John Smith" }'
|
||||
```
|
||||
|
||||
### Replacer function
|
||||
|
||||
A replacer function is more versatile than an array of keys and takes both the key and value as its arguments. Apart from using it to include or exclude keys, it can also be useful in altering the value of each key in the stringified representation of the object. In order for a key to be included in the output, the replacer function must return a serializable value (string, number, boolean, null or object).
|
||||
|
||||
```js
|
||||
class Point {
|
||||
constructor (x, y) {
|
||||
this.x = x;
|
||||
this. y = y;
|
||||
}
|
||||
}
|
||||
|
||||
const target = {
|
||||
id: 1234,
|
||||
location: new Point(10, 20),
|
||||
name: 'Delivery point',
|
||||
};
|
||||
|
||||
JSON.stringify(target, (key, value) => {
|
||||
// Exclude id
|
||||
if (key === 'id') return undefined;
|
||||
// Convert location to an array of coordinates
|
||||
if (value instanceof Point) return [value.x, value.y];
|
||||
// Return other properties (i.e. name) without modification
|
||||
return value;
|
||||
});
|
||||
// '{ "location": [10, 20], "name": "Delivery point" }'
|
||||
```
|
||||
62
snippets/articles/s/javascript-listen-once.md
Normal file
62
snippets/articles/s/javascript-listen-once.md
Normal file
@ -0,0 +1,62 @@
|
||||
---
|
||||
title: How can I execute an event handler at most once?
|
||||
shortTitle: Execute event handler only once
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser,event]
|
||||
cover: dog-waiting
|
||||
excerpt: Learn how to attach an event handler to events that is executed at most once in this JavaScript article.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### jQuery
|
||||
|
||||
Back in the day when jQuery was all the rage, we would usually use [`$.one()`](https://api.jquery.com/one/) to create an event handler that would execute at most once for a given event per element. A simple example would be as follows:
|
||||
|
||||
```html
|
||||
<button id="my-btn">Click me!</button>
|
||||
```
|
||||
|
||||
```js
|
||||
$('#my-btn').one('click', () => {
|
||||
console.log('Hello!'); // 'Hello!' will only be logged on the first click
|
||||
});
|
||||
```
|
||||
|
||||
### Using a flag
|
||||
|
||||
However, jQuery seems to have fallen out of favor lately and thus many developers have resorted to writing their version of `$.one()`. An implementation could look like this:
|
||||
|
||||
```js
|
||||
const listenOnce = (el, evt, fn) => {
|
||||
let fired = false;
|
||||
el.addEventListener(evt, (e) => {
|
||||
if (!fired) fn(e);
|
||||
fired = true;
|
||||
});
|
||||
};
|
||||
|
||||
listenOnce(
|
||||
document.getElementById('my-btn'),
|
||||
'click',
|
||||
() => console.log('Hello!')
|
||||
); // 'Hello!' will only be logged on the first click
|
||||
```
|
||||
|
||||
In this implementation, we use a flag, `fired`, to check if the event has been triggered before and only execute the passed callback, `fn`, the first time the event is triggered. There are some details that we might have omitted such as removing the listener, but overall this is a reasonably solid implementation.
|
||||
|
||||
### Event listener options
|
||||
|
||||
If you are targeting modern browsers (i.e. not IE), [`EventTarget.addEventListener()`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) has introduced the `options` object parameter, which allows us to pass a few different flags, one of which is `once`. Setting `once` to `true` results in the exact same behavior as the snippet above with minimal effort.
|
||||
|
||||
Here's one way to write the previous snippet using `once`, which also happens to be how we implemented the latest version of the [listenOnce snippet](/js/s/listen-once):
|
||||
|
||||
```js
|
||||
const listenOnce = (el, evt, fn) => el.addEventListener(evt, fn, { once: true });
|
||||
|
||||
listenOnce(
|
||||
document.getElementById('my-btn'),
|
||||
'click',
|
||||
() => console.log('Hello!')
|
||||
); // 'Hello!' will only be logged on the first click
|
||||
```
|
||||
34
snippets/articles/s/javascript-make-iterable.md
Normal file
34
snippets/articles/s/javascript-make-iterable.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: "Tip: Make any JavaScript value iterable"
|
||||
shortTitle: Make any value iterable
|
||||
type: tip
|
||||
language: javascript
|
||||
tags: [array,iterator,generator]
|
||||
author: chalarangelo
|
||||
cover: colorful-plastic
|
||||
excerpt: Did you know you can define an iterator for any JavaScript value? This quick tip will show you how.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript's `Symbol.iterator` is a very powerful tool that every web developer should learn how to use. It allows you to define and customize the way a value is iterated, effectively allowing you to make any value iterable. You can easily apply this knowledge to plain JavaScript objects and even classes.
|
||||
|
||||
All you need to correctly define an iterator is a generator function `yield`ing each of the iteration values. This could be used to retrieve key-value pairs in an object, call specific getter functions from a class or split a number into an array of digits:
|
||||
|
||||
```js
|
||||
const obj = { a: 1, b: 2, c: 3 };
|
||||
|
||||
obj[Symbol.iterator] = function* () {
|
||||
for (let key of Object.keys(obj)) yield { [key]: obj[key] };
|
||||
};
|
||||
|
||||
[...obj]; // [ { a: 1 }, { b: 2 }, { c: 3 }]
|
||||
|
||||
class IterableNumber extends Number {
|
||||
*[Symbol.iterator]() {
|
||||
for (let digit of [...`${this}`].map(d => Number.parseInt(d))) yield digit;
|
||||
}
|
||||
}
|
||||
|
||||
const num = new IterableNumber(1337);
|
||||
[...num]; // [ 1, 3, 3, 7]
|
||||
```
|
||||
69
snippets/articles/s/javascript-memoization.md
Normal file
69
snippets/articles/s/javascript-memoization.md
Normal file
@ -0,0 +1,69 @@
|
||||
---
|
||||
title: Where and how can I use memoization in JavaScript?
|
||||
shortTitle: Memoization introduction
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [function,memoization]
|
||||
author: chalarangelo
|
||||
cover: cherry-trees
|
||||
excerpt: Learn different ways to memoize function calls in JavaScript as well as when to use memoization to get the best performance results.
|
||||
dateModified: 2021-11-07T16:34:37+03:00
|
||||
---
|
||||
|
||||
Memoization is a commonly used technique that can help speed up your code significantly. This technique relies on a **cache** to store results for previously completed units of work. The purpose of the cache is to **avoid performing the same work more than once**, speeding up subsequent calls of time-consuming functions. Based on this definition, we can easily extract some criteria that can help us decide when to use memoization in our code:
|
||||
|
||||
- Memoization is useful mainly in speeding up slow-performing, costly or time-consuming function calls
|
||||
- Memoization speeds up subsequent calls, so it's best used when you anticipate multiple calls of the same function under the same circumstances
|
||||
- Memoization stores results in memory, so it should be avoided when the same function is called multiple times under very different circumstances
|
||||
|
||||
A simple, object-oriented example of implementing memoization could be as follows:
|
||||
|
||||
```js
|
||||
class MyObject {
|
||||
constructor(data) {
|
||||
this.data = data;
|
||||
this.data[this.data.length - 2] = { value: 'Non-empty' };
|
||||
}
|
||||
|
||||
firstNonEmptyItem() {
|
||||
return this.data.find(v => !!v.value);
|
||||
}
|
||||
|
||||
firstNonEmptyItemMemo() {
|
||||
if (!this.firstNonEmpty)
|
||||
this.firstNonEmpty = this.data.find(v => !!v.value);
|
||||
return this.firstNonEmpty;
|
||||
}
|
||||
}
|
||||
|
||||
const myObject = new MyObject(Array(2000).fill({ value: null }));
|
||||
|
||||
for (let i = 0; i < 100; i ++)
|
||||
myObject.firstNonEmptyItem(); // ~4000ms
|
||||
for (let i = 0; i < 100; i ++)
|
||||
myObject.firstNonEmptyItemMemo(); // ~70ms
|
||||
```
|
||||
|
||||
This example showcases a way to implement memoization inside a class. However, it makes a couple of assumptions. First, it's assumed that the `data` structure will not be altered over the lifecycle of the object. Seconds, it's assumed that this is the only expensive function call we will make, so the code is not reusable. The example also doesn't account for arguments being passed to the function, which would alter the result. A functional approach would work with any given function and also account for arguments. Such an approach can be seen in the form of the [memoize snippet](/js/s/memoize/), which uses a `Map` to store different values.
|
||||
|
||||
We still recommend using that snippet as the primary way to memoize a function, however JavaScript's [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) provides an interesting alternative via the use of the `handler.apply()` trap, which can be used for this purpose as follows:
|
||||
|
||||
```js
|
||||
const memoize = fn => new Proxy(fn, {
|
||||
cache: new Map(),
|
||||
apply (target, thisArg, argsList) {
|
||||
let cacheKey = argsList.toString();
|
||||
if(!this.cache.has(cacheKey))
|
||||
this.cache.set(cacheKey, target.apply(thisArg, argsList));
|
||||
return this.cache.get(cacheKey);
|
||||
}
|
||||
});
|
||||
|
||||
const fibonacci = n => (n <= 1 ? 1 : fibonacci(n - 1) + fibonacci(n - 2));
|
||||
const memoizedFibonacci = memoize(fibonacci);
|
||||
|
||||
for (let i = 0; i < 100; i ++)
|
||||
fibonacci(30); // ~5000ms
|
||||
for (let i = 0; i < 100; i ++)
|
||||
memoizedFibonacci(30); // ~50ms
|
||||
```
|
||||
58
snippets/articles/s/javascript-merge-arrays.md
Normal file
58
snippets/articles/s/javascript-merge-arrays.md
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
title: How do I merge two arrays in JavaScript?
|
||||
shortTitle: Merge arrays
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [array]
|
||||
author: chalarangelo
|
||||
cover: arrays
|
||||
excerpt: Arrays are one of the most used data types in any programming language. Learn how to merge two arrays in JavaScript with this short guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Spread operator
|
||||
|
||||
The [spread operator (`...`)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) was introduced in ES6 and can be used to merge two or more arrays, by spreading each one inside a new array:
|
||||
|
||||
```js
|
||||
const a = [1, 2, 3];
|
||||
const b = [4, 5, 6];
|
||||
|
||||
const merged = [...a, ...b]; // [1, 2, 3, 4, 5, 6]
|
||||
```
|
||||
|
||||
### Array.prototype.concat()
|
||||
|
||||
[`Array.prototype.concat()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat) is a method on the `Array` prototype and can be used to create a new array, either by concatenating both arrays to a new array or one array to the other. Both methods result in a new array, without mutating the original:
|
||||
|
||||
```js
|
||||
const a = [1, 2, 3];
|
||||
const b = [4, 5, 6];
|
||||
|
||||
const merged = [].concat(a, b); // [1, 2, 3, 4, 5, 6]
|
||||
// -- OR --
|
||||
const alsoMerged = a.concat(b); // [1, 2, 3, 4, 5, 6]
|
||||
```
|
||||
|
||||
### Comparing the two
|
||||
|
||||
The spread operator version is definitely shorter and as readable as the `Array.prototype.concat()` one. Apart from that, the spread operator seems to be slightly faster based on [some benchmarks I have performed](https://jsben.ch/9txyg) (as of **Aug, 2020 on Google Chrome 84** - this might or might not be the case in the future, as new optimizations land in different browsers).
|
||||
|
||||
However, `Array.prototype.concat()` can deal with non-array values better than the spread operator can, which might be something to consider when merging values that you are not certain are arrays:
|
||||
|
||||
```js
|
||||
const a = [1, 2, 3];
|
||||
const b = true;
|
||||
const c = 'hi';
|
||||
|
||||
const spreadAb = [...a, ...b]; // Error: b is not iterable
|
||||
const spreadAc = [...a, ...c]; // [1, 2, 3, 'h', 'i'], wrong result
|
||||
// You should use [...a, b] and [...a, c] instead
|
||||
|
||||
const concatAb = [].concat(a, b); // [1, 2, 3, true]
|
||||
const concatAb = [].concat(a, c); // [1, 2, 3, 'hi']
|
||||
```
|
||||
|
||||
As you can see in the above example, the spread operator either throws an error or doesn't output the correct result when passed a non-iterable object. `Array.prototype.concat()` on the other hand has no trouble being passed mixed input.
|
||||
|
||||
So what's the verdict? Use the spread operator (`...`) whenever you know your inputs are arrays, as it performs better and is easy to read and understand. Favor `Array.prototype.concat()` when you are uncertain of one or more of the inputs and do not want to add additional checks, as it handles those cases more gracefully.
|
||||
50
snippets/articles/s/javascript-modify-url-without-reload.md
Normal file
50
snippets/articles/s/javascript-modify-url-without-reload.md
Normal file
@ -0,0 +1,50 @@
|
||||
---
|
||||
title: How do I use JavaScript to modify the URL without reloading the page?
|
||||
shortTitle: Modify URL without reloading
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [browser]
|
||||
author: chalarangelo
|
||||
cover: compass
|
||||
excerpt: Learn all of the options JavaScript provides for modifying the URL of the current page in the browser without reloading the page.
|
||||
dateModified: 2021-09-27T16:47:49+03:00
|
||||
---
|
||||
|
||||
### Using the History API
|
||||
|
||||
The HTML5 [History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) is definitely the way to go for modern websites. It accomplishes the task at hand, while also providing additional functionality. You can use either `history.pushState()` or `history.replaceState()` to modify the URL in the browser, depending on your needs:
|
||||
|
||||
```js
|
||||
// Current URL: https://my-website.com/page_a
|
||||
const nextURL = 'https://my-website.com/page_b';
|
||||
const nextTitle = 'My new page title';
|
||||
const nextState = { additionalInformation: 'Updated the URL with JS' };
|
||||
|
||||
// This will create a new entry in the browser's history, without reloading
|
||||
window.history.pushState(nextState, nextTitle, nextURL);
|
||||
|
||||
// This will replace the current entry in the browser's history, without reloading
|
||||
window.history.replaceState(nextState, nextTitle, nextURL);
|
||||
```
|
||||
|
||||
The arguments for both methods are the same, allowing you to pass a customized serializable `state` object as the first argument, a customized `title` (although most browsers will ignore this parameter) and the `URL` you want to add/replace in the browser's history. Bear in mind that the History API only allows same-origin URLs, so you cannot navigate to an entirely different website.
|
||||
|
||||
### Using the Location API
|
||||
|
||||
The older [Location API](https://developer.mozilla.org/en-US/docs/Web/API/Location) is not the best tool for the job. It reloads the page, but still allows you to modify the current URL and might be useful when working with legacy browsers. You can modify the URL, using either `Window.location.href`, `location.assign()` or `location.replace()`:
|
||||
|
||||
```js
|
||||
// Current URL: https://my-website.com/page_a
|
||||
const nextURL = 'https://my-website.com/page_b';
|
||||
|
||||
// This will create a new entry in the browser's history, reloading afterwards
|
||||
window.location.href = nextURL;
|
||||
|
||||
// This will replace the current entry in the browser's history, reloading afterwards
|
||||
window.location.assign(nextURL);
|
||||
|
||||
// This will replace the current entry in the browser's history, reloading afterwards
|
||||
window.location.replace(nextURL);
|
||||
```
|
||||
|
||||
As you can see, all three options will cause a page reload, which can be undesirable. Unlike the History API, you can only set the URL, without any additional arguments. Finally, the Location API doesn't restrict you to same-origin URLs, which can cause security issues if you are not careful.
|
||||
140
snippets/articles/s/javascript-module-cheatsheet.md
Normal file
140
snippets/articles/s/javascript-module-cheatsheet.md
Normal file
@ -0,0 +1,140 @@
|
||||
---
|
||||
title: JavaScript modules Cheat Sheet
|
||||
type: cheatsheet
|
||||
language: javascript
|
||||
tags: [cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: mountain-lake-2
|
||||
excerpt: Learn everything you need to know about JavaScript modules with this handy cheatsheet.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Named exports
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
export const key = 'this-is-a-secret';
|
||||
|
||||
/* index.js */
|
||||
import { key } from 'environment';
|
||||
```
|
||||
|
||||
- Named exports use a name.
|
||||
- A module can have any number of named exports.
|
||||
- Import and export name should be the same.
|
||||
- Importing requires `{}`.
|
||||
|
||||
### Default exports
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
const environment = {
|
||||
key: 'this-is-a-secret',
|
||||
port: 8000
|
||||
};
|
||||
|
||||
export default environment;
|
||||
/* index.js */
|
||||
import environment from 'environment';
|
||||
|
||||
const { key, port } = environment;
|
||||
```
|
||||
|
||||
- Default exports expose a default value, use the `default` keyword.
|
||||
- A module can only have one default export.
|
||||
- Import name can be anything.
|
||||
- Importing does not require `{}`.
|
||||
|
||||
### Default + named
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
export const envType = 'DEV';
|
||||
|
||||
const environment = {
|
||||
key: 'this-is-a-secret',
|
||||
port: 8000
|
||||
};
|
||||
|
||||
export default environment;
|
||||
|
||||
/* index.js */
|
||||
import { envType }, environment from 'environment';
|
||||
|
||||
const { key, port } = environment;
|
||||
```
|
||||
|
||||
- Default and named exports can be mixed.
|
||||
- Rules about number of exports and naming conventions apply as before.
|
||||
- Import rules apply as before, can be mixed if necessary.
|
||||
|
||||
### Export list
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
const key = 'this-is-a-secret';
|
||||
const port = 8000;
|
||||
|
||||
export {
|
||||
key,
|
||||
port
|
||||
};
|
||||
|
||||
/* index.js */
|
||||
import { key, port } from 'environment';
|
||||
```
|
||||
|
||||
- An export list is a compact way to write multiple named exports.
|
||||
- Rules about number of exports, naming conventions and import rules are the same as those of named exports.
|
||||
- Export lists are not objects.
|
||||
|
||||
### Rename export
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
const key = 'this-is-a-secret';
|
||||
|
||||
export { key as authKey };
|
||||
|
||||
/* index.js */
|
||||
import { authKey } from 'environment';
|
||||
```
|
||||
|
||||
- Named exports can make use of the `as` keyword to rename an export.
|
||||
- Import name should be the same as the renamed export.
|
||||
|
||||
### Rename import
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
export const key = 'this-is-a-secret';
|
||||
|
||||
/* index.js */
|
||||
import { key as authKey } from 'environment';
|
||||
```
|
||||
|
||||
- Named imports can make use of the `as` keyword to rename an import.
|
||||
- Import name (before the `as` keyword) should be the same as the export.
|
||||
|
||||
### Import all
|
||||
|
||||
```js
|
||||
/* environment.js */
|
||||
export const envType = 'DEV';
|
||||
|
||||
const environment = {
|
||||
key: 'this-is-a-secret',
|
||||
port: 8000
|
||||
};
|
||||
|
||||
export default environment;
|
||||
|
||||
/* index.js */
|
||||
import * as env from 'environment';
|
||||
|
||||
const { default: { key, port}, envType } = environment;
|
||||
```
|
||||
|
||||
- Use `*` to import everything a module exports.
|
||||
- Named exports will be available by their names on the imported object.
|
||||
- Default export will be available as the `default` key on the imported object.
|
||||
44
snippets/articles/s/javascript-naming-conventions.md
Normal file
44
snippets/articles/s/javascript-naming-conventions.md
Normal file
@ -0,0 +1,44 @@
|
||||
---
|
||||
title: JavaScript naming conventions
|
||||
shortTitle: Naming conventions
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [variable,cheatsheet]
|
||||
author: chalarangelo
|
||||
cover: naming-conventions
|
||||
excerpt: Naming conventions make code easier to read and understand. Learn how to name your variables in JavaScript with this handy guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
### Variables
|
||||
|
||||
- Names are case-sensitive, lowercase and uppercase are different.
|
||||
- Start variable names with a letter, use `camelCase` for names.
|
||||
- Variable names should be self-descriptive, describing the stored value.
|
||||
- Boolean variables are usually prefixed with `is` or `has`.
|
||||
|
||||
### Functions
|
||||
|
||||
- Names are case-sensitive, lowercase and uppercase are different.
|
||||
- Start function names with a letter, use `camelCase` for names.
|
||||
- Use descriptive names, usually verbs in the imperative form.
|
||||
- Common prefixes are `get`, `make`, `apply` etc.
|
||||
- Class methods follow the same rules.
|
||||
|
||||
### Constant
|
||||
|
||||
- Names are case-sensitive, lowercase and uppercase are different.
|
||||
- Define constants at the top of your file, function or class.
|
||||
- Sometimes `UPPER_SNAKE_CASE` is used, while other times plain `camelCase`.
|
||||
|
||||
### Classes
|
||||
|
||||
- Names are case-sensitive, lowercase and uppercase are different.
|
||||
- Start class names with a capital letter, use `PascalCase` for names.
|
||||
- Use descriptive names, explaining the functionality of the class.
|
||||
- Components, which are used in frontend frameworks follow the same rules.
|
||||
|
||||
### Private
|
||||
|
||||
- Prefix any variable or function with `_` to show intention for it to be private.
|
||||
- As a convention, this will not prevent other parts of the code from accessing it.
|
||||
@ -0,0 +1,59 @@
|
||||
---
|
||||
title: How can I use optional chaining and nullish coalescing in my JavaScript project?
|
||||
shortTitle: Optional chaining and nullish coalescing
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [type]
|
||||
author: chalarangelo
|
||||
cover: purple-leaves
|
||||
excerpt: JavaScript ES2020 introduced optional chaining and nullish coalescing. Learn everything you need to know with this quick guide.
|
||||
dateModified: 2021-06-12T19:30:41+03:00
|
||||
---
|
||||
|
||||
JavaScript ES2020 introduced some new features that help us write cleaner code. Let's take a quick look at two of them that aim to make working with objects and variables a lot easier.
|
||||
|
||||
### Optional chaining
|
||||
|
||||
The [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) (`?.`) allows us to access deeply nested object properties without having to validate each reference in the nesting chain. In case of a reference being nullish (`null` or `undefined`) the optional chaining operator will short-circuit, returning `undefined`. The optional chaining operator can also be used with function calls, returning `undefined` if the given function does not exist.
|
||||
|
||||
The resulting code is shorter and simpler, as you can see below:
|
||||
|
||||
```js
|
||||
const data = getDataFromMyAPI();
|
||||
|
||||
// Without optional chaining
|
||||
const userName = data && data.user && data.user.name;
|
||||
const userType = data && data.user && data.user.type;
|
||||
data && data.showNotifications && data.showNotifications();
|
||||
|
||||
// With optional chaining
|
||||
const userName = data?.user?.name;
|
||||
const userType = data?.user?.type;
|
||||
data.showNotifications?.();
|
||||
```
|
||||
|
||||
### Nullish coalescing
|
||||
|
||||
In the same spirit, the [nullish coalescing operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) (`??`) is a logical operator that allows us to check for nullish (`null` or `undefined`) values, returning the right-hand side operand when the value is non-nullish, otherwise returning the left-hand side operand.
|
||||
|
||||
Apart from cleaner code, this operator might spare us some headaches related to falsy values:
|
||||
|
||||
```js
|
||||
const config = getServerConfig();
|
||||
|
||||
// Without nullish coalescing
|
||||
const port = config.server.port || 8888;
|
||||
// Oops! This will be true even if we pass it false
|
||||
const wrongkeepAlive = config.server.keepAlive || true;
|
||||
// We'll have to explicitly check for nullish values
|
||||
const keepAlive =
|
||||
(config.server.keepAlive !== null & config.server.keepAlive !== undefined)
|
||||
? config.server.keepAlive : true;
|
||||
|
||||
// With nullish coalescing
|
||||
const port = config.server.port ?? 8888;
|
||||
// This works correctly
|
||||
const keepAlive = config.server.keepAlive ?? true;
|
||||
```
|
||||
|
||||
**Note:** Keep in mind that both features are quite new, so their support might not be great just yet (around 80% at the time of writing [[1]](https://caniuse.com/#feat=mdn-javascript_operators_optional_chaining)[[2]](https://caniuse.com/#feat=mdn-javascript_operators_nullish_coalescing)).
|
||||
31
snippets/articles/s/javascript-numeric-separator.md
Normal file
31
snippets/articles/s/javascript-numeric-separator.md
Normal file
@ -0,0 +1,31 @@
|
||||
---
|
||||
title: JavaScript's numeric separators explained
|
||||
shortTitle: Numeric separators explained
|
||||
type: story
|
||||
language: javascript
|
||||
tags: [math,type]
|
||||
author: chalarangelo
|
||||
cover: coffee-drip
|
||||
excerpt: Numeric separators are a somewhat lesser-known JavaScript syntactic sugar that can make working with large constants a lot easier.
|
||||
dateModified: 2021-06-27T05:00:00-04:00
|
||||
---
|
||||
|
||||
Numeric separators are a lesser-known JavaScript syntactic sugar that can make working with numeric constants a lot easier. The long and short of it is that you can add underscores (`_`) to numeric values to make them more readable.
|
||||
|
||||
The idea of separating large numeric values with a special character might sound familiar on account of it being a syntax present in multiple other languages, such as Java, Python, Ruby etc. From what I can tell, numeric separators are at their best when creating shared constants that will not change and are very large, have many repeated digits and/or can be ambiguous.
|
||||
|
||||
Apart from readability, numeric separators don't really offer anything else. For the sceptics among us that don't really see a lot of readability value either, I'd like to show two rather convincing sample cases:
|
||||
|
||||
```js
|
||||
// How many zeroes is that? Millions? Billions? Trillions?
|
||||
const msInOneYear = 31536000000;
|
||||
// Is this 4,200 or 42.00 (in cents)?
|
||||
const price = 4200;
|
||||
|
||||
// Ok, this is approximately 31.5 billion
|
||||
const msInOneYear = 31_536_000_000;
|
||||
// Based on the separator, this should be 42.00 (cents)
|
||||
const price = 42_00;
|
||||
```
|
||||
|
||||
Finally, as far as caveats go, you only need to remember that numbers cannot start or end with a numeric separator and that you can't have two or more numeric separators in a row.
|
||||
125
snippets/articles/s/javascript-object-array-proxy.md
Normal file
125
snippets/articles/s/javascript-object-array-proxy.md
Normal file
@ -0,0 +1,125 @@
|
||||
---
|
||||
title: Can I use an object as an array without modifying it in JavaScript?
|
||||
shortTitle: Object as array
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object,array,proxy,iterator,pattern]
|
||||
author: chalarangelo
|
||||
cover: birds
|
||||
excerpt: Learn how you can leverage the Proxy object to use a JavaScript object the same way as you would use a regular array.
|
||||
dateModified: 2021-09-27T16:42:11+03:00
|
||||
---
|
||||
|
||||
The other day, I stumbled upon some code where I needed to handle an object as a regular array a few times. This was, of course, achievable using `Object.keys()`, `Object.values()` or `Object.entries()`, but it started getting verbose real quick.
|
||||
|
||||
So I thought I could create some kind of wrapper that would take an object and define some array-like behavior for it. I was mainly in need of `Array.prototype.map()`, `Array.prototype.find()`, `Array.prototype.includes()` and `Array.prototype.length`. All of this functionality was pretty straightforward to create using `Object` methods. The only tricky part, so to speak, was getting the object to behave as an iterable, which required using the `Symbol.iterator` and a generator function.
|
||||
|
||||
Injecting the new functionality into an object could be as simple as adding the methods to it. The downside of this approach is that they would be part of the actual object, which can be problematic. It also doesn't help that this is not very reusable if we want to apply this over a handful of objects.
|
||||
|
||||
Enter the [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), one of the lesser known tools in a JavaScript developer's tool belt, yet a very powerful one. It's used to intercept certain operations for an object, such as property lookup, assignment etc. In this scenario, it can neatly wrap the required functionality into a function that creates a proxy around the object.
|
||||
|
||||
The final code, long as it may be, can be seen in the example below. It implements the functionality I needed, as well as a handful more `Array` methods for good measure:
|
||||
|
||||
```js
|
||||
const toKeyedArray = obj => {
|
||||
const methods = {
|
||||
map(target) {
|
||||
return callback =>
|
||||
Object.keys(target).map(key => callback(target[key], key, target));
|
||||
},
|
||||
reduce(target) {
|
||||
return (callback, accumulator) =>
|
||||
Object.keys(target).reduce(
|
||||
(acc, key) => callback(acc, target[key], key, target),
|
||||
accumulator
|
||||
);
|
||||
},
|
||||
forEach(target) {
|
||||
return callback =>
|
||||
Object.keys(target).forEach(key => callback(target[key], key, target));
|
||||
},
|
||||
filter(target) {
|
||||
return callback =>
|
||||
Object.keys(target).reduce((acc, key) => {
|
||||
if (callback(target[key], key, target)) acc[key] = target[key];
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
slice(target) {
|
||||
return (start, end) => Object.values(target).slice(start, end);
|
||||
},
|
||||
find(target) {
|
||||
return callback => {
|
||||
return (Object.entries(target).find(([key, value]) =>
|
||||
callback(value, key, target)
|
||||
) || [])[0];
|
||||
};
|
||||
},
|
||||
findKey(target) {
|
||||
return callback =>
|
||||
Object.keys(target).find(key => callback(target[key], key, target));
|
||||
},
|
||||
includes(target) {
|
||||
return val => Object.values(target).includes(val);
|
||||
},
|
||||
keyOf(target) {
|
||||
return value =>
|
||||
Object.keys(target).find(key => target[key] === value) || null;
|
||||
},
|
||||
lastKeyOf(target) {
|
||||
return value =>
|
||||
Object.keys(target)
|
||||
.reverse()
|
||||
.find(key => target[key] === value) || null;
|
||||
},
|
||||
};
|
||||
const methodKeys = Object.keys(methods);
|
||||
|
||||
const handler = {
|
||||
get(target, prop, receiver) {
|
||||
if (methodKeys.includes(prop)) return methods[prop](...arguments);
|
||||
const [keys, values] = [Object.keys(target), Object.values(target)];
|
||||
if (prop === 'length') return keys.length;
|
||||
if (prop === 'keys') return keys;
|
||||
if (prop === 'values') return values;
|
||||
if (prop === Symbol.iterator)
|
||||
return function* () {
|
||||
for (value of values) yield value;
|
||||
return;
|
||||
};
|
||||
else return Reflect.get(...arguments);
|
||||
},
|
||||
};
|
||||
|
||||
return new Proxy(obj, handler);
|
||||
};
|
||||
|
||||
// Object creation
|
||||
const x = toKeyedArray({ a: 'A', b: 'B' });
|
||||
|
||||
// Accessing properties and values
|
||||
x.a; // 'A'
|
||||
x.keys; // ['a', 'b']
|
||||
x.values; // ['A', 'B']
|
||||
[...x]; // ['A', 'B']
|
||||
x.length; // 2
|
||||
|
||||
// Inserting values
|
||||
x.c = 'c'; // x = { a: 'A', b: 'B', c: 'c' }
|
||||
x.length; // 3
|
||||
|
||||
// Array methods
|
||||
x.forEach((v, i) => console.log(`${i}: ${v}`)); // LOGS: 'a: A', 'b: B', 'c: c'
|
||||
x.map((v, i) => i + v); // ['aA', 'bB, 'cc]
|
||||
x.filter((v, i) => v !== 'B'); // { a: 'A', c: 'c' }
|
||||
x.reduce((a, v, i) => ({ ...a, [v]: i }), {}); // { A: 'a', B: 'b', c: 'c' }
|
||||
x.slice(0, 2); // ['A', 'B']
|
||||
x.slice(-1); // ['c']
|
||||
x.find((v, i) => v === i); // 'c'
|
||||
x.findKey((v, i) => v === 'B'); // 'b'
|
||||
x.includes('c'); // true
|
||||
x.includes('d'); // false
|
||||
x.keyOf('B'); // 'b'
|
||||
x.keyOf('a'); // null
|
||||
x.lastKeyOf('c'); // 'c'
|
||||
```
|
||||
70
snippets/articles/s/javascript-object-comparison.md
Normal file
70
snippets/articles/s/javascript-object-comparison.md
Normal file
@ -0,0 +1,70 @@
|
||||
---
|
||||
title: How do I compare two objects in JavaScript?
|
||||
shortTitle: Object comparison
|
||||
type: question
|
||||
language: javascript
|
||||
tags: [object,comparison]
|
||||
author: chalarangelo
|
||||
cover: blue-lake
|
||||
excerpt: Learn how you can compare two objects in JavaScript using various different techniques.
|
||||
dateModified: 2021-09-26T05:00:00-04:00
|
||||
---
|
||||
|
||||
### Equality comparison
|
||||
|
||||
Even though two different objects can have the same properties with equal values, they are not considered equal when compared using either the loose or strict equality operators (`==` or `===`). This is because arrays and objects in JavaScript are compared by reference. This is unlike primitive values which are compared by value.
|
||||
|
||||
```js
|
||||
const a = { name: 'John', age: 26 };
|
||||
const b = { name: 'John', age: 26 };
|
||||
|
||||
a === b; // false
|
||||
```
|
||||
|
||||
### JSON.stringify
|
||||
|
||||
`JSON.stringify()` often comes up as the solution to this problem. While it can be useful in some situations, comparing the serialized strings can have its own pitfalls. The most common of these has to do with similar, but not equal, values that result in the same serialized string.
|
||||
|
||||
```js
|
||||
const equals = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
const a = { name: 'John', age: 26 };
|
||||
const b = { name: 'John', age: 26 };
|
||||
|
||||
equals(a, b); // true
|
||||
|
||||
const c = { name: 'John' };
|
||||
const d = { name: 'John', age: undefined };
|
||||
|
||||
equals(c, d); // true, should be false
|
||||
```
|
||||
|
||||
### Deep equality comparison
|
||||
|
||||
As it turns out, comparing two objects is not trivial. This is the reason why shallow or deep equality comparison helper functions are so common. These usually use recursion to deeply compare two objects, accounting for most scenarios such as empty values, special types and nesting.
|
||||
|
||||
```js
|
||||
const equals = (a, b) => {
|
||||
if (a === b) return true;
|
||||
if (a instanceof Date && b instanceof Date)
|
||||
return a.getTime() === b.getTime();
|
||||
if (!a || !b || (typeof a !== 'object' && typeof b !== 'object'))
|
||||
return a === b;
|
||||
if (a.prototype !== b.prototype) return false;
|
||||
const keys = Object.keys(a);
|
||||
if (keys.length !== Object.keys(b).length) return false;
|
||||
return keys.every(k => equals(a[k], b[k]));
|
||||
};
|
||||
|
||||
const a = { name: 'John', age: 26 };
|
||||
const b = { name: 'John', age: 26 };
|
||||
|
||||
equals(a, b); // true
|
||||
|
||||
const c = { name: 'John' };
|
||||
const d = { name: 'John', age: undefined };
|
||||
|
||||
equals(c, d); // false
|
||||
```
|
||||
|
||||
The above helper function handles all of these issues and is explained in more depth in the [equals snippet](/js/s/equals).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user