7 minute read

You don’t know JS

Haven’t had time to post because of rolling off of my old job and starting a new one! Excitement! Here are some notes from my reading of YDKJS. I strongly recommend it to anyone interested in learning front-end development and want to get to really know the internals of the language.

Up and Going:

  • JavaScript is an amalgamation of different languages, which makes it so accessible to new users
  • Often, can ship production code without really fully understanding JS behind the scenes, so rarely do people actually master it
  • Expressions: literal value, variable expression, arithmetic expression, assignment, and call expression (function calls)
  • JS makes use of dynamic typing, which allows for variables to hold any type
  • Scope: Each function gets its own scope, variable name has to be unique within its scope
  • Code in one scope can utilize scope variables of its own and the scope outside of its own

  • Object types (7): number, null, undefined, string, boolean, object, (symbol is new)
  • Only values have types, not their variables
  • Bracket notation useful if property/key in object is named the same as another variable in scope, obj[b] vs. obj[“b”]
  • Object -> subtype is function or array
  • FALSY: “”, 0, -0, NaN, null, undefined, false
  • == with coercion, === without
  • If either side of comparison can be true/false, or if either value is 0, “”, [], use ===. Otherwise, can use ==
  • Non-primitive equality checks are only referential and not by value
  • Function scopes:
    • Var keyword to declare variable in current function scope, or global scope if outside any function
  • Hoisting: declarations are “moved” to the top of the enclosing scope at compilation time. Generally okay for functions, not for variables
  • When variable value is not available, you get a ReferenceError
  • Variables declared in outer scope can be accessed by any lower/inner scopes
  • Immediately Invoked Function Expressions (IIFE) can be used to return values
  • Closures used for module pattern, defining public API and hiding internals
  • ‘This’ does not refer to the function itself
  • To handle backwards compatibility of new features in JS, we use transpilers (Babel) or polyfills or shims

Scope & Closures:

  • Compilers do: tokenizing -> parsing (into Abstract Syntax Tree) -> Code Generation
  • For RHS lookups, will get reference errors. For LHS lookups not in strict mode, will create a global scope variable

Lexical Scope

  • defined as the scope defined at sexing time - where variables and blocks of scope are authored when the lever processes the code, WHERE FUNCTION IS DECLARED
  • Scope lookups stop once they find the first match
  • Cheating lexical scope leads to poorer performance, but can be done through eval()/setTimeOut()/setTimeInterval as if you programmatically wrote code or with, which creates its own separate lexical scope. “Var” inside with will be in containing scope, can lead to weird side effects to the global scope
  • With and eval will stop the javascript engine from optimizing because it can have no idea about the contents of the code so it cannot optimize.
  • In strict mode, eval has its own lexical scope and will not affect the scope around it Function vs. Block Scope
  • Function-based scope
  • Keeps values enclosed and hidden from the outside world
  • Helps collision avoidance
  • Use IIFE for functional expressions
  • Anonymous function expressions can be hard to debug as there’s no label, and leaves documentation lacking
  • Catch creates its own block scope but otherwise, js doesn’t have a built-in concept of block scoping
  • Adding explicit blocks {} can allow for garbage collecting, note that let doesn’t get hoisted. const also creates a block-scoped variable
  • Var is function scope, let and const are block scopes

Hoisting

  • Declaration is done in the compilation phase, assignment during execution phase
  • Function declarations are hoisted, but not expressions
  • Functions are hoisted first before normal variables
  • Any functions within blocks normally get hoisted to enclosing scope

Scope Closure

  • closure is when a function can remember and access its lexical scope even when function is executing outside of its lexical scope
  • callback functions are often examples of closures, or whenever you treat functions as a first-class citizen
  • in for loops with closures, may need to use IIFEs and ensure that there is a new scope per iteration of the loop. Or you could use let and it will be declared for each iteration
  • Can use module-like pattern and expose an object from a function that has references to other functions, sort of like a public api of sorts
  • outer function must be invoked at least once, and must return back at least one inner function to access closure and private scopes
  • ES6 syntax for import/export of modules must be in their own files now
  • Dynamic scope is runtime, and cares where it’s called from, lexical scope is where the function was declared
  • Arrow functions take the immediate lexical enclosing scope, which is different from the usual. No need to do “var self = this;” to bind functions to its own lexical scope
  • This.count++, and then have a “bind(this) on the end of the function, to bind to enclosing lexical scope

This and Object Prototypes:

  • this is not related to lexical scope
  • this matters where the function is called, and not where declared. Run time mechanism
  • Binding made when function is invoked and what it references is based on the call-site of where the fun is called

this all makes sense now!

  • Call-site, where the function is called
  • Focus on the call-stack to find the moment right before the invocation of the function
  • Call-site will follow rules:
    • Default binding - plain undecorated function reference; with “use strict” in contents of function not its call-site, will throw TypeError undefined
    • Implicit binding - when an object has function as attribute, and you call from there - obj.foo(), would have obj as call-site
      • Can get implicitly lost because functions are passed by reference in parameters, and will not be tied to any object reference even if it has
    • Explicit binding - using call and apply, forcing the method to use the object passed in as the “this” binding
      • Hard binding - create a function that calls the function you want -> function() { foo.call(obj);}, called bind in ES6
      • API call contexts does something similar as well, passing an extra parameter to be the obj you wish to bind
    • new binding - no actual constructor functions, but construction calls of functions. New object is created and set as the this binding, will return object automatically
  • New > explicit > implicit > default
  • Can explicitly bind null to do currying or to go to default binding rules -> leads to manipulating global state in cases
    • Use Object.create(null), which skips Object.prototype delegation for safety instead of null
  • Indirection -> when you do assignment, the return value is reference to underlying function, which would then be global
    • (p.foo = o.foo)(); (not o.foo or p.foo, but rather foo());
  • Soft-binding will be overridden with default unless there is an alternate binding that is above default. Allows for flexibility compared to the hard binding
  • Arrow functions bind to the lexical scope, so it overrides rules of this (old way was var self = this). In a codebase, choose one or the other but not both

Objects

  • Primary types in JS: string, number, boolean, null, undefined, object
  • Sub-objects = String, Number, Boolean, Object, Function, Array, Date, RegExp, Error
  • JS will coerce primitives to their related object when required for length, etc.
  • “.attribute” is property access, [“attribute”] is key access. Property Access requires an Identifier compatible property name
  • Can add properties to an array, as it is an object
  • Object.assign() takes target object and multiple sources to create an object via reference, but only immediately available attributes
  • Can define properties and metadata, as well
    Object.defineProperty( myObject, "a", {
      value: 2,
      writable: false, // not writable!
      configurable: true,
      enumerable: true
    } );
    
  • Object preventExtensions will stop getting new properties added to it
  • Object.seal() will lock any new attributes and even config changes
  • Object.freeze will seal and leads to immutability for the object
  • Object.get() does hv a recursive branch from theory
  • Supports GET, PUT, DELETE
  • Built-in delete operator syntax

Mixing “Class” Objects

  • Child inheritance implies copies
  • Explicit mixins - specifying exactly which method to call, and the object to bind it to -> Vehicle.drive.call(this); for shadowed variables
    • Will still get references that don’t duplicate, like for arrays, functions
    • Parasitic inheritance is possible as well, but mainly hijacking the parent and using closures to keep the reference of the original shadowed method
  • Implicit mixins - declaring another object and grafting on a function call that will make a call to another object’s function.
    var Another = {
      cool: function() {
          // implicit mixin of `Something` to `Another`
          Something.cool.call( this );
      }
    };
    

I’ll post the follow-up to these notes soon! Thanks, as always, for following.