Posts tagged "javascript"

Understanding Javascript at a low level

Did you ever ask yourself how Javascript works under the hood? It's important to understand how the language we're working with works on a lower level.

 Top level view of the Javascript engine

  • Receive source code
  • Parse the code and produce an Abstract Syntax Tree (AST)
  • Interpret as byte code and execute it
  • The profiler checks for optimisations at run-time
  • The compiler creates optimized code and replaces it with the byte code

Javascript Engine

What does the parser do?

Parser

A parser takes the source code and creates an AST. First, the parser splits the source code into tokens. There are different kinds of tokens, e.g. let and new are keywords, while + is an operator.

The tokens are then used to build the AST. If something unexpected is found, a SyntaxError is thrown.

A SyntaxError is thrown when the Javascript engine finds a piece of codes that don't belong to the language syntax.

Example

Let's try out the parser (using this website).

> var answer = 6 * 7;
[
    {
        "type": "Keyword",
        "value": "var"
    },
    {
        "type": "Identifier",
        "value": "answer"
    },
    {
        "type": "Punctuator",
        "value": "="
    },
    {
        "type": "Numeric",
        "value": "6"
    },
    {
        "type": "Punctuator",
        "value": "*"
    },
    {
        "type": "Numeric",
        "value": "7"
    },
    {
        "type": "Punctuator",
        "value": ";"
    }
]

 Abstract Syntax Tree (AST)

It's a graph (data structure) that represents a program.

It's used in:

  • Javascript Engine
  • Bundlers: Webpack, Rollup, Parcel
  • Transpilers: Babel
  • Linters: ESLint, Prettify
  • Type Checkers: Typescript, Flow
  • Syntax Highlighters

You can check the generated AST in the AST Explorer.

Below is an example on how to add an ESLint rule.

export default function(context) {
  return {
    VariableDeclaration(node) {
    	// const variable type
     	if (node.kind === "const") {
        	const declaration = node.declarations[0];
          	
          	// make sure that the value it's a number
          	if (typeof declaration.init.value === "number") {
            	 if (declaration.id.name !== declaration.id.name.toUpperCase()) {
                   	context.report({
                      	node: declaration.id,
                      	message: "The constant name should be in uppercase",
                      	fix: function(fixer) {
                         	return fixer.replaceText(declaration.id, declaration.id.name.toUpperCase()) 
                        }
                    })
                 }
            }
        }
    }
  };
};

 Example using AST to extend an ESLint rule without fix option

Example using AST to extend an ESLint rule without fix option

 Example using AST to extend an ESLint rule with fix option

Example using AST to extend an ESLint rule with fix option


Object preventExtension vs seal vs freeze

ECMAScript 5 introduced new Object methods to Javascript. Among them preventExtensions, seal, freeze methods will be compared to each other.

preventExtensions

An object called by this method can't have any new properties being added.

Example

let person = {
  name: "Agustin",
  age: 27,
};

Object.preventExtensions(person);
Object.isExtensible(person); // return false

person.surname = "Ramirez";
console.log(person.surname); // return undefined

person.name = "Maria";
console.log(person); // return { name: "Maria", age: 27 }

delete person.age;
console.log(person); // return { name: "Maria" }

seal

An object called by this method can not have any new properties being added or current properties deleted.

Example

let person = {
  name: "Agustin",
  age: 27,
};

Object.seal(person);
Object.isSealed(person); // return true

// In strict mode this will throw a `TypeError`
person.foo = "something";
console.log(person.foo); // return undefined

person.name = "Maria";
console.log(person); // return { name: "Maria", age: 27 }

delete person.age;
console.log(person); // return { name: "Maria", age: 27 }

Object.defineProperty(person, "name", {
  get: () => "Juan",
}); // Throw TypeError

console.log(person); // return { name: "Maria", age: 27 }

freeze

An object called by this method can not have any further changes done to it.

Example

let person = {
  name: "Agustin",
  age: 27,
};

Object.freeze(person);
Object.isFrozen(person); // return true

person.name = "Maria";
console.log(person); // return { name: "Agustin", age: 27 }

shallow only

All of these methods only work on object properties shallowly, meaning that just work with the direct property references.

let person = {
  name: "Agustin", // Prevented, Sealed and Frozen
  age: 27, // Prevented, Sealed and Frozen
  address: {
    // Un-prevented, un-sealed and un-frozen
    country: "Argentina", // Un-prevented, un-sealed and un-frozen
    city: "Corrientes", // Un-prevented, un-sealed and un-frozen
  },
};

Feature matrix

Feature default preventExtensions seal freeze
add new properties
remove existing properties
change existing property values

How to chain multiple functions in Javascript properly with async/await

The code below chains multiple functions waits for everything to resolve, and then sends the result:

// chain any number of async functions
const asyncChain = (...fns) => x => fns.reduce(async (res, fn) => fn(await res), x);

// async functions
const add = async x => x + 1;
const multiply = async x => x * 2;
const square = async x => x * x;

const getResult = asyncChain(add, multiply, square);

(async () => console.log(await getResult(4)))(); // 100

Destructuring assignment features in es6

Today I learned about the helpful ES6 "destructuring" feature to unpack arrays and objects.
It is a convenient way to extract values into distinct variables.

It is possible to object-destructure arrays:

const { 0: x, 2: y, 3: z } = ['a', 'b', 'c', 'd'];
console.log(x) // 'a'
console.log(z) // 'd'

This works because array indices are properties as well!

Alternatively, array-destructuring can be applied to any value that is iterable, not just to arrays:

// Sets are iterable
const mySet = new Set().add('a').add('b').add('c');
const [first, second] = mySet;
console.log(first) // 'a'
console.log(second) // 'b'

// Strings are iterable
const [a, b] = 'xyz';
console.log(a) // 'x'
console.log(b) // 'y'

Pretty Printing JSON

JSON is everywhere, but reading nested JSON without proper formatting can be a nightmare.

PostgreSQL

The function jsonb_pretty allows you to pretty print jsonb data.

\pset format unaligned
SELECT jsonb_pretty('{"name": "Lorenz", "team": {"name": "Team #1", "color", "blue"}}'::jsonb);

{
    "name": "Lorenz",
    "team": {
        "name": "Team #1",
        "color": "blue"
    }
}

Javascript

If you work with JSON data in Javascript, you surely know the function JSON.stringify. But did you know it can prettify your JSON as well?

JSON.stringify({"name": "Lorenz", "games_won": 4, "games_lost": 1}, null, 4)
                                      // number of spaces for indentation ^

{
    "name": "Lorenz",
    "games_won": 4,
    "games_lost": 1
}

Python

Python's json module adds functions to work with JSON.

>>> import json
>>> print(json.dumps({"players": [{"name": "Lorenz"}, {"name": "Philip"}]}, indent=4))

{
    "players": [
        {
            "name": "Lorenz"
        },
        {
            "name": "Philip"
        }
    ]
}

Command line using Python

You can also directly run the tool exposed by the json module from the command line:

$ echo '{"name": "Lorenz", "has_eyes": true}' | python3 -m json.tool

{
    "name": "Lorenz",
    "has_eyes": true
}