- Explain Event Delegation
- Describe Event Bubbling
- What are benefits of a
named function
over aanonymous function
? - Difference between
target
andcurrentTarget
? - Explain why the following doesn't work as an IIFE
- Difference between a variable that is: null, undefined, or undeclared
Synchronous
programming means that, barring conditionals and function calls, code is executed sequentially from top-to-bottom, blocking on long-running tasks such as network requests and disk I/O.Asynchronous
programming means that the engine runs in an event loop. When a blocking or, long time operation is needed, the request is started, and the code keeps running without blocking for the result (running in other place). When the response is ready, and interrupt is fired, which causes an event handler to be run, where the control flow continues. In this way, a single program thread can handle many concurrent operations.- User interfaces are asynchronous by nature!
- Good to hear:
- An understanding of what is blocking means, and the performance implications.
- An understanding of event handling, and why its important for UI code
- Red flags:
- Unfamiliar with the terms asynchronous or synchronous
- Unable to articulate performance implications or the relationship between asynchronous code and UI code.
Js event listeners fire not only a single DOM but on all it's descendants
Also known as "propogation". Events on an element will "bubble up" and also fire on all parents.
- Handy function self-reference: necessary when referencing from inside the function, e.g.
recursion
- More debuggable stack traces: helpful when seeing
stack trace
while debugging - More self-documenting code: when using the function as callback it is more documented
So, in production code we should use Name Function
always.
target
is actual thing what is clicked and currentTarget
is where the event listener is attached to.
null
is a primitive type in JavaScript that represents an intentional absence of a value -- it is set on-purpose
- One way to check for
null
is JS is to check if a value loosely equal tonull
using double equality (==) operator:
console.log(null == null); // true
console.log(null == undefined); // true!
So, null
is only loosely equal to itself
and undefined
(not to the other falsy values).
- Other way is to check using strict equality (===)
console.log(null == null); // true
console.log(null == undefined); // false
so, that's perfect!
- An alternative method of checking for
null
is based on knowing that null is falsy, but empty objects are truthy, sonull
is the only falsy object.
console.log(typeof null === 'object' && !null); // true
console.log(typeof {} === 'object' && !{}); // false
The value null is falsy but empty objects are truthy, so typeof null === "object" && !null
is a convenient way to check for null.
function foo() {
// I am known as a definition or statement
}
var foo = function () {
// i am an expression, i resolve to a value, even if just 'undefined'
// expression = MDN - an expression is any valid unit of code that resolves to a value
};
Any function that returns a new object
which is not a Constructor function (not called with new
keyword).
-
Undeclared: never used/defined before const bar = foo + 1;
console.log(typeof bar); // undeclared, but also returns "undefined"
-
Undefined:
-
variable declared but no defined value (not initialized)
-
object/array exists but nothing at that key/index
-
function exists but doesn't return anything
-
falsy
let foo; const bar = foo; // foo is undefined console.log(typeof foo); // "undefined" as a string console.log(foo === undefined); // true boolean const baz = 'undefined'; console.log(baz === undefined); // false. Hooray, I guess
-
-
null:
- null has a value. It's value is null
- null is a "nothing" value
- not zero, not an empty string/object/array
- falsy
let foo = null; console.log(foo === null); // true boolean
Two Principles:
- Goes through the code line-by-line and runs/executes each line -- known as the
thread of execution
- Saves
data
like strings and arrays so we can use that data later -- in its memory (we can even save code e.g. functions)
The get
syntax binds an object property to a function that will be called when that property is looked up.
Note:
- It can have an identifier which is either a number or a string
- It must have exactly zero parameters
- It must not appear in an object literal with another
get
or with a data entry for the same property. e.g.{ get x() }, get x() {})
and{ x: ..., get x() {}}
are forbidden.
Examples:
const obj = {
log: ['foo', 'bar'],
get latest() {
if (this.log.length === 0) return undefined;
return this.log[this.log.length - 1];
}
}
console.log(obj.latest); // "bar"
```
- Deleting a getter using the `delete` operator
```js
delete obj.latest;
var o = { a: 0 };
Object.defineProperty(o, 'b', {
get: function () {
return this.a + 1;
},
});
console.log(o.b); // runs the getter, which yeilds a + 1
const name = 'foo';
const o = {
get [name]() {
return 'bar';
},
};
console.log(o.foo); // "bar"
When using get
the property will be defined on the property of the object while using Object.defineProperty()
the property will be defined on the instance it is applied to.
class Foo {
get hello() {
return 'world';
}
}
const o = new Foo();
console.log(o.hello); // "world"
console.log(Object.getOwnPropertyDescriptor(o, 'hello')); // undefined
console.log(Object.getOwnPropertyDescriptor(Object.getPrototypeOf(o), 'hello'));
// // { configurable: true, enumerable: false, get: function get hello() { return 'world'; }, set: undefined }
A null value represents nothing, nonexistent or invalid object or address. It converts to 0
in simple arithmetic operations and it's global object. null == false
gives us false.
The global undefined property represents the primitive value undefined
. It tells us something has not assigned value; isn't defined. undefined isn't converted into any number, so using it in maths calculations returns NaN
.
NaN (Not-A-Number) represents something which is not a number, even though it's actually a number. It's not equal to itself and to check if something is NaN we need to use isNaN() function.
All of the above are falsy values so they convert to false.
const arr1 = ['a', 'b', 'c'];
const arr2 = ['b', 'c', 'a'];
console.log(
arr1.sort() === arr1,
arr2.sort() == arr2,
arr1.sort() === arr2.sort()
);
// output: true true false
Explanation: the array sort()
method sorts the original array and returns a reference to that array. The sort order of the array doesn't matter when we're comparing objects. Since arr1.sort()
and arr1
point to the same object in the memory, the first equality test return true
.
Second comparison also return true
for same reason.
Third comparison returns false
cause the sort order of arr1.sort()
and arr2.sort()
are the same; however, they still point to different objects in memory.
const str = 'constructor';
console.log(str[str](01));
Answer 1 --> str.constructor(01) returns "1"
function b() {
console.log(`the length is ${this.length}!`);
}
let a = {
length: 10,
getLength: function(b) {
arguments[0]();
}
}
a.getLength(b, 5);
Answer: "the length is 2!" -- cause inside b() this = arguments!
function printIt(value) {
return 'Hello ';
}
const string = printIt `World`;
console.log(string);
Answer: Hello
function foo() {
let a = (b = 0);
// declares a local variable 'a' and a global variable 'b'. There is no variable 'b' declared in the foo() scope or global scope. So JS interprets 'b=0' expression as window.b = 0
a++;
return a;
}
foo();
typeof a; // ???
// 'undefined' -> 'a' is declared within 'foo()' scope
typeof b; // ???
// 'number' -> 'b' is global variable with value 0
const arr = ['sajib', 'khan'];
arr.length = 0;
arr[0]; // ???
If we reduce the value of the length
property has the side-effect of deleting own array elements whose array index is between the old and new length values.
So, when JS executes arr.length = 0
, all the items of the array arr are deleted
.
Answer: undefined
- There must be an outer enclosing function that executes at least one.
- From inside that functions return at least one inner function(s).
// example 1
var myModule = (function () {
'use strict';
return {
publicMethod: function () {
console.log('Hello World!');
},
};
})();
myModule.publicMethod(); // Hello World!
// example 2
define('foo', function () {
// define run the function automatically and assign the return value in 'foo'
var o = { bar: 'bar' };
return {
bar: function () {
console.log(o.bar);
},
};
});
One important thing is to the File content is also a module. e.g. ES6 + module pattern
- By default, File based module is singletone, mean it has only one instance. If we import the File from inside different files it creates only one instance and share with all.
// foo.js
var o = { bar: 'bar' };
export function bar() {
console.log(o.bar);
}
import { bar } from 'foo.js';
bar(); // bar
import * as foo from 'foo.js';
foo.bar(); // bar
We can use Object.preventExtensions()
method to prevent adding new properties to an object:
'use strict';
let myObj = {
one: 1,
two: 2
};
console.log(myObj.one); // 1
console.log(myObj.two); // 2
Object.preventExtensions(myObj);
try {
myObj.three = 3;
} catch (e) {
console.log(e.message);
// "Cannot add property three, object is not extensible" }
console.log(myObj.three); // undefined
- In
strict mode
browser will throw the error shown in the catch block - In
non-strict node
, browser attempt will just fail silently - We can still delete properties, unlike when using
Object.freeze()
- This prevents addition of own properties, but we can still add to the object's prototype
const uniqueValues = [...new Set([1, 2, 3, 3])];
console.log(uniqueValues);
// [1, 2, 3]
const onlyTrueValues = [0, 1, "Alice", undefined, null, false, "Bob"
.map(item => {
// ...
}).filter(Boolean);
console.log(onlyTrueValues);
// [1, "Alice", "Bob"]
console.log(
'%c This is a styled message',
'background: #222; color: #bada55; font-size: 14px; padding: 5px'
);
So, here is some example we can use console in different way:
// log a variable
const fruit = 'Apple';
console.log({ fruit }); // {fruit: "Apple"}
// %s replaces an element with a string
console.log('Hello I love %s', 'Javascript');
// %d replaces an element with an integer
console.log('Hello %d ', 1);
// %f replaces an element with a float
console.log('Hello %f ', 1.078);
// %(o|O) | element is displayed as an object.
console.log('Hello %O', { Name: 'Sidd' });
// %c | Applies the provided CSS
console.log('%cThis is a red text', 'color:red');
let emptyArr = Object.create(null);
// emptyArr.__proto__ === "undefined"
let a = 10;
let b = 20;
console.log(a, b); // OUTPUT: 10, 20
[a, b] = [b, a];
console.log(a, b); // OUTPUT: 20, 10
const isRequired = () => {
throw new Error('param is required');
};
const sayHello = (name = isRequired()) => {
console.log(`Hello ${name}`);
};
sayHello(); // throw an error
sayHello(undefined); // throw an error
sayHello(null); // no error!!
sayHello('Sajib'); // no error
// say, "?post=1234&action=edit"
const urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.has('post')); // true
console.log(urlParams.get('action')); // "edit"
console.log(urlParams.getAll('action')); // ["edit"]
console.log(urlParams.toString()); // "?post=1234&action=edit"
console.log(urlParams.append('active', '1')); // "?post=1234&action=edit&active=1"
There are some browser events that can fire many times within a short timespan very quickly, such as resizing a window or scrolling down page. This can cause a serious performance issues.
Debouncing
is one way to solve this issue by limiting the time that needs to pass by until a function is called. So, it limits the
rate at which a function can fire.
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
}
// another implementation
const debounce = (func, timer) => {
let timeId = null;
return (...args) => {
if (timeId) {
clearTimeout(timeId);
}
timeId = setTimeout(() => {
func(...args);
}, timer);
};
};
document.querySelector('input').addEventListener(
'keyup',
debounce((e) => {
console.log('e: ', e);
}, 1000)
);
This function - when wrapped around an event - will execute only after a certain amount of time has elapsed
// function to be called when user scrolls
function foo() {
console.log('You are scrolling');
}
// wrap our function in a debounce to fire once 2 seconds have gone by
let elem = document.getElementById('container');
elem.addEventListener('scroll', debounce(foo, 2000));
Ref: MDN, FreeCodeCamp
The sequence we should be think is: (priority: top to bottom)
- Is the function called by new?
- Is the function called by call() or apply()?
- Note:
bind()
effectively usesapply()
- Is the function called on a context object? e.g. ob.funcA
- DEFAULT: global object(except strict mode)
Here, 5 ways are described to covert a value to String:
const value = 12345;
// Concat Empty String
value + '';
// Template Strings
`${value}`;
// JSON.stringify
JSON.stringify(value);
// toString()
value.toString();
// String()
String(value);
// RESULT
// '12345'
Let's compare some different type of variables converting to String
// string
const string = 'hello';
string + ''; // 'hello'
`${string}`; // 'hello'
JSON.stringify(string); // '"hello"'
string.toString(); // 'hello'
String(string); // 'hello'
// number
const number = 123;
number + ''; // '123'
`${number}`; // '123'
JSON.stringify(number); // '123'
number.toString(); // '123'
String(number); // '123'
// boolean
const boolean = true;
boolean + ''; // 'true'
`${boolean}`; // 'true'
JSON.stringify(boolean); // 'true'
boolean.toString(); // 'true'
String(boolean); // 'true'
// array
const array = [1, '2', 3];
array + ''; // '1,2,3'
`${array}`; // '1,2,3'
JSON.stringify(array); // '[1,"2",3]'
array.toString(); // '1,2,3'
String(array); // '1,2,3'
// object
const object = { one: 1 };
object + ''; // '[object Object]'
`${object}`; // '[object Object]'
JSON.stringify(object); // '{"one":1}'
object.toString(); // '[object Object]'
String(object); // '[object Object]'
// symbol
const symbolValue = Symbol('123');
symbolValue + ''; // ❌ TypeError
`${symbolValue}`; // ❌ TypeError
JSON.stringify(symbolValue); // undefined
symbolValue.toString(); // 'Symbol(123)'
String(symbolValue); // 'Symbol(123)'
// undefinedValue
const undefinedValue = undefined;
undefinedValue + ''; // 'undefined'
`${undefinedValue}`; // 'undefined'
JSON.stringify(undefinedValue); // undefined
undefinedValue.toString(); // ❌ TypeError
String(undefinedValue); // 'undefined'
// nullValue
const nullValue = null;
nullValue + ''; // 'null'
`${nullValue}`; // 'null'
JSON.stringify(nullValue); // 'null'
nullValue.toString(); // ❌ TypeError
String(nullValue); // 'null'
We can see that String()
handle the null
and undefined
quite well. No errors are thrown - unless that what we want. If we are not sure about our data type then String()
is always good by default.
Spinal string case is all-lowercase-words-joined-by-dashes
// Input:
This Is Spinal Tap
thisIsSpinalTap
The_Andy_Griffith_Show
Teletubbies say Eh-oh
AllThe-small Things
// Output:
this-is-spinal-tap
this-is-spinal-tap
the-andy-griffith-show
teletubbies-say-eh-oh
all-the-small-things
Solutions:
function spinalCase(str) {
// Create a variable for the white space and underscores
var regex = /\s+|_+/g;
// Replace low-upper case to low-space-uppercase
str = str.replace(/([a-z])([A-Z])/g, '$1 $2');
// Replace space and underscore with -
return str.replace(regex, '-').toLowerCase();
}
// test here
spinalCase('This Is Spinal Tap');
- Lexical scope
Let
inside a scopeVar
inside a functionerr
in catch close: e.g. catch(err) { ... }
What are the four
things the new
keyword actually does when we put in front of a function call (aka: constructor call)?
- Create a brand new empty object (aka constructed) out of thin air.
- Newly created/constructed object is linked to (
[[Prototype]]
) the function's prototype. - Newly created/constructed object is set as the
this
binding for that function call. - Unless the function returns its own alternate object, the new-invoked call will automatically return the newly constructed object.
Undeclared
: It's never been declared in any scoped we have accessed to
Undefined
: It has beed in a scope but it does not have currently any value
NaN
(not a number) is the only value that is not equal to itself.
if (!Number.isNaN) {
Number.isNaN = function isNaN(x) {
return x !== x;
// NaN === NaN -- false
};
}
The isNaN()
function determines whether a value is NaN or not.
const a = NaN, b = 5;
isNaN(a); // true
isNaN(b); // false
isNaN('test'); // true, surprising!!
Coercion
inside the isNaN function can be surprising so we can use Number.isNaN()
method determines whether the passed value is NaN and its type is Number. It is a more robust version of the original, global isNaN().
Number.isNaN('test'); // false
Tips: typeof(NaN) returns number
!
if we want to prevent the user from pasting text into inputs by javascript we can use paste
event listeners
<input type="text"></input>
<script>
// selecting the input.
const input = document.querySelector('input');
// prevent the user to paste text by using the paste eventListener.
input.addEventListener("paste", function(e){
e.preventDefault()
})
</script>
Declaring with var
two things are happened:
- Hoist the variable at compile time.
- Initialize the Hoisted variable with
undefined
at runtime.
Declaring with let
only one thing is happened:
- Hoist the variable
function foo(bar) { | var a; // undefined
if (bar) { | if (bar) {
console.log(baz); // ReferenceError | let baz; // uninitialized
let baz = bar; | console.log(baz);
var a; | let baz = bar;
} |
} |
So, let
is hoisted but not initialized actually.
Using ES2015
classes:
class EventEmitter {
constructor() {
this.events = {};
}
on(event, listener) {
if (typeof this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
return () => this.off(event, listener);
}
off(event, listener) {
if (typeof this.events[event] === 'object') {
const idx = this.events[event].indexOf(listener);
if (idx > -1) {
this.events[event].splice(idx, 1);
}
}
}
emit(event, ...args) {
if (typeof this.events[event] === 'object') {
this.events[event].forEach((listener) => listener.apply(this, args));
}
}
once(event, listener) {
const remove = this.on(event, (...args) => {
// tricky, think how it's working!
remove();
listener.apply(this, args);
});
}
}
Using plain prototype
with factory function
const anEventEmitter = {
events: {},
on(event, listener) {
if (this.events[event] !== 'object') {
this.events[event] = [];
}
this.events[event].push(listener);
return () => this.off(event, listener);
}
off(event, listener) {
if(this.events[event] === 'object') {
const idx = this.events[event].indexOf(listener);
if (idx > -1) {
this.events[event].splice(idx, 1);
}
}
}
emit(event, ...args) {
if (this.events[event] === 'object') {
this.events[event].forEach(listener => listener.apply(this, args));
}
}
once(event, listener) {
const remove = this.on(event, (...args) => {
remove();
listener.apply(this, args);
});
}
};
const EventEmitter = () => ({
__proto__: anEventEmitter,
events: {}
})
Live Reloading
relaods or refreshes the entire app when a file changes. For example, if we have four links deep into our navigation and saved a change, live reloading would restart the app and load the app back to the initial route.Hot Reloading
only refreshes the files that were changed without losing the state of the app. If we have four links deep into our navigation and saved a change to some styling, the state would not change, but the new styles would appear on the page without having to navigate back to the page we are on because we would still be on the same page
- Order of object properties: always instantiate our object properties in the same order so that hidden classes, and subsequently optimized code, can be shared.
- Dynamic properties: adding properties to an object after instantiation will force a hidden class change and slow down any methods that were optimized for the previous hidden class. Instead, assign all of an object's properties in its constructor.
- Methods: code that executes the same method repeatedly will run faster than code that executes many different methods only once (due to inline caching).
- Arrays: avoid sparse arrays where keys are not incremental numbers. Sparse arrays which don't have every element inside them are a
hash table
. Elements in such arrays are more expensive to access. Also, try to avoid pre-allocating large arrays. It's better to grow as you go. Finally, don't delete elements in arrays. It makes the keys sparse. - Tagged values: V8 represents objects and numbers with 32 bits. It uses a bit to know if it is an object (flag = 1) or and integer (flag = 0) called SMI (
SMall Integer
) because of its 31 bits. Then, if a numeric value is bigger that 31 bits, V8 will box the number, turning it into a double and creating a new object to put the number inside. Try to use 31 bit signed numbers whenever possible to avoid teh expensive boxing operation into a JS object.
========================================================================
- Make sure the code works
- Code is easily understand, written following the coding standards (React/Redux in our case), sync with existing code patterns
- Check if it is possible to refactor to reduce duplication, write generic/re-useable code!
- Meaningful names for variables/classes/methods and following the standard convention.
- Handle JavaScript exceptions, errors, promise rejections and api errors properly
- Keep functions/classes/components small, use Functional components where needed instead Class component, avoid infinite loop- while updating state, avoid using nested if/else statements
- When new library is needed for a feature check for library size -- if lightweight (+popular) library present then check it out
- Clean code: remove unused/unreachable code/packages, keep only the useful comments, no console logs
- Check how component/page looks in different browsers (desktop and mobile)
- Check security: SQL injection, Cross-site Scripting (XSS), etc.
- Tests are readable, maintainable, trustworthy (though we're not following this now-a-days)
- Git commits are small, divided into logical parts, understandable, use branches for new feature/fix
======================================================
Some questions to check:
OOP
- How does this changes in different context? How many contexts are there?
- What is a prototype in JavaScript?
- How do you create objects in JavaScript?
- What is the module pattern? When do you use it?
- What is the factory pattern? When do you use it?
- FP
- What is immutability?
- What array methods are immutable?
- How do you change JavaScript properties while not mutating the object?
- What is a pure function?
- How many kinds of actions should a function contain?
- What are side effects?
- How do you handle side effects when you write pure functions?
- AJAX
- What are JavaScript promises?
- How do you chain promises?
- How do you catch errors when using promises?
- How do you use the Fetch API?
- What does CRUD stand for?
- How do you query Github’s API to get a list of your own repositories?
- Best practices
- Why do you avoid global variables?
- Why use strict equality (===) instead of normal equality (==)?
- How do you use ternary operators to help you write terser code?
- What ES6 features help you write terser code?
- What is event bubbling and capturing?
- How do you delegate events?
- How do you remove event listeners? When should you remove them?