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!']); // HTMLDivElement

Scope

  • 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 scope

IIFEs

  • "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 undefined

Temporal 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