-
<script></script> tag adds js to html page. two ways:
- inline or
- external script file
- Data types
- Undefined: datatype assigned to a variable w/ 'no value yet'...ie, empty value
- Null: means 'no-existent'
- Undefined: datatype of variable w/ no value yet
- Null: means variable 'no-existent'
- Falsy
- values evaluated to false...the 5 falsy values; undefined, null, 0, '', NaN
- Function
- function expression returns a value
- stored in variable, say x = function(y)
- x is called instead....x(param)
- expressions = anything that returns/produces a value
- statement = just actions, ends in ';'
- 2+3...an expression cus produces a value/result
- user fxn expressions over fxn declarations
- arrows: declare,
const x = y => y + 1
...use,const z = x(y)
- used when function has just one line of code
- Object
-
x = {y: 2}
<= means object literal -
dotNotation
x.y
or -
bracketNotation
x['y']
...this's computable/dynamic -
check property existence
user.name
oruser[name]
-
can have function property...function expression as key/value property
- access as
obj.function(arg)
/obj["function"](arg)
- access as
-
object is mutable...ie, can change their prop values
-
- Concepts
- Variable mutation: changin' var value
- Type coercion: converting from one type to another automatically
- Tackling any problem (4 steps)
- Understand 100% by asking the right questions for clear picture
- Divide and conquer by subtasking
- Research
- Devise a plan. For bigger problems, write a psuedo-code b4 actual code
- Ternary operator: can store their return value
- ternary computed in string template
- Brief History
- Brendan Eich created 1st version of Js in 10 days, called Mocha
- Was changed to LiveScript
- In 1996, was changed to JavaScript to 'attract Java developers'.
- meanwhile, has nothing to do with Java - marketing strategy
- 1997: ES1 (ECMAScript 1) became the 1st version of JavaScript languade standard
- 2009: ES5 (ECMAScript 5) released
- 2015: ES6/ES2015 (ECMAScript 2015) released. the biggest update to the language ever
- After 2015: changed to an annual release cycle
- 2016/2017/2018/2019/... : Release of ES2016(ES7)/ES2017(ES8)/ES2018(ES9)/ES2019(ES10)/...
- ES5:
- supported in all browsers
- ES6/ES7/ES8:
- supported in modern browsers, but not in older browsers
- Most features can be used in production by transpilling and polyfilling (converting to ES5) ^^
- ES9/ES10:
- called ESNext, including future versions
- not all features supported in all modern browsers
- Can use most features in production by transpilling and polyfilling :)
- Function xpression vs fxn declaration
- use fxn expressions over declarations
- Definition w/ the 9 monter terms: Js is a
- high-level,
- prototype-based object-oriented
- multi-paradigm
- interpreted/Just-in-time compiled
- dynamic
- single-threaded
- garbage-collected lang with
- first-class functions and
- a non-blocking event loop concurrency model
- Overview::The Js Engine - program d@ execute js code,
- contains the heap & call stack
- call stack: uses the execution context to execute code
- heap: memory pool storing all objects
- How execution happens::Compilation vs Interpretation
- Compilation: convert entire source code to machine at once, and write to portable binary file, to be executed by any computer
- file can be executed way after compilation
- Interpretor: runs through source code, convert to machine code and execute line by line, all at same time
- js used to be intepreted but now NOT true
- Just-in-time (JIT): convert entire code to machine at once, then execute immediately line by line
- no portable file generated to be executed later
- mix b/n the two.
- how modern JIT compilation works in JS?
- parse the source code
- creates an AST tree
- takes the AST and compile into machine code, JIT
- machine code executed right-away, in the Call Stack
- during execution, background optimization happens on special thread (outside the call) but inside the engine
- Compilation: convert entire source code to machine at once, and write to portable binary file, to be executed by any computer
- Outside the Engine is the callback queue thats queues waiting events
- Events are moved from the queue to the call stack when the call stack is empty, by the Event loops
- Web-APIs like the DOM, Console.log, Fetch API is provided to the engine for access to browser objects
- The Js Engine
- The Web APIs
- The Callback Queue
This is an overview of what happens to our code hosted in the browser
- The host has a Js Engine: program that takes the code and execute
- This is what happens inside the engine;
- Parser read our code and validate the syntax
- If valid, a data structure called Abstract Syntax Tree is produced by the Parser
- Abstract Syntax Tree is then translated into matchine code
- Now, code is run by the processor and does its work
Code run in an env called execution context. A box/container/wrapper that stores vars and in which our code is evaluated and executed - by default is the Global Execution Context - code not inside any function - it's the window object in browser - only one GEC - runtime stacking - global execution context > execution context (on top) > execution context (on top) - then pops each exe context off the stack after completion - one execution context per fxn call
- the EC Object. Has 3 ff props;
- Variable Object (VO)
- Scope chain
- "This" variable
- Goes through 2 phases in the ES
- Creation phase.
- Creation of the VO
- Arg object created to store all args passed into the fxn
- Var declarations are scanned: a property is created in the Variable Object for each variable, and set to undefined
- Function declarations are scanned: a property is created in the Variable Object for each fxn, pointing to the function
- NB: Var + Function declarations = Hoisting
- Means: they're available before the execution phase starts
- Creation of the scope chain
- Each new fxn creates a scope: the env/space in which the vars if defined and accessible
- Lexical scope means that in a lexically nested implemented function, the inner functions (not function declarations) have access to the (scope) variables and other resources of their parent scope.
- But it doesn't work backward to its parents, meaning the child scopes not available to its parents.
- This means that the child's functions are lexically bound to the execution context of their parents.
- Scope chain doesn't relate to the order in which method called but rather where it is written
- const varibles are block(eg, if-else) scoped
- var variables are function scoped even when defined in block scope
- var type create a property on the global window object
- that is the reason to always use const/let unless in legacy code
- declarable functions can be block or function scoped depending wether in strict mode or not
- Determine value of 'this' var
- In a regular function, the this points to the global object (ie, window object in the browser)
- In a method, the this points to the object that (defined) called the method, ie, the owner of the method
- points the custom object within which it is defined, otherwhise the global
- method borrowing:
x.a = y.b
- this is not static
- In regular method call, the 'this' is undefined/window global obj, because not called by object
- In a method, the this points to the object that (defined) called the method, ie, the owner of the method
- In arrow function, 'this' is the 'this' of the surrounding/parent function (ie, lexical 'this'). Arrow fxns does not get their own 'this' keyword...
- nb: don't use 'this' ins arrow fxns
- In event listener: 'this' is the DOM element that the handler is attached to, ie. the dom element owning the handler
- Event handlers can have return value to their caller. eg;
// PaginationButtonView file class PaginationButtonView{ _parentElement = document.querySelector('.pagination'); addPaginationHandler(handler) { this._parentElement.addEventListener('click', e => { const sourceBtn = e.target.closest('.btn--inline'); if(!sourceBtn) return; // guard clause const goToPageBtnNumber = +sourceBtn.dataset.goto; handler(goToPageBtnNumber); // passing data to handler for further process }) } } // controller file const paginationButtonController = function(responseFromHandler) { console.log('page controller:', responseFromHandler); // return response } paginationButtonView.addPaginationHandler(paginationButtonController); // pub-sub, event-driven
- The 'this' will NOT point to the function itself or the variable environment
- use a regular function declaration/expresion over arrow function to avoid the
- In a regular function, the this points to the global object (ie, window object in the browser)
- Creation of the VO
- Execution phase
- Funtion's code d@ generated the current EC is ran line-by-line
- Creation phase.
** NB: - scope => 'where do variables live?' - 3 types of scopes: global scope, function scope, block scopes (let, const)
- means some variables can be used/accessed in the code before they're actually declared/defined/body-written
- in short "variables lifted to the top of their scope"
- eg. var type variables are 'hoisted' before even being asigned a value
- initial value: undefined
- eg. functions declarations are declared on top before actually being defined
- initial value: actual funtion
- eg. function expressions are not hoisted
- intial value: unintialized, Temporal Dead Zone (TDZ) - defined but can't be used unless has value
- how hosting really happens?
- before execution (in the context execution inside the call stack), code in scanned for variable declarations, and for each variable, a new property is created in the variable environment object.
- if it knows that variables are declared beforehand, it will be set to undefined when you want to use
- var type is undefined cus hoisted
- const and let type is uninitialized cus not hoisted... in TDZ mode
- if you want to use a function before being declared, it will point to that
- if it knows that variables are declared beforehand, it will be set to undefined when you want to use
variableObj = { x: undefined, y: z() }
- before execution (in the context execution inside the call stack), code in scanned for variable declarations, and for each variable, a new property is created in the variable environment object.
- applies more on regular functions not function expressions
- examples
console.log(a) // undefined
console.log(b) // uninitialized, TDZ
console.log(c) // uninitialized, TDZ
var a = 'a';
const b = 'b';
let c = 'c';
Premitive values: values string, numbers, etc
- stored in call stack
- new address is created in stack when copied to another
let a = 'a';
let b = a
b = 'b'
// a = 'a'
// b = 'b'
Reference values: values of objects
- stored in heap
- obj can't be stored in stack cus it's huge so only it's memory address is reference in the stack
let me = {
sn: 'erb',
age: 25
}
let friend = me;
friend.age = 26;
// me = { sn = 'erb', age = 26 }
// friend = { sn = 'erb', age = 26 }
- new variable, not created w/ new address but rather points to the same object address in the call stack (referenced from the heap)...thus affecting existing value
DOM: structured rep of an html doc, used to conn webpages to script like Js - Js helps to access and manipulate the dom Events - notifications sent to notify a code that sth happened on the webpage - unlike regulare fxns that are called, event listeners are also fxns but perform action based on event. They wait for specific event to happen, like click a btn, scroll, key press, etc - how are events processed - events can only be handles/processed as soon as the Execution Stack is empty. - events are stored in a message queue waiting to be processed
- Every Js obj has a prototype property, which makes inheritance possible in Js
- The prototype property is where methods and properties that will be inherited by other objects are put
- The Constructor/Class's prototype property is NOT the prototype of the Class itself, it's the prototype of all instances that are created through it.
- When certain meth is called, that meth is being searched for in the obj itself, and it cannot be found, the search moves to the mother Object's prototype. This search continues until meth is found. This is called Prototype Chain
- Useful instance fxns
hasOwnProperty
instanceOf
_proto_
-
Array destructuring: unpacking var data
- switching vars
- nexted distructuring
- setting default values
-
Object destructuring
- destructuring unpacks key-value pair elements in object
- basically removes the ending bracket
- always set default values
// Given data = {a: 1, b: 2} // Task: add a 'c' property const data = {a: 1, b: 2} const result = {...data, c: 3} const recipe = model.state.recipe; const {recipe} = model.state; // improved
-
Spread operator: used whenever need to expand elements from array individually
...x
...n, n1, n2
- mostly used on iterables: array, set, map
- used on objects as well
- useful in place that expect items separated by comma
- 2 useful case
- ...to build/join arrays
- ...as param to function
-
Rest pattern: packs element of array
- lookup :)
-
Ternary
- to set default value where 0 and null are invalid (replaceable with
||
Short-circuiting) - replaces one-line if-else blocks
- to set default value where 0 and null are invalid (replaceable with
-
Short-circuiting with or and &&
&&
=> to replace one-line if-condition without the 'else' part||
=> to set default value where 0 and null are invalid- eg; Adding a key-value pair to object if key exist on incomming
data
let recipe = data; return { id: recipe.id, cookingTime: recipe.cooking_time, ingredients: recipe.ingredients, ...(recipe?.key && {key: recipe?.key}) // add object if exists ** }
-
Null colescing value
- always use to replace default value setup w/ ternary (when 0 is considered valid)
- for only
nullish
property
-
Optional chaining
- checks if property exist before access
- used w/ null colescing to set default
a.b?.c ?? 'default'
-
Refactoring - Use case;
const userSpendingLimit = { 'erb': 1200, 'anthony': 1000 } // if-else let user; // input if(userSpendingLimit[user]){ user = userSpendingLimit[user]; } else { user = 'erb'; // default user } // improved using ternary const spendingLimit = userSpendingLimit[user] ? userSpendingLimit[user] : 'erb'; // improved using null colescing const spendingLimit = userSpendingLimit?.[user] ?? 'erb'; //// Different use-case with String concatination // Opt A...if with no else let output = ''; if (entry.value <= -bigLimit) { output += `${entry.description.slice(-2)} / `; // Emojis are 2 chars } // Improved A...ternary output += entry.value <= -bigLimit ? `${entry.description.slice(-2)} / ` : '';
- Refer to the
clean.js
in Module10-Modern...
for more refactoring details
- Refer to the
-
For-of loop
- get index with
.entries()
- get index with
-
Enhanced object literal
- object literal => you literary build an object without using any api, just using
{...}
- keys/property names can be computed not just the values
- if property name equal value..don't repeat the variables
// given someArray.push({name: name, age: age, address: location}) // where name, age, location are inputs // then it can be refactored to someArray.push({name, age, address: location})
- object literal => you literary build an object without using any api, just using
-
Set
- accept an iterable eg, array
- specific item is not retrievable because order doesn't matter, ie no indexes
- can check whether an element exists
-
Maps vs Object
- object keys are always string whilst map keys can be any type
- maps:
- when need any type as keys
- easy to iterate and compute size
- when need map keys
- objects:
- easy to access with . and []
- when working with json
- when need only string as keys and need to include methods as value
-
Object to/from Entries
- Can convert entries to object
- Convert object to entries in order to perform data transfromation with map, filters, etc on it
- entries eg:
[ ['1', one], ['2', two], ... ]
- convert entries to object with
Object.fromEntries(entries)
- vice-versa (from object to enteries):
Object.entries(someobject)
- convert entries to object with
-
Arrays vs Sets
- arrays:
- used when need ordered list of values
- when can contain duplicates
- when need to manipulate data
- sets:
- used when need unique values
- when high-performance is important
- arrays:
-
Strings
- most useful methods:
- expression:
.slice(), .trim(), .toLowerCase(), .toUpperCase(), .replace[All]()
- conditional:
.includes(), .startWith(), .endsWith()
- array:
.split(), .join()
.padEnd(), .repeat()
- expression:
- most useful methods:
-
Functions with default param
const addExpense = function (value, description, user) { if (!user) user = 'jonas'; // setting default user param user = user.toLowerCase(); ... } // Improved...set in param const addExpense = function (value, description, user = 'jonas') { user = user.toLowerCase(); ... } renderError(message = this._errorMessage) {...}
-
Arrow functions
- usecase 1: when creat straigtforward function
- takes user input an returns result
const getLimit = user => spendingLimits?.[user] ?? 0; const user = 'erb'; const limit = getLimit(user)
-
Pass arg by value vs reference
- by value:
- for premitive types
- arg value is copied to into param value
- diff addresses thus independent of each other
- change on one doesn't affect the other
- by reference
- for non-premitive types
- arg address is coppied to param, ie pointing to same address
- change on one affect the other
- js does NOT have pass by reference even though it looks like
- the reference is a value containing memory address
- by value:
-
First class and Higher order functions
- 1st-Class fnxs
- functions treated as 1st class
- means functions are simply values
- functions are just another 'type' of object
- functions can be
- stored in a variable
- passed as argument to another fnx
- called on another fnxs
- Higher-order fnxs
- fnxs that recieves anther fnx as arg or returns another fnx or both
btn.addEventListener('click', greet)
addEventListener
= higher order fnxgreet
= 1st class fnx
- Function returning another function
- the call() and apply() and bind() methods
- IIFE
- Closures
- 1st-Class fnxs
-
.splice() / .slice() / .reverse() / .join() / .pop()
-
forEach loop
- is a higher-order function that takes a callback fxn
- only causes side-effect, doesn't return a new instance
- in Arrays
- forEach comes with index & the entire array. eg;
numbers.forEach((number, index, numbers) => log(number, index, numbers))
- loop is not breakable
- always loop over the entire array
- continue and break statement doesn't work in forEach loop
- to break out of the loop, use For-of loop
- forEach comes with index & the entire array. eg;
- in Maps
- forEach comes with the value, key & the entire map. eg;
numbers.forEach((value, key, numbers) => log(value, key, numbers))
- forEach comes with the value, key & the entire map. eg;
- in Sets
- forEach comes with the value, throw-away & the entire set. eg;
numbers.forEach((value, _, set) => log(value, set))
- _ implies throwaway variable
- means **variable is unnecessary or unused
- _ implies throwaway variable
- forEach comes with the value, throw-away & the entire set. eg;
-
Data transformation
- Map, Filter, Reduce
- Map:
-Has index
- .map((item, index, array) => ...)
- used when wanna return a new array
- unlike
forEach
that just does causes side effect (modifies the original data) without returning a new object/array
- unlike
- Given
const data = [ {value: 550, description: 'Sold old TV 📺'} {value: 95, description: 'Groceries 🥑'} {value: 15000, description: 'Monthly salary 👩💻'} {value: 4500, description: 'New iPhone 📱'} ] // Impure: causes side-effect + mutability const checkExpenses = function (data) { for (const entry of data) { // or forEach if (entry.value > 1000) { entry.flag = 'limit'; // add property } } }; // Pure: immutability + no side-effect const data = Object.freeze([ {value: 550, description: 'Sold old TV 📺'} {value: 95, description: 'Groceries 🥑'} {value: 15000, description: 'Monthly salary 👩💻'} {value: 4500, description: 'New iPhone 📱'} ]) const checkExpenses = function (data) { data.map(entry => { // improved with map + destructuring return entry.value > 1000 ? {...entry, flag: 'limit'} : entry // add property *** }) };
- Filter:
- conditioned to return new array with same or less number of elements
const data = Object.freeze([ {value: 550, description: 'Sold old TV 📺'} {value: 95, description: 'Groceries 🥑'} {value: 15000, description: 'Monthly salary 👩💻'} {value: 4500, description: 'New iPhone 📱'} ]) const logBigProducts = function () { let bigExpense = ''; for (const entry of data) { bigExpense += entry.value <= 1000 ? `${entry.description.slice(-2)} / ` : ''; } bigExpense = bigExpense.slice(-2); console.log(bigExpense); } // Improved with filter, map, join const logBigProducts = function (data) { const bigExpense = data.filter(entry => entry.value <= 1000) .map(entry => entry.description.slice(-2)) .join(' / '); console.log(bigExpense); }
- Reduce:
- .reduce((accumular, currentItem, index, array) => ..., initialValue)
- initialValue => value before the first iteration even starts
- just like initializing
int accumular(sum) = 0
before for-loop
- just like initializing
- usages;
- any operation that returns only one value;
- for sum
- finding max value
const max = movements.reduce((acc, cur) => { if(acc > cur) return acc; else return cur; }, movements[0]) console.log('max', max);
- any operation that returns only one value;
- NB: It's a bad practice to chain methods(like
splice()
) that mutate the underlying original array
-
Find() method
- returns only the 1st element which satisfies the condition
- used to find only one matching element
- vs Filter
- find doesn't return a new array but filter does
-
FindIndex() into Splice()
- really useful to delete item
-
Some()
- checks if matches any of the condition
- similar to includes() some(x => x == match)
-
Every()
- returns truthy if every item matches given condition
-
Flat(), FlatMaps();
- Flat = flats nested (on n-deep level) arrays
- FlatMap =
.map(..).flat();
-
Programmatically creating and filling arrays. using;
- Array constructor + .fill()
- Array.from({length: 5}, callbackFxn)
- fill the array by using callbackFxn like in .map(), but only the index is needed
- most preferred
- .querySelectorAll() yeilds a nodeList which is does not have array methods
- so Array.from() can be used to create a real array from that
- usage similar as the spread operator (...)
-
array cheatsheet
-
Numbers
- Number.parseInt(), Number.parseFloat('10px'),
- Number.isFinite(), - better to check if sth is a number not stringified number
-
Math and Rounding
- Math.min/max()
trunc(), ciel(), floor()
-
Remainder
- %
-
Big int
- Numbers
- max bit: 2^64
- max number: 2^(53-1)
- same as
Number.MAX_SAFE_INTEGER
- Big int help get number beyond the max int number
- using 'n' postfix
- eg:
23333333333333333333334444444444444444444n
- Numbers
-
Dates
- Timestamp is the milliseconds
- eg
getTime()
- eg
- .getFullYear() over getYear()
.toISOString()
- eg '2019-11-18T21:31:17.178Z'
- .padStart(2, 0) useful to get 2 digits for minute, hour, day, month
- date operations
- timestamp (in ms) is useful
- Timestamp is the milliseconds
-
Internationalization
- format dates based on location or country
- search "iso language code" to format date
- can determine local from browser programmatically
navigator.language
-
Timers:
- setTimeout
- setInterval
- How DOM really works
- DOM is basically an interface b/n Js and Browser
- allows make Js interact with browser
- help us write js code to create, modify and delete html elements; set styles, classes and attributess; listen and respond to events;
- help us interact with the DOM tree from an HTML document
- DOM contains APIs (methods and properties) to interact with the DOM tree
- every element is represented as Node object, each node, eg ``
Paragraph text
` consist of the following types of subNodes;- element
<p> ... </p>
- has HTMLElement type, with downlevel types like HTMLButtonElement, HTMLDivElement.
- One type of HTMLElement per HTML element, each has access to different properties/methods for each type
- text
... Paragraph text ...
- comment
... Comment ...
- document
- is another type of SubNode
- has access to
.querySelector(), .getElementById(), etc
- element
- DOM is basically an interface b/n Js and Browser
- The Node object also inherits from a super class object called EventTarget
- which has access to these methods -
.addEventListener(), .removeEventListener()
- this makes it possible for child nodes to add and listen to event listeners - Selecting, Creating and Deleting ELEMENTS
- follow-up with the
.js
files for illustrations
- follow-up with the
- Styles, Attributes and Classes
.styles
- computed styles
- .attributes...
getAtrributes()
.ClassLis
t- follow-on source .js file
- Types of Events and Eventhandlers
- adding events listeners
- removing event listener
- Event Propagations: Bubbling and Capturing
- Events travels from the root element down to the target element, and then handled. This is called Capturing. After handled, event then travels all the way up to the root again called Bubbling
- This means: when an event is also handled by any of the target element's parents, it will be handled twice - the parent and child target element.
- DOM Traversing
- walking through the DOM
- selecting an element relative to another element
- sometimes direct child or parent
- when we don't know the structure of the DOM at runtime
- getting child element(s)
.querySelector()
- getting parent element(s)
.closest()
....opposite of.querySelector()
since target child element whilst closest target parent element
- getting sibbling element(s)
next[previous]Sibling()
- follow-up to .js file
- walking through the DOM
- Building Tapped component
- guard clause: find the opposite of what we're interested in, and if the opposite is true, return the funtion immediately
if(!handsome) return; if(handsome){ interested flow } // on array const data = []; if(!data || data.length === 0) return 'error'; // show when the data is empty
- Passing Arguments for Event Handlers
- followup to 'mennu fade animation'
- Sticky Navigation
- not implemented in js file
- Building Slider / Carousel
- not implemeted
- Lazy Loading Image
- not implemented in js file
- Life Cycle DOM Events
DOMContentLoaded
event: triggered when HTML, and internal Js script loaded with the DOM tree built...but exeternal resources like images are not yet loadedload
: triggered are the page has fully loaded with images and resources done downloadingbeforeunload
: triggered just before the page is about to be closed...can be used when user is in the middle of filling a form and that can lose data when closing the page
- Efficient js Script loading
- regular synthronous script at end of html body
- Order:
Finish parsing html -> Fetch script -> Execute script
- Quite Better
- Order:
- regular synthronous script at head of html
- while parsing html, it will wait to fetch and execute js script before finishing html parsing
- meaning, html/dom will not finish parsing before js script execute
- order: -
Parsing html -> Fetch script (While paused html parsing) -> Execute script -> Finish html parsing
- not good cus some html script execute against html dom
async
in html head- Order:
Parsing html (Along side fetching script) -> Execute script (while paused html parsing) -> Finish html parsing
- Order:
defer
in html head- best for developer custom script
- script fetched async and executed after html is completely parsed
- order:
Parsing html (along side fetching script) -> finisht html parsing -> Execute script
- conclusion:
- Goal is to finish parsing the html dom and fetching script along side before executing script
- Therefore, the
defer
in head is perfect as it's most efficient
- regular synthronous script at end of html body
- What it is.
- OOP principles
- Abstraction: hiding class members(properties/methods) / details that don't matter
- Exposes only required members that will be interracted with
- Encapsulation: keeping class members private
- Makes members not accessible from outside the class
- Inheritance: making members of certain class available to child class.
- Ensures common logic reuse in class relationship
- Polimorphism: child class can overwrite method inherited from parent
- Abstraction: hiding class members(properties/methods) / details that don't matter
- OOP principles
- OOP in Js
- Class in Java === Prototype in JavaScript
- Instantiation in Java === Prototypal Inheritance/delegation in Js
- 3 Ways of creating Prototypal inheritance
- Constructor functions
- ES6 Classes: most prefered
- Object.create()
- Prototypes
- sort of like a class in Java
Class.prototype.somemethod()
on Class prototype__proto__
property on objects- linkes object to its class properties
- refer to implementation
- Prototypal Inheritance on Built-In Objects
- Prototypal Inheritance is how js classes work
__proto__
to lookup parent chain...from child object to top-level parents- refere to implementation
- ES6 Classes
- Makes it easier for devs from OOP background to adopt to js classes
- syntactic sugar for function constructors
- Classes in js doesn't really work like traditional classes in languages like Java
- 2 types of implementation
-
- Class expression
-
- Class declaration
- most preferred by devs from oop background
-
- Key things about classes
- Classes are NOT hoisted
- Classes are first-class citizens: can be passes into functions and return them from fnxs
- Classes are executed in strict mode
- refer to implementation
- Makes it easier for devs from OOP background to adopt to js classes
- Getters and Setters
- help access methods as properties instead of funtions
instance.age
instead ofinstance.age()
- Getters
- Help perform calculation before while getting
- Setters
- Help perform data validation before setting data to class property
- Regular methods
- Good practice to return the
this
object onmethod setters
for method chaining purposes - while getters return
properties
, setters returnthis
- Especially methods that changes object state(s)
- Chain of responsibility design principle
- Good practice to return the
- Getter or setter property already existing in the constructor should recreate that property with diff name like - starting with '_' underscore
- help access methods as properties instead of funtions
- Static Methods
- not added to
.prototype
property- not inherited by instances
- they are helper methods
- not added to
- Object.Create()
- create an instance from a prototype
const erb = Object.create(PrototypeClass)
- Inheritance Between "Classes"
- Using Constructor functions
- using
.bind()
andObject.create(somePrototype)
- refer to implementation
- using
- Using ES6 Classes
- using
extends
andsuper()
- refer to implementation
- using
- Using Object.create()
- linking objects to create inheritance
- refer to implementation
- Using Constructor functions
- Encapsulation
#privateFields
#privateMethods
- refer to implementation
- Planning a Web Project
- User Stories: Description of the application's functionality from the user's perspective.
- All user stories put together decribe the entire application
- As a [user], I want [an action] so that [a benefit]
- Answers Who? What? Why?
- Features: exact features to make user stories work as intended
- Flowchart: what it will be built
- Architecture: how it will be built. It gives structure.
- How code is organized into different modules, classes, and functions
- User Stories: Description of the application's functionality from the user's perspective.
- Development steps (Mapty project)
- User stories
- As a user, I want to log my running workouts with location, distance, time, pace and steps/minute, so that I can keep a log of all my running
- As a user, I want to log my cycling workouts with location, distance, time, speed and elevation gain, so that I can keep a log of all my cycling
- As a user, I want to see all my workouts at a glance, so that I can easily track my progress over time
- As a user, I want to also see my workouts on a map, so that I can easily check where I work out the most
- As a user, I want to keep all my workouts when I leave the app and come back later, so that I can keep using the app over time
- Features on user stories
- Map for user to click and add new location (best way to get user coordinates) using
- Geolocation to display map at current location
- Form to input rest of data - distance, time, space, steps/minute
- Form to input distance, time, speed, steps/minute
- Form to input distance, time, speed, elevation gain
- Display all workouts in a list
- Display all workouts on the map
- Store workout data in browser using local storage API
- On page reload, read the saved data from local storage and display data
- Map for user to click and add new location (best way to get user coordinates) using
- Flowchart
- Architecture: how code will be organized
- User stories
- Displaying map using Leaflet library
- the order of js script in html head:
- each script will have access to the global members (methods/properties) of the preceeding js scripts, but not vice versa
- the order of js script in html head:
- Displaying map and form
submitEvent.preventDefault()
=> very key that it prevents form reload
- Refactoring for Project Architecture
- Refactor spagetti code to OOP-based code
- To re-point from method caller to app instance, use:
.bind(this)
on event handlers
- Managing workout data: creating classes
- desiging Workout parent class, with Cycling and Running a children through inheritance
- event delegation
- add event to common / parent element
- select the scope of each child element using
closest()
- process that target child element...
- help to listen for event on child element that's not rendered yet (generated dynamically), by targeting the parent element which there already and then traversing down to the child element by the time it's present
- Working with local storage
- storing works in browser local storage
- object loses its prototype chain when restored from local storage
- Asynchronous
- Synchronous
- code runs and execute line by line (in the execution thread) in the order written
- example
const a = 'a'; alert('text'); const b = 'b';
- each line of code waits for previous line to finish
- Problem: if a line of code takes a long time to run
- it blocks the rest of the execution
- long-running operations block code execution
- Async
- means 'not occuring at same time'
- code is executed after a task that runs in 'bg' finishes
- code is non-blocking
- callbacks alone doesn't make code asyncronous
- since called by another fxn (and not us), () brackets is not required
- Synchronous
- Promises
- an object that is used as a placeholder for the future result of an async operation
- simply a container for a future value, that will be delivered asynchronously
- eg: lottery and bet ticketing
- benefits:
- replaces useage of events & callbacks to handle async results
- easy chain of promises for sequence of async operations, instead of callback hell
- life cycle
-
- Pending state: before future value is available
-
- Settled states: async tasks has finished. either
- Fulfilled: Success! The result is now available
- Rejected: An error happened
-
- To consume a promise,
- you need to have a promise already
- or need to create one
- where a Promise is returned
- the
.then()
is called on it for the value response.json()
also returns a Promise, thus.then()
needs to be called on it
- the
- Error handling
.catch(err => ...)
- each callback error can be caught/handled
- need to be handled globally by being the last in the callback chain
.finally()
- runs whether promise is success / not
- use case: handle loading spinner
- hide spinner wherether success / not
- Throwing error manaully
- what if 404, means promise was not rejected(no error occurs), success just that no data is found
- this needs to be handled manually
- handle custom error with the
.ok
property on responseif(!esponse.ok) throw new Error('sth went wrong')
- manaually throw any error, that the Promise global catch() might not be able to catch automatically
- what if 404, means promise was not rejected(no error occurs), success just that no data is found
- an object that is used as a placeholder for the future result of an async operation
- How async works behind the scenes in the runtime within the
- call stack
- web API
- callback queue
- micro-task queue
- Creating promises
- Promises are consumed most often, but a use case for creating one is by wrapping callback fnx with a Promise
- this is called Promisifying
- promising Function should return a
new Promise((resolve, reject)...)
like this
const downloadImage = function (imgPath) { return new Promise((resolve, reject) => { // returns a promise // do some work resolve(theImageResult) // work leading to error reject(new Error('Image not found')) }) } downloadImage.('path/img').then(img => console.log(img)) .catch(err => console.log(err))
- note that
resolve
andreject
are callback functions - Creating promise and resolving immediately using static method....
Promise.resolve()....Promise.reject()
- if resolve has no parameter, means it returns nothing
- waiting for all promises:
Promise.All()
- Promises are consumed most often, but a use case for creating one is by wrapping callback fnx with a Promise
- Async / await
- mark function as async
- has at least one await operation
- await any funtion that returns a promise
- it's is synthetic sugar for writing Promises/then
- makes code looks syncronous but it's async under the hood
- Handling async / await error
- use try-catch block
- never ignore handling error esp with async functions
- Returning values at top-level funtion
.then()
can be called on async methods but not a good practice- mixes async with promises
- use async with IIFE with await with try/catch
- use await to get the value from a Promise operation
- if
Promise{<pending>}
, then justawait
for the result
- Running promises in parallel
- when promise operations are independable, it's good to run them in parallel using
-
await Promise.All([...promises])
- it short-circuit when one fails
- Promise Combinators
- Promise.All.......returns an array
- short-circuits if there's a rejection
- important
- Promise.Race......returns only one value, res[0]
- used with timeout() to cancel long request
- important
- others:
- Promise.settleAll
- Promise.any
- map with asyn / await
- since
createImage()
is a promise, it needs to be awaited thus callback param should be asynced - map() with Promise.all() is very handy
- since
try { const imgs = imgArr.map(async img => await createImage(img)); // returns array of Promises const imgsEl = await Promise.all(imgs); // await them to get the fulfilled values from the promise array console.log(imgsEl); } catch { }
- Promise.All.......returns an array
-
Mordern Js Development Process
- Developement: Modules, 3rd-party packages
- Build process: Bundling -> Transpiling/Polyfilling (uing Babel) ...(automate build tools bundler tools like Webpack or Parcel)
- Production: Js Bundle into prod
- Transpiling simply means taking out ES6 code and converting it into ES5 so it can be read by all browsers
-
Overview of modules
- Modules: reusable piece of code that encapsulates implementation details
- Usually standalone file
- main goal is to encapsulate functionality, to have private data and to expose a public API
- Importance
- small building blocks to build complex apps
- can be developed in isolation w/out thinkin' of the entire codebase
- organize codebase
- code reuse
-
How module is diff from regular js script
- Module supports imports and exports
- Has html linking of
<script type='module'>
instead of<script>
- The Top-level
this
value isundefined
, butwindow
for script - Top-level variables are only scoped to that module, but global variable for script
- Module's file dependency downloading is async. but sync for script
-
How ES6 Modules are imported to a project 'index.js' script file
- Given index.js making with other external modules imported, this is what happens:
- Index.js is parsed (read w/out execution)
- External imported modules are downloaded asynchronously
- Link imports to external module's exports variables
- Execute external module
- Execute my index.js module
- Given index.js making with other external modules imported, this is what happens:
-
Importing and exporting modules
- Named imports & exports
- Default imports & exports
- Import can have any name and not wrapped in
{}
because... - can have only one default export per module file, but you can have as many named exports
- Import can have any name and not wrapped in
- Aliasing imports and even aliasing exports with
as
- Can import everything - the alias becomes an object
import * as ShoppingCart from './shoppingCart.js' ShoppingCart.totalCost(); ShoppingCart.carts;
- NB: imports are live connection to exports
- Imports are not copies of exports
- Imports are organized in objects -
{var1, var2, ...}
- Imports varibles are passed by reference
- Top-level imports are executed first
-
Other Types of module system
- AMD Modules
- CommonJS Modules
- used in node
- uses
export.someVariable = ...
for export andconst { someVariable } = require('./someModule.js')
for import
-
Intro to NPM
- Why the need for npm ?
- Back in the days, external libraries are included in the HTLM file using the
<script>
tag, which exposes global variables that could be used - similar done in the Mapty project - This approach creates problems in huge apps as it's not manageable
- First, doesn't make sense for html file to load dependencies
- Also, normally the dependencies (like Boostrap files) will be added into the project offline. With this, when a new version of the dependencies are released, one has to manually go their site, download the updated version and manually reconfigure into the project. This is a huge pain and not ideal.
- Solution: NPM provided a single centralized place where dependencies are managed with ease.
- How? NPM is initialized in each project to create a
package.json
file package.json
stores all dependencies independencies: {}
block- Any installed module dependency gets stored in the node_modules folder
- Each file there can be inspected (for what variables are imported), imported and used
import cloneDeep from './node_modules/lodash-es/cloneDeep.js'; // import cloneDeep from 'lodash-es'; // shorter const myobject = { name: 'erb', age: 25 }; const result = cloneDeep(myobject); // imported member is used as function...as intended after checking the cloneDeep.js file console.log(result);
- How? NPM is initialized in each project to create a
- NB: Never include/share the
node_module
folder to Github or another matchine. It can always be restored withnpm install
command
- Back in the days, external libraries are included in the HTLM file using the
- Why the need for npm ?
-
Building with Parcel and NPM scripts
- Parcel is a module bundler that works out of the box, without configuration
- Other similar one is Webpack.
- Parcel gets to the
"devDependency": {}
block once installed dependency
block vsdevDependency
block (inpackage.json
)- Dependencies, when installed. are used in the code and needed to run...eg: Express
- DevDependencies, when installed, are used as build tools (by developers) to only develop...eg: unit tests, liveserver, js transpilation, minification, etc
- NPM scripts
- allows usage of installed devDependencies. eg;
- inside
package.json
"scripts": { "mystart": "command with here" }
- inside the CLI
$ npm run mystart
- It's recommended to install tools locally instead of globally
- Parcel hot reload script - makes it easier to update only small component, without reloading the whole app
- Polifilling
-
Clean and modern Js techniques and best practices
- Write readable code
- write code others understand
- code that your future self will still understand
- avoid too 'clever' and overcomplicated solutions
- use descriptive variable names: what they contain
- use descriptive function names: what they do
- General
- use DRY principle (refactor code)
- don't pollute global namespace, encapsulate instead
- don't use var
- use strong type checks (=== and !==)
- Functions
- functions should be cohesive - do only one thing
- don't use more than 3 function parameters
- use default parameters when neccessary
- use arrow functions when makes code more readable.
- Good use-case is arrow functions as callbacks in array methods like map, foreach, etc
- OOP{}
- use ES6 classes
- encapsulate data - don't mutate directly from outside the class. use public API to manipulate the data
- implement method chaining
- don't use arrow functions as regular class methods because you will not get access to the 'this' keyword on the object
- try avoid arrow function usage as methods to prevent errors regarding 'this'
- Avoid nested code
- use early return (guard clauses)
- use ternary / logical operators instead of 'if'
- avoid for-loops, use array methods instead - like maps, filter, reduce
- avoid callback-based async APIs
- Asynchronouse code
- consume promises with async/await instead of 'then' for best readability
- run promises in parallel (
Promises.all
) whenever possible. it's faster - handle errors and promise rejections
- Write readable code
-
Programming paradigms
- Imperative programming
- Developer explains "How to do things"
- Developer explains every single step to follow to achieve result
- doubling
const arr = [2, 4, 6, 8] const doubled = []; for (let i = 0; i < arr.length; i++) { doubled[i] = arr[i] * 2; }
- Declarative programming
- Developer tells "What to do"
- Developer describes the way and the computer achieve the result
- The HOW is abstracted
const arr = [2, 4, 6, 8] const doubled = arr.map(x => x * 2);
- This has given rise to Functional programming, a declarative programming paradigm
- Based on the idea of writing software by combining many pure functions, avoiding side effects and avoiding mutating data
- Side effects: means, modification of any data outside of function
- Pure functions: functions without side effects.
- does not modify/mutate/manipulate any external variables, only locals
- does not depend on any external variables outside its scope, only locals
- all data dependancies (ie. initial data) should be passed as param, in order not to modify data outside its scope
- mostly returns a new data/instance/object
- given same input, always returns same output
- Immutability: original data (state) is never modified.
- Instead, the data is copied and the copy is mutated and returned.
- makes it easier to track the flow/state of the data throughout the application...make code less buggie
- Immutable object
Object.freeze()
to freeze object making its immutable- ie, prevents adding new properties...but property values can be modified
- Functional programming
- uses more of Pure functions
- uses less Impure funtions
- not only modifying original data but also creating console logs
- impure functions are kept at the end of the chain for outputs
- Functional programming and declarative Syntax techniques
- Try avoid data mutations
- use built-in methods that doesn't produce side effects
- use data transformation methods like .map(), .filter() and .reduce()
- use array and object destructuring
- use the spread operator
- use ternary operator
- use template literals
- Imperative programming
-
Project Overview and Planning
- Overview
- The application basically allows users to search for recipes and display them on the UI.
- Users can post and update recipes.
- Planning (User Stories -> Features -> Flowcharts -> Architecture)
- User Stories
- As a user, I want to search for recipes, so that I can find new ideas for meals
- As a user, I want to be able to update the number of servings so that I can cook meal for different number of people
- As a user, I want to bookmark recipes, so that I can review them later
- As a user, I want to be able to create my own recipes, so that I have them all organized in the same app
- As a user, I want to be able to see my bookmarks and own recipes when I leave the app and come back later, so that I can close the app safely after cooking
- Features (for user stories)
- For Story 1;
- Search functionality: input field to send request to API with searched keywords
- Display results with pagination
- Display recipe with cooking time, servings and ingredients
- For Story 2;
- Change servings functionality: update all ingredients according to current number of servings
- For Story 3;
- Bookmarking functionality: display list of all bookmarked recipes
- For Story 4;
- User can upload own recipes
- User recipes will automatically be bookmarked
- User can only see their own recipes, not recipes from other users
- For Story 5;
- Store bookmark data in the browser using local storage
- On page load, read saved bookmaks from local storage and display
- For Story 1;
- Flowchart
- The start (pill-shape) of the chart is always an event
- Flowchart Part 1
- Flowchart Part 2
- Architecture
- User Stories
- Overview
-
Architecture (deep-dive)
- What
- structure on how software is will be built.
- Why
- Gives structure. Organizes code into different modules, classes, and functions, holding the software together
- Code maintainability. Easy for future changes. Project is never done!
- Expandability. Ability to add new features
- Selection: MVC architecture
- Components of any architecture
- Business logic
- The actual business problem
- What business does and need
- Very high-level. In no specific prog lang
- Business requirement
- Mostly about the service layer
- State
- stores the data about the application
- known as the "single source of truth"
- kept in sync with UI and vice-versa
- state libraries exist. Redux, etc.
- HTTP library
- making and receiving requests over the internet
- Application logic (router)
- implementation of the business logic and the app itself
- handles UI navigation and UI events
- the entire app itself composing of the core business logic
- Presentation logic (UI layer)
- code concerned about the visible part of the application
- essentially displayes the application state
- interation about the user
- Business logic
- MVC
- Model: Business Logic, State, HTTP Library
- Controller: Application Logic
- View: Presentation Logic
-
- Architecture
- Files structure
- helpers | config => models/services | config => controllers | config => views
- What
-
Misc impl
- Parcel
- Page is served with Parcel
- Parcel need a way to access static files like images, therefore need to import images from images folder to js file
- For images in dynamic html markup in js, need to import images the html with Parcel
- Config file
- stores important data about the app which are constant
- Error handling
- error messages should be intrinsic property of the view layer
- Pagination
- logic behind
- data attribute on
<btn>
element to store data - data attribute eg.
data-someData=3
help to pass data from ui to handler- turns to
dataset.somedata
in non-template mode
- turns to
- Generating updating algorithm
- only redering dom elements whose text values changed
- Bookmarking
- API request timeout
- Realizations
- Parent class can access members of child class
- Purple numbers in console are Numbers, while White are String type
- The
this
keyword in event handler points to the caller by default. To change the reference point to the current object, outsource the handler block to a function and use the.bind(this)
on the function
- Js docs
- Deploy:
- forkify deployed manaually with
dist\
...could have been moved to a new git repo but want everything to be in one repo
- forkify deployed manaually with
- Parcel
- Enable strict mode in JS to write secure code
- Emojis are 2-character long not 1-character
- array[i] means the value
- object[i] means property or value
- Documented code on dev branch
- Leaflet
- Lodash
- Parcel
Inspired by © Jonas