We've learned nothing
There’s a sort of evil out there.
Some History and Empathy
- JavaScript was born in about 10 days in the mid-nineties and the top requirement was to appease the Netscape marketing team…so not the ideal scenario for a complex language.
- ECMA, an industry association from 1961 concerned with making communication standards, stepped in to help with JavaScript shortly after release.
- ECMA could not use the name JavaScript for trademark reasons, so it calls it ECMAScript.
- ECMAScript 4 is when shit got real, tried to make a push for “full on language”, also a legal shitstorm which ended in 3.1 instead after 8 years.
- In order to “minimize” confusion, ECMAScript 3.1 was re-named ECMAScript 5.
- 3.1/5 had a small number of improvements.
- Next came ECMAScript 6, aka ECMAScript 2015 (cause why not).
- Much more hefty update, including modules.
- Great blog up to this point.
- ECMAScript now has smaller releases with dates (e.g. ECMAScript 2020 bring Nullish Coalescing Operator
??
).
Productivity
Patterns for writing better code.
strict mode
- strict mode (vs. sloppy mode) was introduced in ECMAScript 5
- Eliminates some JavaScript silent errors by changing them to throw errors.
- Fixes mistakes that make it difficult for JavaScript engines to perform optimizations: strict mode code can sometimes be made to run faster than identical code that’s not strict mode.
- Prohibits some syntax likely to be defined in future versions of ECMAScript.
- applies to entire scripts or to individual functions
'use strict';
modules, exports, and imports
- The scope of JavaScipt started off very small, initial modularity was just loading separate scripts into the global scope
- scripts can overwrite each other
- order of loading scripts matters
<!DOCTYPE html>
<html>
<head>
<script src="person.js"></script>
<script src="author.js"></script>
</head>
<body>
<script>
person.author.doJob('ES6 module history');
</script>
</body>
<script>
// shared scope means other code can inadvertently destroy ours
var person ='all gone!';
</script>
</html>
Loading scripts
-
Node brought along
CommonJS
on the server side- concise syntax with an external requirement for a “loader”
exports
object andrequire
function
- avoids global scope by the loader (require function impl)
- Before a module’s code is executed, Node.js will wrap it with a function wrapper
- concise syntax with an external requirement for a “loader”
-
AMD
spec was on the frontend side- async nature means no static analysis
-
UMD
combines CJS and AMD and sucks -
ES6 finally proposes modules
ESM
, with the lessons (re-learned…)- can determine imports/exports at compile time (statically)
- importing is now handled by the runtime instead of a library
-
- modern browsers have started to support module functionality natively, used to just be “get the whole script, it shouldn’t be that big”
- Use of native JavaScript modules is dependent on the import and export statements
-
- The export statement is used when creating JavaScript modules to export live bindings to functions, objects, or primitive values from the module so they can be used by other programs with the import statement.
- Named Exports (Zero or more exports per module)
- Default Exports (One per module)
- Named exports are useful to export several values. During the import, it is mandatory to use the same name of the corresponding object.
// file test.js
let k; export default k = 12;
// some other file
import m from './test'; // note that we have the freedom to use import m instead of import k, because k was default export
console.log(m); // will log 12
default export can be called anything in import
<script type="module" src="main.js"></script>
declare script as type module
- You can only use import and export statements inside modules, not regular scripts
- modules are deferred automatically
- Modules are only executed once, even if they have been referenced in multiple
<script>
tags
import * as Module from './modules/module.js';
Module.function1()
Module.function2()
grabs all the exports available inside module.js, and makes them available as members of an object Module, effectively giving it its own namespace
typescript compiler tsc
composite
– enforces certain constraints which make it possible for build tools to determine if project has been builtincremental
– save information about the project graph from the last compilation to files stored on disktarget
– JS language features (e.g.ES2022
)declaration
– emit type files (e.g.*.d.ts
)declarationMap
– helps tools (IDEs) map back to source with project referenceslib
– default set of type definitions for built-in JS APIs,DOM
for window, documentesModuleInterop
– less surprises with CJS due to some early bad assumptions- also enables
allowSyntheticDefaultImports
- used with
importHelpers
to minimize extra code created
- also enables
strict
– enable all extra type checking features
typescript project references
-
The path property of each reference can point to a directory containing a tsconfig.json file, or to the config file itself (which may have any name).
-
Referenced projects must have the new composite setting enabled. This setting is needed to ensure TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the composite flag changes a few things
-
tsc --build
which is typescript’s multistage build
building
nodejs
is the runtime (btdubs, browsers are the OG runtime)npm
is the package manageryarn
is the dependency manager
pacman -S nodejs npm yarn
prep arch env for javascript development
yarn run [script] [<args>]
- scripts are defined in
package.json
- the
run
is optional :|
npm
yarn
- v2+ is at berry
yarn install
is used to install all dependencies for a project. This is most commonly used when you have just checked out code for a project, or when another developer on the project has added a new dependency that you need to pick up.- running yarn with no command will run yarn install
yarn workspaces foreach
run a command in each workspace
- appears to not fail if command doesn’t exist in workspace, which is a bummer cause can’t use convention pattern in packages
ESM, TS, Node, the Browser…FIGHT
What do we know?
- Browsers and Node both like explicit extensions in import statements
package
- the
type
can be set tomodule
orcommonjs
to force how all files in the package are handled
file extensions
- if
type
isn’t set in the package config, file extensions can be used on a per-file bases (so not all or nothing)
tsconfig
- typescript exposes compiler settings to change behavior and emitted code
module
can beCommonJS
or a whole bunch or ESM optionsES2020
adds dynamic supportnode16
andnodenext
emit based on the node module rulesThe emitted JavaScript uses either CommonJS or ES2020 output depending on the file extension and the value of the type setting in the nearest package.json. Module resolution also works differently.
moduleResolution
– compiler will try to locate a file that represents the imported module. To do so the compiler follows one of two different strategies:Classic
orNode
node
module resolution is the most-commonly used in the TypeScript community and is recommended for most projects- A relative import is one that starts with
/
,./
or../
- TypeScript compiler has a set of additional flags to inform the compiler of transformations that are expected to happen to the sources to generate the final output
- baseUrl and paths
References
JavaScipt Fundamentals
JavaScipt things I always forget.
objects and classes
-
object
is the simplest structure in JS, a map ofstring
keys to any type data or function- objects are callable
-
class
is functionality wrapped around an object- the class constructor is called with the
new
keyword and it:- create a new object
- bind this to the new object, so you can refer to this in your constructor code
- run the code in the constructor
- return the new object
- classes promote inheritance which can be difficult to scale
- prefer more verbose, but explicit, composition over inheritance
- for example a function might be required to wire together 2 objects (animal + barker) to produce a dog object
- prefer more verbose, but explicit, composition over inheritance
- the class constructor is called with the
prototypes and this
- the prototype chain to find a method is kinda gnarly
- objects have a link to a prototype object
- When trying to access a property of an object, the property will not only be sought on the object but on the prototype of the object, the prototype of the prototype, and so on until either a property with a matching name is found or the end of the prototype chain is reached
const o = { a: 1 };
// The newly created object o has Object.prototype as its [[Prototype]]
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null
const b = ['yo', 'whadup', '?'];
// Arrays inherit from Array.prototype
// (which has methods indexOf, forEach, etc.)
// The prototype chain looks like:
// b ---> Array.prototype ---> Object.prototype ---> null
function f() {
return 2;
}
// Functions inherit from Function.prototype
// (which has methods call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null
const p = { b: 2, __proto__: o };
// It is possible to point the newly created object's [[Prototype]] to
// another object via the __proto__ literal property. (Not to be confused
// with Object.prototype.__proto__ accessors)
// p ---> o ---> Object.prototype ---> null
standard prototypes
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}
class Square extends Polygon {
constructor(sideLength) {
super(sideLength, sideLength);
}
get area() {
return this.height * this.width;
}
set sideLength(newLength) {
this.height = newLength;
this.width = newLength;
}
}
const square = new Square(2);
// square ---> Square.prototype ---> Polygon.prototype ---> Object.prototype ---> null
class prototypes
class Box {
constructor(value) {
this.value = value;
}
// Methods are created on Box.prototype
getValue() {
return this.value;
}
}
modifying class prototype
-
this
points to an object instance- which object? well now, that is the part that sucks
- When used in an object method, this refers to the object
- Methods like call(), apply(), and bind() can refer this to any object
bind
>apply
andcall
> method > global
- which object? well now, that is the part that sucks
-
Bind vs Call?
- call is invoked immediately
const person = {
firstName : "John",
lastName : "Doe",
id : 5566,
myFunction : function() {
return this;
}
};
this is the person object
const person1 = {
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
const person2 = {
firstName:"John",
lastName: "Doe",
}
// Return "John Doe":
person1.fullName.call(person2);
with call
an object can use a method belonging to another object, this
refers to person2
const person = {
firstName:"John",
lastName: "Doe",
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
const member = {
firstName:"Hege",
lastName: "Nilsen",
}
let fullName = person.fullName.bind(member);
with bind
an object can borrow a method from another object
this.x = 9; // 'this' refers to the global object (e.g. 'window') in non-strict mode
const module = {
x: 81,
getX() {
return this.x;
},
};
console.log(module.getX()); // 81
const retrieveX = module.getX;
console.log(retrieveX()); // 9; the function gets invoked at the global scope
// Create a new function with 'this' bound to module
// New programmers might confuse the
// global variable 'x' with module's property 'x'
const boundGetX = retrieveX.bind(module);
console.log(boundGetX()); // 81
showing off a common issue that bind can fix
- arrow functions might be a simpler way to fix method extract issues than bind
var and (then) let and const
- before the advent of ES6,
var
declarations ruledlet
andconst
are the new hotness let
is block scoped, can be updated (but not re-declared)const
is block scoped, cannot be updated
comparisons with referential equality
const hero1 = {
name: 'Batman'
};
const hero2 = {
name: 'Batman'
};
hero1 === hero1; // => true
hero1 === hero2; // => false
hero1 == hero1; // => true
hero1 == hero2; // => false
Object.is(hero1, hero1); // => true
Object.is(hero1, hero2); // => false
referential equality in practice
TypeScript Fundamentals
TypeScript things I always forget.
optional parameters
function printName(last?: string) {
if (last) {
...
}
}
- The
?.
operator is like the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined.
interfaces and types
Interfaces and types feature sets are converging, making the distinction confusing.
interface User {
name: string;
id: number;
}
const user: User = {
name: "Hayes",
id: 0,
};
interface
- handbook
- duck typing like Go
- variables use const whereas properties use readonly
function printName(obj: { first: string; last?: string }) {
// ...
}
optional properties with ?
type ID = number | string;
type alias
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
type assertions
union and intersection types
interface A { a: number }
interface B { b: number }
var ab: A & B = { a: 1, b: 1 };
var a: A = ab; // A & B assignable to A
var b: B = ab; // A & B assignable to B
intersection literal
const args = [8, 5];
// const args: number[]
const angle = Math.atan2(...args); // error! Expected 2 arguments, but got 0 or more.
console.log(angle);
const args = [8, 5] as const;
// const args: readonly [8, 5]
const angle = Math.atan2(...args); // okay
console.log(angle);
giving the typescript compiler some more info to work with, narrow the scope