Javascript!
Closures
var a = 10
console.log(window)
var b = 20
console.log(a)
var c = a + b
Scope
function getChildNodes() {
return this?.childNodes;
}
function append(html) {
const div = document.createElement('div');
div.innerHTML = html;
this?.appendChild?.(div);
return div;
}
getChildNodes(); // undefined
getChildNodes.apply(document.body); // NodeList
append('Hello World!'); // undefined
append.apply(document.body, ['Hello World!']); // HTMLDivElementScope
- Functions are assigned scope by the object instance they belong to, but only as long as that object is known at call-time.
- Dissociating a function from its object by assigning it to a variable reference means it no longer knows its scope.
- You alter a function's internal scope using the apply, call, or bind functions.
- All 3 signatures take the intended scope as the first parameter.
- Apply accepts a 2nd parameter, which is an array of all function inputs.
- Call accepts a list of N parameters, which correspond to function inputs.
- Bind takes the same signature as call, but returns a function reference that will be called with those parameters when it's invoked.
Hoisting
const foo = () => {
console.log('foo!');
};
foo();
bar();
baz();
function bar() {
console.log('bar!');
}
function baz() {
var fooVar = 'fooVar';
console.log(fooVar);
console.log(barVar);
console.log(bazVar);
var barVar = 'barVar';
const bazVar = 'bazVar';
}
Arrow Functions
const getChildNodes = () => {
return this?.childNodes;
};
getChildNodes(); // undefined
getChildNodes.apply(document.body); // undefined
const MyAPI = {
notifyIfRemoved = (element, callback) => {
const observer = new MutationObserver((entries) => {
const [entry] = entries;
if (entry.removedNodes.length) {
this.log('Node Removed: ', entry.removedNodes[0]);
observer.disconnect();
callback.apply(element);
}
});
observer.observe(element);
},
log = (message, ...args) => {
console.log(`MyAPI: ${message}`, ...args);
},
}
const div = document.createElement('div');
div.innerHTML = 'Hello World!';
document.body.appendChild(div);
MyAPI.notifyIfRemoved(div, function() {
console.log('Removed node! ', this.innerHTML);
});
Arrow Functions
- Are not hoisted, because they're always either assigned to a variable, or immediately invoked.
- They do not self-bind this scope.
- They resolve their scope by traversing the scope resolution chain.
- They do not allow you to modify their scope at call time.
- Calling them with apply, call, or bind has no effect on them whatsoever.
Function Declarations
- Declared with the "function" keyword
- Not assigned to a variable reference
- Not passed as a parameter
- Always hoisted to the top of the block
function foo () {
console.log('I am a function declaration!');
}
Function Expressions
- Declared with the "function" keyword
- Assigned to a variable reference
- Passed as a parameter
- NOT hoisted to the top of the block
const foo = function () {
console.log('I am an anonymouse function expression!');
}
const bar = function bar () {
console.log('I am a named function expression!');
}
const MyAPI = {
baz() {
console.log('I am a function expression that is an object member!');
}
}
const baz = MyAPI.baz; // Function expression that no longer knows its scopeIIFEs
- "Immediately Invoked Function Expression"
- Typically used to avoid pollution of the block
(() => {
function foo() {
console.log('I only exist inside this block.');
}
var bar = 'I only exist inside this block.';
})();
console.log(`I would throw an error trying to reference ${bar}.`); // ReferenceError: bar is undefinedTemporal Dead Zone
(() => {
var firstExample = 'First Example';
console.log(firstExample); // 'First Example'
var secondExample;
console.log(secondExample); // undefined
secondExample = 'Second Example';
console.log(secondExample); // 'Second Example'
console.log(thirdExample); // undefined
var thirdExample = 'Third Example';
console.log(thirdExample); // 'Third Example'
console.log(fourthExample); // Temporal Dead Zone!
let fourthExample = 'Fourth Example';
console.log(fourthExample); // 'Fourth Example'
})();Temporal Dead Zone
- var declarations are hoisted to the top of the block
-
var initializations happen on the line where they're written
- Declaring and initializing a var further down in your block will hoist the declaration to the top, but it will be undefined until initialized
-
let and const declarations are considered block-level, but their definition is still hoisted to the top of the block
- These types of variables are untouchable until initialized, so if you reference them before that, you get a ReferenceError
Prototype Chaining
function Animal(type) {
this.type = type;
this.hunger = 100;
this.isWalking = false;
}
Animal.prototype = {
eat() {
this.hunger = Math.max(this.hunger - 1, 0);
},
stop() {
this.isWalking = false;
},
walk() {
this.isWalking = true;
},
}
function Mammal(type) {
Animal.call(this, type);
}
Mammal.prototype = Object.create(Animal.prototype);
function Dog(name) {
Mammal.call(this, 'dog');
this.name = name;
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.speak = function(message) {
console.log(`${this.name}: Woof!`);
};
function Person(name, age) {
Mammal.call(this, 'human');
this.name = name;
this.age = age;
}
Person.prototype = Object.create(Mammal.prototype);
Person.prototype.speak = function(message) {
console.log(`${this.name}: ${message}`);
};
const me = new Person('Richard', 'too old');
const dog = new Dog('Fido');
me.speak("I'm a real person!"); // "Richard: I'm a real person!"
dog.speak("I'm also a real person!"); // "Fido: Woof!"
console.log(me instanceof Person); // true
console.log(me instanceof Mammal); // true
console.log(me instanceof Animal); // true
console.log(me instanceof Dog); // false
Prototype Chaining
class Animal {
constructor(type) {
this.type = type;
this.hunger = 100;
this.isWalking = false;
}
eat() {
this.hunger = Math.max(this.hunger - 1, 0);
}
stop() {
this.isWalking = false;
}
walk() {
this.isWalking = true;
}
}
class Mammal extends Animal {}
class Dog extends Mammal {
constructor(name) {
super('dog');
this.name = name;
}
speak(message) {
console.log(`${this.name}: Woof!`);
}
}
class Person extends Mammal {
constructor(name, age) {
super('human');
this.name = name;
this.age = age;
}
speak(message) {
console.log(`${this.name}: ${message}`);
}
}
const me = new Person('Richard', 'too old');
const dog = new Dog('Fido');
me.speak("I'm a real person!"); // "Richard: I'm a real person!"
dog.speak("I'm also a real person!"); // "Fido: Woof!"
console.log(me instanceof Person); // true
console.log(me instanceof Mammal); // true
console.log(me instanceof Animal); // true
console.log(me instanceof Dog); // false
fn.
Javascript!
By Richard Lindsey
Javascript!
- 82