Advanced JS
What is Jasmine?
a test runner and expectation/assertion library. We will be using it to run all of our tests. A test runner is a tool that is responsible for running tests that you write and logging the results of the tests for you to see.
spec file
an external js file that should contain all your tests
Modifying the Object prototype
Another anti-pattern is modifying the Object prototype as it can have many unintended consequences, which you would not expect. Let's look at this code: var obj = {name: "Elie", job: "Instructor"} for (var key in obj) { console.log(key); // name, job } Object.prototype.lastName = "Schoppik"; for (var key in obj) { console.log(key); // name, job, lastName } Whoa! By modifying the Object.prototype we can actually break our for in loops when iterating over an object, so it's always best to avoid extending the Object.prototype
global variables - again!
We previously mentioned that declaring variables in the global scope inside of functions was a bad idea because it's not readable and unecessary, but there is also another issue. We can very easily create variables in the global scope that are intended for use only in the function. Once the function has run that variable can not be garbage collected because it is in the global scope! function badIdea(){ data = "This is no good!" } badIdea() // data can still be accessed!
Memory Leaks
While memory leaks are not exactly an 'anti-pattern', they are something you should always consider when writing code. Memory leaks occur when there is memory that is not required but still allocated. In JavaScript, memory is deallocated/de-referenced through a tool called "garbage collection." Garbage collection manages memory through constantly checking to see what variables are not in use by your application. With lower level languages, specifically languages like C, the developer is in charge of managing memory and needs to manually allocate and free/deallocate memory. In JavaScript, we do not have to worry about manually managing memory, but we can still make mistakes which cause variables that are not being used to not be collected by the garbage collector. Without getting into too much detail, memory leaks occur when there are existing references which the garbage collector can not remove, even if the developer is done using that data. Let's look at some examples: global variables - again! timers, DOM references and closures
Greedy matching Here are some special characters you'll find working with regular expressions.
? - This matches at most 1 of the previous match. In other words, it marks the previous the previous match as optional. var match1 = "cookies".match(/cookies?/) // ['cookies'] var match2 = "cookie".match(/cookies?/) // ['cookie'] var match3 = "cookies".match(/cookiess?/) // ['cookies'] var match4 = "cookies".match(/cookiesss?/) // null + - This matches one or more of the previous match. var match1 = "cookiessssssssss".match(/cookies+/) // ["cookiessssssssss"] var match2 = "cookies".match(/cookies+/) // ['cookies'] var match3 = "cookie".match(/cookies+/) // null * - This matches zero or more of the previous match. var match1 = "cookiessssssssss".match(/cookies*/) // ["cookiessssssssss"] var match2 = "cookies".match(/cookies*/) // ['cookies'] var match3 = "cookie".match(/cookies*/) // ['cookie'] We can also use the wildcard character . and the * character to match zero or more of anything. // match anything that starts with, ends with or has the letter e inside of it "elie".match(/.*e.*/gi); // ['elie'] "elephants are everywhere".match(/.*e.*/gi); // ['elephants are everywhere'] "can you think of a string containing almost all non-consonants?".match(/.*e.*/g); // null
Canvas Introduction
A canvas is an html element that allows the developer to draw shapes, text, etc. It was originally introduced by Apple as an attempt to make the web more dynamic. Here is a basic canvas tag: <canvas height="200" width="200"> This text is shown to the user if the browser does not support canvas </canvas> Your imagination is the only thing holding you back with the canvas element.
Groupings - ()
A more advanced concept in regular expressions is the idea of creating groups which you can later access. To create a group, we use the () characters. We can then refer to these groups as $1, $2, and so on. var tweet = "This is the best tweet #amazing #perfect #sogood"; var regex = /#([\S]+)/ig; var matches = tweet.match(regex); matches.map(v => v.replace(regex, 'hashtag: $1')) // ["hashtag: amazing", "hashtag: perfect", "hashtag: sogood"] If we want to iterate over multiple groups, we can loop and continue to use the exec function
Thread
A process can have many threads if multi-threading is supported. JavaScript only supports a single thread, so there can only be a single sequential flow of control within a program. So how are we able to write asynchronous code that "seems" to be doing multiple things at once? The answer is that it really is not, it just may appear so. Let's learn more about some key terms related to this.
What is a regular expression?
A regular expression is a sequence of characters that create a pattern. We can use regular expressions to search for and find these patterns in strings, which enables us to perform complex pattern-matching on strings. Let's imagine we are given the following string: "The quick brown fox jumps over the lazy dog." If you were tasked with finding the word "jumps," that would not be too difficult. But what happens when you are tasked with finding three or four word characters that start with a vowel and contain at least one other vowel? Finding matching patterns like this requires something a bit more powerful: that's where regular expressions, or 'regex,' come in. Regular expressions are commonly used to validate emails, phone numbers, zip codes, passwords, and much more. They are also used to find or replace characters in text files, which makes knowing them very helpful. So let's get started with creating a regular expression and seeing which JavaScript methods can help us with finding patterns.
Revealing Module Pattern
A slight variation on the module pattern is the revealing module pattern. In this pattern, public functions are defined before the return {} inside of the module and inside the return {} we use references to previously defined functions. This simplifies the return statement and can make the code easier to read. Let's see an example. var myRevealingModule = (function(){ var start = 0; function incrementNumberPrivate(){ start++; } function incrementNumberPublic(){ incrementNumberPrivate(); } function initialize(){ incrementNumberPublic(); } function getPrivateNumber(){ return start; } return { init: initialize, add: incrementNumberPublic, status: getPrivateNumber } })()
Abstraction
Abstraction is the result of a good object oriented design. Rather than thinking about the details of how a class works internally, you can think about it at a higher level. You can see all of the functions that are made available by the class and understand what the class does without having to see all of the code. Continuing with our example, if you had a deck of cards class and you saw that you could call the .shuffle() function or the .deal() function, you would have a good understanding of what the class does and what functionality it provides without having to understand how the functions are working internally.
Class syntax
Although this addition to the language is not widely loved by the community, it is important to understand because modern frameworks like React and Angular 2 use it quite frequently. All that this syntax does is obfuscate constructor functions and prototype properties/methods. There is nothing new going on here; it is just a layer of abstraction. Let's recall what this looked like in ES5: function Person(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } Person.prototype.sayHi = function(){ return this.firstName + " " + this.lastName + " says hello!"; } Person.isPerson = function(person){ return person.constructor === Person; } Here is what an ES2015 Implementation looks like: class Person { constructor(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } sayHi(){ return `${this.firstName} ${this.lastName} says hello!`; } static isPerson(person){ return person.constructor === Person; } } The function called constructor MUST be named that way (since that is what is run when the new keyword is used). If you just try to run Person() you will get a TypeError with the message that "Class constructor Person cannot be invoked without 'new'". If we want to add functions directly on the "class" (which is really a function), we use the word static.
Creating Your Own Inheritance Chain
An important concept in object oriented programming is inheritance. The idea behind inheritance is that one or more parent / super classes can pass along functions and properties to other child / sub classes. function Parent(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } Parent.prototype.sayHi = function(){ return this.firstName + " " + this.lastName + " says hi!"; } function Child(firstName, lastName){ // This is how we "inherit" properties from the parent Parent.apply(this,arguments); } // This is how we inherit functions // (create a new prototype + reset the constructor) Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; var c = new Child("Bran", "Stark"); c.sayHi() // Bran Stark says hi! So what have we done here? We've set the prototype of the Child to be a newly created object with a prototype of Parent.prototype (Object.create accepts as a parameter another object to set as the prototype).
Default parameters
Another nice ES2015 feature is the ability to add default values to parameters in our functions // OLD - causes unintended issues because 0 is falsey! function add(a, b){ a = a || 12 b = b || 13 return a + b; } add() // 25 add(0) // 25 - WHY IS THIS?? add(0,0) // 25 - WHAT IS HAPPENING?? // NEW function add(a=12,b=13){ return a+b } add() // 25 - CORRECT! add(0) // 13 - CORRECT! add(10,10) // 20 Default parameters are set by assigning them when the function is defined, as you can see.
for of loop
Another nice addition to ES2015 is the for of loop which allows us to iterate through what is called an iterable. We will not get in depth about iterables, but know that these include objects, arrays and even returned values from generators. let arr = [1,2,3,4,5]; for(let val in arr) { console.log(val); } // 0 // 1 // 2 // 3 // 4 for(let val of arr) { console.log(val); } // 1 // 2 // 3 // 4 // 5 When dealing with arrays, note the difference between a for in loop and a for of loop. The for in loop will loop over the indices of an array; the for of loop will loop over the elements of an array: let letters = ['a', 'b', 'c']; for (let idx in letters) { console.log(idx); } // 0 // 1 // 2 for (let char of letters) { console.log(char); } // 'a' // 'b' // 'c'
eval
Another very common anti-pattern in JavaScript is using the eval function. You will commonly hear that eval is "evil" - but why? Since the eval function will evaluate anything that is passed to it, using it opens yourself up to serious security threats if you are using eval with any input that can be modified externally. There are also performance issues when using it as well.
Closures
Another very common tool with functional programming is the use of closures, or functions that make use variables defined in outer functions that have previously returned. We will see how closures allow us to make use of partial application, or partially calling and applying parameters to one function. We will also make heavy use of closures when we discuss currying. Here is an example of a simple closure function outer(){ var num = 10; return function inner(newNum){ // the inner function makes use of num // which was defined in the outer function // and which has returned by the time inner makes use of it return num + newNum; } } outer()(5); // 15 outer()(10); // 20 outer(10)(); // NaN - why is this? While closures are a very powerful tool in functional programming (and in general), they can cause memory issues when not written properly.
Arrow functions
As an alternative to the keyword function, ES2015 gives us a new function syntax called arrow functions. These functions are denoted by the => characters. While they are very similar to the function keyword, arrow functions have a few key differences: If the arrow function is all on 1 line - an implicit return is added (you do not need the keyword return). If the arrow function is on more than one line, {} must be used (just like a regular function) If the arrow function takes 1 argument, you don't need to wrap that argument in parentheses (though for multiple arguments, you do) Arrow functions are always anonymous Let's see some examples: // basic examples: var add = (a, b) => a + b; var shout = str => str.toUpperCase(); var multilineArrowFunction = a => { let b = a * a; return b + a; } // callback examples: var arr = [1,2,3,4]; // function keyword syntax arr.map(function(val){ return val*2; }) // arrow function syntax arr.map(val => val *2) Another important distinction between arrow functions and functions defined using the function keyword has to do with the keyword this. Arrow functions lexically bind the this value. What does that mean? From MDN: Until arrow functions, every new function defined its own this value (a new object in case of a constructor, undefined in strict mode function calls, the context object if the function is called as an "object method", etc.). This proved to be annoying with an object-oriented style of programming. Ok, but what does that mean? Let's take a look at an example: var obj = { firstName: "Elie", sayHi: function(){ setTimeout(function(){ console.log("Hi, I am " + this.firstName); },1000) } } If you call obj.sayHi() in this example, you'll see that "Hi, I'm undefined" gets logged to the console one second later. This is because the callback to setTimeout loses the context of this, as it isn't being called as a method on obj. When using an arrow function for this callback, however, the arrow function keeps its value from the enclosing context: in this case, it will refer to the obj object, and the console.log will work as expected: var objES2015 = { firstName: "Elie", sayHi: function(){ setTimeout(() => { console.log(`Hi, I am ${this.firstName}`); },1000) } }
ES2017 Async Functions
As of ES2017, two new keywords have been added to the language: async and await. These two keywords allow us to write code that "looks" synchronous, but is actually asynchronous. We prefix our functions with the async keyword to denote that they are actually asynchronous and we invoke those functions with the keyword await to ensure that they have completed before their values are stored. These keywords are actually not new to programming languages; both Python and C# have the same concept. async and await are relatively new additions to the language, so in order to run all of these code examples, make sure you're using Node version 7.6 or higher, or are working in a modern browser (the most recent version of Chrome will suffice). Note that when you make a funciton an async function, it automatically returns a promise to you. Very commonly you'll create the promise yourself, but even if you don't, the value you return will be wrapped in a promise: async function asyncExample() { return 1; } asyncExample(); // Promise {<resolved>: 1} Let's revisit our example from before, but refactor it one more time to use async and await! Try this out in the browser: // Using Promises alone async function logJokesAbout(term) { var data = await $.getJSON(`https://icanhazdadjoke.com/search?term=${term}`); console.log("Here is the joke data!", data); } logJokesAbout("spider"); logJokesAbout("pizza"); // etc. Remember: if you return the data rather than logging it, you'll get a promise, not the actual data!
Promises in jQuery
As of version 3, jQuery has built-in support for promises! This means that when you use methods like $.get, $.getJSON, or $.ajax, you can chain .then and .catch methods to the end of them. Let's revisit some of the examples we looked at in the previous section. function getJokesAbout(term) { return $.getJSON(`https://icanhazdadjoke.com/search?term=${term}`); } getJokesAbout("spider") .then(function(data) { console.log("Here is our joke data!", data); }) .catch(function(err) { console.log("Oops, something went wrong", err); });
Design Patterns
As programming languages and programming in general has evolved and applications have grown larger and larger, best practices around designing the structure of our code have been established. Patterns are reusable solutions to solve potential issues when building applications. There are many different types of design patterns that can be used to solve a multitude of problems when building software. When reading about design patterns, you will see the term class and instance quite frequently. It is important to note that JavaScript does not have built in class support, so we use objects and functions to mimic this behavior. There are three types of design patterns: creational, structural, and behavioral. Creational - these patterns are focused on object creation. Structural - these patterns are focused on managing relationships between objects. Structural patterns ensure that when one part of your application changes, it does not affect other parts of your application. Behavioral - these patterns are focused on managing communication between objects.
Why write test?
As you begin writing more complicated functions and larger applications, you're bound to make mistakes. Everyone does it, even professional programmers. When your programs grow they can become more difficult to reason about, and as hard as you may try it's impossible to predict every bug in your program. Fixing bugs also has a cost, as it can be quite easy for one fix to introduce bugs in other parts of your application. Is there any way to avoid our programs becoming more brittle and difficult to maintain as they grow in complexity? Yes! The solution to this problem lies in testing our code as thoroughly as possible. This makes it easier to protect against bugs, and to ensure that you don't introduce new bugs in your code as you add new features or rewrite old ones.
Unit vs. Integration Tests
As you're reading about testing, you're likely to come across two different kinds of tests: unit tests and integration tests. As you're first writing tests, you'll probably be writing mostly unit tests. These are tests which are written for one small component of your application, e.g. one function. They're meant to test the individual pieces, or units of your application. Integration tests, by contrast, are meant to test the system as a whole, and ensure that different pieces of the application are working correctly. The distinction isn't terribly important right now, but it's good to know what the terms mean and how they're different.
Concurrency
Concurrency occurs when more than one task makes progress regardless of another task. This means that if two tasks are happening, the second does not need to wait for the first to finish before it can complete execution. Concurrency can occur on one a single thread and that is how asynchronous tasks happen with JavaScript.
Currying
Currying is the process of breaking down a function that takes multiple arguments into a series of functions that take some subset of the arguments. Let's examine a very simple curry function. We will partially apply a function's arguments one at a time function simpleCurry(fn, ...outerArgs){ return function(...innerArgs){ return fn.apply(this, outerArgs.concat(innerArgs)); } } function add(a,b){ return a+b; } var a1 = simpleCurry(add,2); a1(10); // 12 This simpleCurry works fine when we only have two parameters, but what happens if we have an infinite number of arguments? Or what happens if we don't bother to pass in a value at all? Let's look at a more complex curry. function complexCurry(fn) { return function f1(...f1innerArgs) { if (f1innerArgs.length >= fn.length) { return fn.apply(this, f1innerArgs); } else { return function f2(...f2innerArgs) { return f1.apply(this, f1innerArgs.concat(f2innerArgs)); } } }; } complexCurry(add)()()()(2)()()()(4); // 6 If you are not sure what the ... is doing, you can read more about the rest operator here. Also, note that strings and arrays have a length property - but so do functions! A function's length can let us know how many arguments are in its definition!
Destructuring assignment syntax
ES2015 also gives us access to the destructuring assignment syntax for objects and arrays. From MDN: The destructuring assignment syntax is a JavaScript expression that makes it possible to extract data from arrays or objects into distinct variables. This can be useful if you want to assign multiple variables at once: var obj = { a:1, b:2, c:3 }; var {a,b,c} = obj; a; // 1 b; // 2 c; // 3 In the same way that we can destructure objects, we can also destructure arrays (which are really just a special type of an object). var arr = [1,2,3,4]; var [a,b,c,d] = arr; a; // 1 b; // 2 c; // 3 d; // 4 var [first,second] = [1,2]; first; // 1 second; // 2 Array destructuring also allows us to swap the values in two variables succinctly. This can be useful if, for instance, you're writing a function which randomly shuffles elements in an array by swapping pairs of elements. Here's an example: var [a, b] = [1, 2]; a; // 1 b; // 2 [a, b] = [b, a]; a; // 2 b; // 1
let and const
ES2015 brings us some new words to declare variables, along with a new kind of scope. So far we have only seen global and function scope, but the keyword let gives us access to block scope. You can think of blocks as code inside of curly braces that are not part of an object or function definition. Blocks exist in if statements, for/while loops, switch statements and try/catch blocks. Here are some examples: let instructor = "Elie"; if (instructor === "Elie") { let anotherInstructor = "Tim"; } anotherInstructor; // ReferenceError! This would not be true if we had used `var` instead of `let`. for (let i = 0; i < 5; i++){ setTimeout(function(){ console.log(i); },1000); } // try replacing let i = 0 by var i = 0 in the above. What happens... and why? ES2015 also gives us another keyword const which allows us to create constants. You can think of a constant as variables that can not be reassigned. const favoriteFood = "Pad Thai"; favoriteFood = "Pad See Ew"; // Uncaught TypeError: Assignment to constant variable. const person; // Uncaught SyntaxError: Missing initializer in const declaration Like let, const also respects block scope. This is the biggest difference between using let and const as opposed to the keyword var.
Object shorthand notation and destructuring assignment
ES2015 gives us quite a few enhancements on objects, which allow us to write more concise code with less repetition. Let's see what that looks like: var obj = { firstName: "Elie", sayHi: function(){ return "Hello from ES5!"; }, sayBye() { return "Bye from ES2015!"; } } var person = "Elie"; var es5Object = {person: person}; es5Object; // {person: "Elie"} var es2015Object = {person}; es2015Object; // {person: "Elie"} In the first example, note that in ES2015, when you're adding a method to an object, you can eliminate the colon and the function keyword. In other words, these two are essentially equivalent: var o1 = { sayYo: function() { console.log("Yo!"); } }; var o2 = { sayYo() { console.log("Yo!"); } } The other example shows that you can pass a variable into an object instead of a key-value pair, and JavaScript will convert the variable name into the key for you.
Template strings
ES2015 gives us string interpolation using back ticks (``) and adding our variables in a ${}. Here is an example: `Now we can do string interpolation like ${1+1}`; var person = "Elie"; `My name is ${person}`; String interpolation can make working with strings much more convenient, because it allows us to avoid having to do a lot of string concatenation: var firstName = "Matt"; var lastName = "Lane"; var title = "instructor"; var employer = "Rithm"; // ughhh, so much concatenation... var greeting1 = "Hi, my name is " + firstName + " " + lastName + ", and I am an " + title + " at " + employer + "!"; // much better var greeting2 = `Hi, my name is ${firstName} ${lastName}, and I am an ${title} at ${employer}!`;
Rest/Spread
ES2015 gives us two new operators with the same syntax. The first is the rest operator (think of it like the "rest" of the arguments), and is used inside of a list of function parameters in the definition of a function to indicate the "rest" of the arguments: function data(a,b,...c){ console.log(a,b,c); } data(1,2,3,4,5); // 1, 2, [3,4,5] Using the rest operator is often a useful way to avoid dealing with the arguments array-like object. arguments can be a little tricky to deal with, since it's not actually an array and therefore doesn't have access to common array methods. However, when you use the rest operator, what you get access to inside of your function is a bona fide array. function checkArguments() { return Array.isArray(arguments) } function checkArgumentsES2015(...args) { return Array.isArray(args); } checkArguments(1, 2, 3); // false checkArgumentsES2015(1, 2, 3); // true Here's another refactoring example that removes reference to arguments: function multiply() { let product = 1; for (let i = 0; i < arguments.length; i++) { product *= arguments[i]; } return product; } function multiplyES2015(...nums) { return nums.reduce((product, num) => product * num, 1); } multiply(1, 2, 3, 4); // 24 multiplyES2015(1, 2, 3, 4); Now let's move on to the spread operator. The spread operator sort of does the inverse of the rest operator: instead of converting a comma-separated list of values into an array, it spreads an array into a comma-separated list of values. Because of this, the spread operator is used when invoking a function, unlike the rest operator, which is used when defining a function. Here's an example: var arr = [1,2,3,4]; function addFourNumbers(a,b,c,d){ return a + b + c + d; } addFourNumbers(...arr); Also, even though the arguments object is not an array, you can still apply the spread operator to it. Here's another example: function addThree(a,b,c) { return a + b + c; } function addThreeArgs() { return addThree(...arguments); } addThree(1, 2, 3); // 6 addThreeArgs(1, 2, 3); // 6
Encapsulation
Encapsulation is the idea that data and processes on that data are owned by a class. Other functions or classes outside of that class should not be able to directly change the data. In our deck class, we have 52 cards. The player class should not be able to choose any card he or she wants from the deck or change the order of a deck manually. Instead a player can only be dealt a hand. The contents of the deck is said to be encapsulated into the deck class because the deck owns the array of cards and it will not allow other classes to access it directly.
The constructor property
Every single .prototype object has a property called constructor that points back to the original function. Let's look at an example: function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } Person.prototype.constructor === Person // true
Prototype Intro
Every single function that is created in JavaScript has a prototype property. Moreover, each object that is created can access its constructor's prototype property via the object's own __proto__ property. Let's start by looking at Object.prototype. In the Chrome console, try typing Object.prototype then expand the object you get back. You can see that Object already has many properties on its prototype. When you create a constructor function, that function will have it's own prototype. Let's try that out by creating a Person constructor function: function Person(name) { this.name = name; } var tim = new Person("Tim"); Person.prototype; // Object {} So far, our Person constructor function has a prototype and the only two properties available on the prototype should be constructor and __proto__. Let's try adding a function to the Person prototype: Person.prototype.sayHello = function() { return "Hello, " + this.name; }; Now that we have added sayHello to the prototype of Person, any person object that will be create or that was created in the past has access to the function: var moxie = new Person("Moxie"); moxie.sayHello(); // returns "Hello, Moxie" // Notice that sayHello still works for tim even though tim was created // before the sayHello function was added to the prototype. tim.sayHello(); // returns "Hello, Tim" So the main things to know so far about an Object's prototype are the following: 1. Any function or property added to the prototype is shared among all instances linked to that prototype (For example, sayHello is shared among all Person instances). 2. Each constructor function has its own prototype.
Pure Functions
First, we want to always strive to create "pure" functions. A pure function is a predictable function that does not have an side-effects. What does that mean? When a pure function is called many times with the same input, it will always give the same output (this is also known as idempotence) and is predictable. Another characteristic of pure functions are that they do not modify external state, or change values outside of their scope. Let's try to identify some pure and impure functions: Are the following functions pure or impure? var arr = [2,4,6]; function doubleValues(arr){ for(var i =0; i< arr.length; i++){ arr[i] = arr[i]*2; } } doubleValues(arr); arr; // [4, 8, 12] doubleValues(arr); arr; // [8, 16, 24] The function is impure because there is a side effect: we are mutating or changing the arr variable, and if we call this function again, we will get a different value! var arr = [2,4,6] function doubleValues(arr){ return arr.map(function(val){ return val*2; }); } doubleValues(arr); // [4,8,12] doubleValues(arr); // [4,8,12] doubleValues(arr); // [4,8,12] This function is pure because there is no side effect. If we wanted to double the result of double, we could combine these functions together! doubleValues(doubleValues(arr)) // [8,16,24] and we still would not change the arr variable. Pretty cool! How about this one? var start = {}; function addNameToObject(obj,val){ obj.name = val; return obj; } The function is impure because there is a side effect: we are mutating or changing the start variable and if we call this function again, we will get a different value! function addNameToObject(obj,val){ var newObj = {name: val}; return Object.assign({}, obj, newObj); } The function is pure because there is a not side effect and we are not mutating or changing the start variable. If we call this function again, we will not mutate any existing variables! Here's another one: var arr = [1,2,3,4] function addToArr(arr,val){ arr.push(val); return arr; } addToArr(arr, 5); // [1,2,3,4,5] arr; // [1,2,3,4,5] The function is impure because there is a side effect and we are mutating or changing the arr variable. If we call this function again, we will get a different value! var arr = [1,2,3,4] function addToArr(arr,val){ var newArr = arr.concat(val); return newArr; } addToArr(arr, 5); // [1,2,3,4,5] arr; // [1, 2, 3, 4] The function is pure because there is a not side effect and we are notmutating or changing the arr variable. if we call this function again, we will get a different value! Let's do one more: var startCountingFrom = (function(num) { var count = 0; return function(num) { count++; return count + num; } })(); This may be a little tricky to read, but if you try to use this function you should have an easier time discovering what's going on: startCountingFrom(0); // 0 startCountingFrom(0); // 1 startCountingFrom(0); // 2 This function is also impure! Even though it's not mutating any variables in the global scope, it doesn't provide the same output given the same input. This function has the potentially to be especially hard to reason about, because you can't reliabily predict what value it will return if you see it being used somewhere in your code.
Generators
In ES2015, a special type of function called a generator was introduced. Generator functions are functions that can return multiple values using the yield keyword. Previously, we have only seen functions that return once, but generators can change that for us. Let's start with a simple generator example (generator functions are denoted using a *): function* firstGenerator() { for (var i = 0; i < 5; i++) { yield i; } } var gen = firstGenerator(); gen.next(); // {value: 0, done: false} gen.next(); // {value: 1, done: false} gen.next(); // {value: 2, done: false} gen.next(); // {value: 3, done: false} gen.next(); // {value: 4, done: false} gen.next(); // {value: 5, done: true} // we can also iterate over a generator using a for..of loop for (var data of firstGenerator()) { console.log(data); } // 0 // 1 // 2 // 3 // 4 Let's now see what our earlier async example would look like if we used generators: function getJokesAbout(term) { $.getJSON( `https://icanhazdadjoke.com/search?term=${term}`, function(data) { // we could also do gen.next(data) to make this function run all three at once console.log(data); }, function(err) { console.log(err); } ); } function* displayResults() { var result1 = yield getJokesAbout("spider"); console.log(result1); var result2 = yield getJokesAbout("ghost"); console.log(result2); var result3 = yield getJokesAbout("pizza"); console.log(result3); } var gen = displayResults(); // if we want to print all without using next() for (var jokeData of displayResults()) { console.log(jokeData); } While this may seem simple to look at, generators are not always the easiest to read and can add a great deal of complexity quickly. A better option is to use ES2017 async functions.
Using call with constructors
In JavaScript, there is no way to make a traditional "class". Similarly, in JavaScript, there is no explicit way for one constructor function to inherit from another. Instead, JavaScript has prototypal inheritance. To borrow the functionality from one constructor and use it in another, we would use the call method. Below is an example. Notice that the Motorcycle constructor function has an additional property that Vehicle does not. Conceptually, the Motorcycle "class" is inheriting from the Vehicle "class". function Vehicle(make,model,year){ this.make = make; this.model = model; this.year = year; } function Motorcycle(make,model,year,motorcycleType){ Vehicle.call(this,make,model,year) this.motorcycleType = motorcycleType; } var moto = new Motorcycle("Kawasaki", "Ninja 500", 2006, "Sports")
Promises and asynchronous code
In our example above, the promise returned by firstPromise resolves or rejects immediately. But in most situations, this won't happen. Instead, you'll often use promises when dealing with some sort of asynchronous operation, like making an AJAX request. We can mimic this behavior using a setTimeout. Let's see what this looks like, so that we can examine another example of promises. function secondPromise() { return new Promise(function(resolve, reject) { var timeToResolve = Math.random() * 5000; var maxTime = 3000; if (timeToResolve < maxTime) { setTimeout(function() { resolve( `Hooray! I completed your request after ${timeToResolve} milliseconds.` ); }, timeToResolve); } else { setTimeout(function() { reject( `Sorry, this is taking too long. Stopping after ${maxTime} milliseconds. Please try again.` ); }, maxTime); } }); } There's a lot to unpack here, so let's take it line by line. First, we're randomly generating a time between 0 and 5 seconds, and storing it in a variable called timeToResolve. This is basically mimicking what happens when we make an AJAX request: we have no idea how long it will take for that request to complete. After that, we're setting a maximum time that we're willing to wait, and setting that equal to three seconds. Next comes the part where we decide whether to resolve or reject. If timeToResolve is less than 3 seconds, then we'll resolve the promise after timeToResolve milliseconds. But if it's greater than 3 seconds, we'll reject the promise after waiting for 3 seconds. Like before, the best way to understand this code is by invoking the function several times and waiting for it to resolve or reject: secondPromise() .then(function(data) { console.log(data); }) .catch(function(error) { console.log(error); }); If you run this code, you'll sometimes see that the promise resolves after a random time that's less than 3 seconds. Other times, you'll wait 3 seconds and then the promise will be rejected.
Coordinates System in Canvas
In the canvas coordinate system, the x coordinate grows from the left side of the element to the right and the y coordinate grows from the top of the canvas to the bottom. This coordinate system can seem not very intuitive to some people since they are used to graphs from math where the y value gets larger from bottom to top, but once you get the hang of it, you'll be fine. We also want to make sure that the width and height of our canvas is the same as the width and height on the page. Try doing the following in the console: var canvas = document.getElementById("my-canvas"); console.log("width: ", canvas.width, "height: ", canvas.height); The above code should return a width of 300 and a height of 150, but our canvas has a different dimension on the page. In fact, 300 and 150 are the default values of width and height for a canvas element. The difference of the canvas dimensions and the real world pixel dimensions will lead to confusion and problems, so the easiest thing to do is update the canvas size to the values from our style: var canvas = document.getElementById("my-canvas"); canvas.width = canvas.scrollWidth; canvas.height = canvas.scrollHeight;
Inheritance
Inheritance is when a child class inherits functionality from a parent class. For example, a parent class may be an automobile, and it could have a child class for sports car or for truck that has different or more specific characteristics. Both sports cars and trucks share some properties in common but they also have differences that are specific to their own class. So the truck class would inherit functionality from automobile and the sports car class would inherit from automobile as well.
Bind
Just like call, bind takes in as its first parameter what we would like the context of the keyword this to be and the 2nd to n-th parameters are the arguments to the function. However, bind does not immediately invoke the function. Instead, it returns a function definition that can be called at a later point in time (or right away, but you will very rarely do this). In other words, fn.bind(thisArg,arg1,arg1,arg3)() (note the parentheses at the end!!) does the same thing as fn.call(thisArg,arg1,arg2,arg3). function add(a,b){ return a+b; } var partialAdd = add.bind(this,2) partialAdd(4) // 6
Anti Patterns
Just like we have seen design patterns, there are certain ways that we should strive to not write JavaScript. We call those anti-patterns. Global variables,
What about resetting the constructor?
Let's examine the last line: Child.prototype.constructor = Child;. Without this line, if you examine Child.prototype.constructor, this will refer to the Parent, and not the Child! In many cases this won't actually matter, but it can definitely be confusing, since if you call .prototype.constructor on a constructor function, you expect it to point back to the original constructor function.
The meaning / purpose of a constructor function
Let's imagine that we are tasked with building an application that requires us to create car objects. Each car that we create should have a make, model and year. So we get started by doing something like this: var car1 = { make: "Honda", model: "Accord", year: 2002 } var car2 = { make: "Mazda", model: "6", year: 2008 } var car3 = { make: "BMW", model: "7 Series", year: 2012 } var car4 = { make: "Tesla", model: "Model X", year: 2016 } But notice how much duplication is going on! All of these objects look the same, yet we are repeating ourselves over and over again. It would be really nice to have a blueprint that we could work off of to reduce the amount of code that we have. That "blueprint" is exactly what a constructor function provides! Constructor functions are the closest thing we have to classes in JavaScript.
Character sets
Let's now imagine that we want to match a string that has four characters, but the first character has to be "a", "b", "c" or "d". We need some way of specifying all of those characters. This is where character sets come in - and they are denoted by placing characters inside of []. You can specify a range with character sets using the - character. var str = 'amen bean cups deer pear'; var matches = str.match(/[a-d].../g); matches // ["amen", "bean", "cups", "deer"]
Prototype Inheritance
Looking at our earlier example again, we can see how our Automobile constructor function inherits properties from the Object constructor function: function Automobile(make, model, year) { this.make = make; this.model = model; if (year !== undefined) { this.year = year; } } Automobile.prototype.year = 2016; var probe = new Automobile("Ford", "Probe", 1993); probe.hasOwnProperty("year"); // returns true probe.year; // returns 1993 Where did the function hasOwnProperty come from? It is defined in the Object prototype. Since all objects in JavaScript inherit from the Object prototype, your Automobile object has access to the hasOwnProperty function through the prototype from Object. Let's investigate the prototype chain for automobile using __proto__ and console.dir: var probe = new Automobile("Ford", "Probe", 1993); // Inspect the returned object in the console // It shows us the prototype associated with the instance of Automobile // You should see the constructor function and a property for year probe.__proto__; // Inspect the returned object in the terminal // It shows us the parent prototype (Object's prototype) that is associated // with the instance of Automobile // You should see many properties here, including hasOwnProperty! probe.__proto__.__proto__; // Click through the returned object to see the __proto__ chain. console.dir(probe);
Classes
Many other programming languages have a concept of classes. In languages like Python or Java there is an explicit way of creating classes which are then used to create an instance of the class. Therefore, a class is like a blueprint for how to build something, and the instance is a construction of that blueprint. If we have a Car class, we have only one class, but there may be many instances of cars. For example, a car class can make an instance of a Ford Probe or an instance of a Bugatti Chiron. Both are instances of a car, but there is only one car class (or blueprint). In JavaScript we DO NOT have classes built into the language. Instead, as a JavaScript programmer we mimic object oriented programming and classes using JavaScript constructor functions and the new keyword.
Object Oriented Programming
Object oriented programming is a method of programming that attempts to model some process or thing in the world as a class or object. An object in this case is not the same as JavaScript's version of an object. Instead, you can conceptually think of a class or object as something that has data and can perform operations on that data. With object oriented programming, the goal is to encapsulate your code into logical groupings using classes so that you can reason about your code at a higher level. Let's see an example:
Red, Green, Refactor
Once you get used to writing tests, you can use a workflow very common in TDD. This workflow is called "Red, Green, Refactor," and goes like this: Start by writing a test. Make sure the tests fails (i.e. is red). Writing a failing test is important; if the test passes before you write any code, then what are you actually testing? Go write code to make the test pass. Refactor your code as needed. As long as the tests are still passing, you can be reasonably confident that you aren't introducing new bugs into the program. Let's walk through this process with a simple example. Suppose you wanted to write a function called onlyStrings which takes in an array, and returns only the elements in the array that are strings. Here are some tests you might want to write: describe("onlyStrings", function(){ it("returns an array", function(){ expect(onlyStrings([1,2,3])).toEqual(jasmine.any(Array)); }); it("does not change arrays of strings", function(){ expect(onlyStrings(["a","b","c"])).toEqual(["a","b","c"]); }); it("removes non-string primitives from an array", function(){ expect(onlyStrings([1,"hi",null,"cool",undefined,"woah",false,"ok"])).toEqual(["hi","cool","woah","ok"]); }); it("removes reference types from an array", function(){ expect(onlyStrings([{},"a",[],"b",function(){},"c"])).toEqual(["a","b","c"]); }); }); After writing the tests, you would then write the onlyStrings function. Here's one possible implementation: function onlyStrings(arr) { let strings = []; for (let i = 0; i < arr.length; i++) { if (typeof arr[i] === "string") strings.push(arr[i]); } return strings; } With this code, the tests should pass. Upon further reflection, however, you may decide to refactor this function so that it uses filter instead: function onlyStrings(arr) { return arr.filter(function(el) { return typeof el === "string"; }); } With this implementation, the tests still pass, and you can be fairly certain that your changes to the onlyStrings function haven't introduced any new bugs.
Canvas - Drawing
Once you have the canvas element, the next thing you need is the context for drawing. The context is the object that actually let's you draw to the screen. To get the context, do the following: var ctx = canvas.getContext('2d'); With the context, draw a rectangle: var upperLeftX = 0; var upperLeftY = 0; var width = 50; var height = 50; ctx.fillRect(upperLeftX, upperLeftY, width, height); The above code creates a black rectangle on our blue canvas. To draw with a different color, we need to specify the fillStyle: ctx.fillStyle = "orange"; ctx.fillRect(upperLeftX, upperLeftY, width, height); Next, let's draw a triangle. There is no build in way to draw a triangle, but rather you can create a path that defines a shape. So our path will need 3 points: ctx.fillStyle = "red"; ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(40, 40); ctx.lineTo(0, 80); ctx.fill(); ctx.closePath(); Here's what that looks like. Let's say you've drawn somethings, and now you want to clear the screen and start over. To do that, you can use clearRect: var canvasWidth = 400; var canvasHeight = 400; ctx.clearRect(0, 0, canvasWidth, canvasHeight);
global variables
One of the most common anti-patterns in JavaScript is declaring lots of variables in the global scope. This is known as "polluting" the global scope (also called "namespace") and is not only an issue when working with other developers and scripts, but it can allow for memory leaks, which we will learn more about later!
Implicit Binding
One option is that the function wrapping the keyword this is being called as a method on some object. In this case the call-site has some parent object, and according to the implicit binding rule, this should refer to this parent object. Anytime you see the keyword this, you should check to see whether the closing function has an associated parent object when the function is called. In other words, check the call-site! Here are a few examples: var friend = { firstName: "Ash", sayHi: function(){ return this.firstName + " says hello!"; } }; // the keyword this will refer to friend when we invoke sayHi: friend.sayHi(); // Ash says hello! var dog = { name: "Whiskey", sleep: function() { return this.name + " is sleeping: zzzzz...." } } dog.sleep(); // Whiskey is sleeping: zzzzz.... var nested = { number: 1, anotherObject: { anotherNumber: 2, whatIsThis: function() { return this; } } } nested.anotherObject.whatIsThis(); // Object {anotherNumber: 2}
Regular Expression syntax
One way to create a regular expression is to place the pattern between two forward slashes //. Do not worry too much about what patterns look like, we will start with a very simple one. Let's match an exact string of characters. Here is what that looks like: var pattern = /Elie/; Now that we have created our first pattern, let's get started with our first string method, match. Match will return an array of matches, or null if a match is not found. Let's start with the following example var str = "My name is Elie, is your name Elie?"; var matches = str.match(/Elie/); matches; // // ["Elie"] Notice here, that matches is an array of the pattern we are trying to match. But it only returns the first occurance of Elie to us! If we would like to match all occurances accross a string, we need to add what is called a flag. We place our flags after the closing / in a regular expression and then specify the type of flag with a single character. The flag we will be using is g, which is the global flag and finds all matches in the entire string. var str = "My name is Elie, is your name Elie?"; var matches = str.match(/Elie/g); matches; // ["Elie", "Elie"] Much better! Often times, we may not care if the string is upper or lower cased, but regular expressions do! If we would like to make our search case insensitive, we can also add the i flag. To use multiple flags, just put them next to each other in the regular expression (after the closing forward slash). Let's see what that looks like. var str = "My name is Elie, is your name Elie?"; var matches = str.match(/elie/gi); matches // ["Elie", "Elie"] Nice! Hopefully this gives you a good start with how the match function works. There are a couple other string methods we can use regular expressions with, but let's learn some more about regular expressions first.
Parallelism
Parallelism occurs when more than one task runs at the same time as another task. This means there is no taking of turns; each process moves at the same time. To achieve parallelism in JavaScript, we would have to run multiple JavaScript processes (either through multiple Node.js processes or using Web Workers which are not fully implemented in all browsers).
Polymorphism
Polymorphism may be useful to understand at a high level now, but we will not focus on it when we do OOP in JavaScript. Polymorphism doesn't apply very well to a language like JavaScript. Polymorphism is the idea that an instance of a child class can be treated as if the child class were the parent class. The child can implement functionality that is specific to its class, but it can be called in the context of the parent. Going back to the automobile example. We could treat both a sports car and a truck as an automobile. An automobile may define an openTrunk function. That function could be implemented differently for a sports car versus a truck, but it would still be available to be called by an automobile. We will not cover these topics in great depth but it's good to understand what they are so that you are not surprised when you read more about OOP, especially in other languages.
Promises
Promises are almost always preferred over callbacks when managing asynchronous code. Promises make use of callback functions, but help avoid deep nesting of callbacks and improve readability. Another advantage of promises is that they are immutable, so once a promise is done, you can not accidentally call it again (something you can do accidentally with callbacks). ES2015 introduces a native Promise API, but promises have been in use for quite a while. Different libraries have their own implementation of Promises (previously called "deferreds" in jQuery), but the idea is very similar between all of them. Think of a promise as a one-time guarantee of future value. Here's a simple example: You go to a cafe to buy a sandwich. When you pay for the food, you are given a "ticket" so that other people can order while you wait. This "ticket" is the idea of a promise (a guarantee of future value). You keep waiting and either: Your ticket is called, you get your sandwich and you put the ticket in the trash (the promise is resolved and no one else can use the ticket) Your ticket is called, but something went wrong with the order so you return your ticket (the promise is rejected) Let's see what this looks like. Using the native Promise API, we can create a Promise using the Promise constructor. This constructor takes a single function as its argument, which represents the code that should be run. This code should also specify whether the promise should be resolved or rejected, by passing in resolve and reject functions as parameters. This syntax definitely takes some getting used to, since the Promise constructor accepts a function which itself accepts two callback functions! But once you've explored a few examples, it will make more sense. Here's a function that generates and returns a new promise: function firstPromise() { return new Promise(function(resolve, reject) { var x = Math.random(); if (x > 0.5) { resolve(`Hooray! Your promise was resolved with value ${x}.`); } else { reject(`Oh no, your promise was rejected with value ${x}`); } }); } To understand what this code is doing, let's invoke firstPromise multiple times. Because firstPromise returns a promise, we need to tell JavaScript what to do when this promise resolves. The way to do this is with a .then method, which will wait for the future value that you are guaranteed to receive. .then accepts a callback which will run when you have that value. In the event that the promise is rejected instead of resolving successfully, you can catch that rejection with the .catch method. Because promises are guaranteed to either resolve or reject eventually, you can be confident that either the callback to your .then method will run, or the callback to your .catch method will run. Enough words, let's just run the code several times and take a look at what we get back: firstPromise() .then(function(data) { return data; }) .catch(function(error) { return error; }); If you run this code several times, you'll see that different callbacks run based on whether the promise is resolved or rejected. (Note that in Chrome console, you'll see the word "resolved" used even if it's the reject callback that runs.)
toEqual
Remember, when we try to compare objects (including arrays) in JavaScript, only the reference is checked! If you have two arrays, using a == or === comparison won't tell you whether or not those arrays have the same values: var numbers = [1,2,3]; var numbersCopy = numbers; var numbersOtherCopy = [1,2,3]; numbers === numbersCopy; // true, since both variables refer to the same object in memory numbers === numbersOtherCopy // false! even though both arrays have the same structure, they are not the same array in memory.
Poker Example
Say we want to model a game of poker in our program. We could write the program using an array to represent the deck of cards, and then other arrays to represent what each player has in their hand. Then we'd have to write a lot of functions to do things like deal, draw cards, see who wins, etc. When you end up writing large functions involving basic data structures and lots of code in one file, there is usually a better way to organize your code. Instead of trying to do everything at once, we could separate concerns. When thinking about a game of poker, some larger processes and objects stand out that you will want to capture in your code: Card Deck of cards Poker hand Poker game Discard pile (maybe) Player Bets Each one of these components could be a class in your program. Let's pick one of the potential classes and figure out the data that it will hold and the functions that it should be able to perform: Deck of cards Cards - the deck should have 52 different playing cards Shuffle - the deck should be able to shuffle itself Deal a card - the deck should be able to remove a card from itself and deal it to a player Deal a hand - the deck should be able to deal a hand to a player or set of players Now that we can conceptualize how a problem can be broken down into classes, let's talk about why programming this way can be useful.
Using the RegExp constructor
So far we have seen how to create a regular expression using the // notation. This notation is quite easy to use, but is a problem when we need to dynamically create it and we don't know what the pattern will be beforehand. Let's examine the following function countLetters, which accepts a word and letter and returns the number of times the letter appears. function countLetters(word, letter){ var matches = word.match(letter) if(matches){ return matches.length } return 0; } countLetters('awesome', 'e') // 1 So what's wrong with our function? The problem here is that the regular expression that gets created does not have the g flag! If we want to dynamically create the regular expression, we can use the RegExp constructor function. This function accepts as its first parameter the pattern (what goes inside the //) and as a second parameter, a string of all the flags we want to pass in. Let's see how that works. function countLetters(word, letter){ var regex = new RegExp(letter, 'gi') var matches = word.match(regex) if(matches){ return matches.length } return 0; } countLetters('awesome', 'e') // 2 Nice! We can now use our regular expression and check for all occurances and even case insensitivity for good measure.
Metacharacters
So far we have seen how to match certain kinds of characters a specific number of times. While this is a good start, we can improve our usage of regular expressions by understanding metacharacters, which are certain characters that can be prefixed with a \. Let's take a look: \d This matches a digit character. Instead of using [0-9], we can use \d: var simplePhoneRegex = /[0-9]{3}-[0-9]{3}-[0-9]{4} / var betterPhoneRegex = /\d{3}-\d{3}-\d{4}/ var str = "My number is 201-867-5309" str.match(betterPhoneRegex) // ["201-867-5309"] \D This matches a non-digit character. We will see that the capitalized versions of metacharacters are equivalent to the not of the lower case metacharacter. var noNumbers = /\D+/g var str = "H3ll0" str.match(noNumbers) // ["H", "ll"] \n, \r and \t These characters match newlines, carriage return, and tab characters, respectively: "this is \n a string \n on many \n lines".match(/\n/g) // returns an array of three newlines \s This character matches any whitespace character: "please remove all the white space now".replace(/\s/g,'') // "pleaseremoveallthewhitespacenow" \S This character matches any non-whitespace character: "please remove everything but the white space now".replace(/\S/g,'') // " " \w This character matches any word character. Notice what is defined as a word character below. In particullar, numbers count as word characters! "pl3ease r3mov3 ALL 12the 44word characters__. So what is left? Maybe [] or {} or () or !@#$%^&*".replace(/\w/g,'') // " . ? [] {} () !@#$%^&*" \W This characters matches any non-word character which includes spaces, special characters (.!@#$%*() and whitespace characters): "j ".replace(/\W/g,'wow') // "jwow"
Escaping characters
So far we have seen special characters like {}, [], +, *, ?, and .. But what happens if we want to search for those actual characters in a string? For instance, take a look at the following example: // let's try to find the number of periods in a sentence. var str = "Hello. I'm Elie." str.match(/./g) // ["H", "e", "l", "l", "o", ".", " ", "I", "'", "m", " ", "E", "l", "i", "e", "."] - think about why this might happen? Rather than matching the two periods, in the above example our regex matched every character! When we are trying to find special characters in a string, we need to escape them with a backslash (\) character. Here is what that would look like: // let's try to find the number of periods in a sentence. var str = "Hello. I'm Elie." str.match(/\./g) // [".", "."] - much better!
The new keyword
So far we have seen three ways for the value of the keyword this to be determined. We have seen the default (or global) binding, implicit (or object) binding, and explicit binding (with call, apply, and bind). Now let's explore the last way the keyword this can be determined: with the new keyword. Let's take a look at some code: function Person(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } The first thing you might notice here is that the name of the function is capitalized. This will not effect the function in any way, but is a signal to ourselves and other developers that this is a certain kind of function, a "constructor" function. We will learn more about constructor functions in the next section, but constructor functions serve the purpose of creating or "constructing" objects that will be created using the new keyword. In the body of the function, we add two properties on the keyword this, firstName and lastName, and assign them to the values that are passed to the function. When we invoke this function, what should we expect to see? var elie = Person('Elie', 'Schoppik'); elie; // undefined Why did this happen? Remember that functions which do not return any values return undefined, so nothing special is happening here. And what about the value of the keyword this? In this case, there's no explicit binding going on, and Person isn't being called as a method on some parent object. So the default binding must take hold, which means that this will be defined as the window object. What we have actually done is defined two properties on the window, firstName and lastName! function Person(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } window.firstName; // undefined window.lastName; // undefined var elie = Person('Elie', 'Schoppik'); elie; // undefined window.firstName; // 'Elie' window.lastName; // 'Schoppik' So how do we fix this issue and change the value of the keyword this? We use the new keyword. Let's try using it to invoke the Person function and examine the value returned. var elie = new Person('Elie', 'Schoppik') elie // {firstName: 'Elie', lastName: 'Schoppik'} So what did the new keyword do? It somehow allowed us to return an object with the values specified! In more detail here is what the new keyword does: It creates an empty object. It sets the value of the keyword this to be that empty object created. It adds an implicit return this in the function. Notice we are not using the word return in our function, and yet our function is still returning an object when we use the new keyword! The last thing the new keyword does is a bit more complex and we will analyze it in much more detail in the next section. The new keyword creates a link between the prototype property on the constructor function and the object that was just created. Once again, we will examine this link in much more detail so do not worry about understanding this final point for now.
Asynchronous code in JavaScript
So how does JavaScript do it? The answer is through the event loop!
Our first constructor function
So what is a constructor function? It's written just like any other function, except that by convention we capitalize the name of the function to denote that it is a constructor. We call these functions constructors because their job is to construct objects. Here is what a constructor function to create car objects might look like. Notice the capitalization of the name of the function; this is a best practice when creating constructor functions so that other people know what kind of function it is. function Car(make, model, year){ this.make = make; this.model = model; this.year = year; } So how do constructor functions actually "construct" these objects? Through the new keyword that we saw before. To construct a new Car, use new: var probe = new Car('Ford', 'Probe', 1993); var cmax = new Car('Ford', 'C-Max', 2014); probe.make; // Returns "Ford" cmax.year; // Returns 2014
beforeEach
Sometimes before each it block, we want to initialize some code so that the setup before running the test is the same. In the above example, our first test mutates the arr array by adding the number 7 to it. Rather than having to keep track of that mutation across all subsequent tests, it's easier to just set arr equal to the same array before each test runs. In other words, we should always strive to not have our tests change the data we are working with in other tests. beforeEach is a great way to save us from writing arr = [1,3,5] before every single block. Here's how the above code would need to look if we didn't use a beforeEach: // WITHOUT BEFORE EACH (notice how many times we repeat let arr = [1,3,5]) describe("Arrays", function(){ describe("#push", function(){ it("adds elements to an array", function(){ let arr = [1,3,5]; arr.push(7); expect(arr).toEqual([1,3,5,7]); }); it("returns the new length of the array", function(){ var arr = [1,3,5]; expect(arr.push(7)).toBe(4); }); it("adds anything into the array", function(){ var arr = [1,3,5]; expect(arr.push({})).toBe(4); }); }); });
The wildcard character
Sometimes, we have an idea of what characters we want to match in a string or word, but don't care about other characters in that string or word. If we want to match anything we can use the special character . - this is known as the "wildcard" character, and will match anything except for the newline character. Let's take a look at an example: var str = "The cat in the hat deserves a pat"; var matches = str.match(/.at/g); matches; // ["cat", "hat", "pat"] We can also use the wildcard character multiple times in many different places, but we will soon see that there are better ways to do this. You can also see that whitespace is being matched in the case of " tape". var str = "shape tape grape"; var matches = str.match(/..a.e/gi) matches; // ["shape", " tape", grape"] We can match numbers as well: var numbers = '123 321 121 111 428 888'; var matches = numbers.match(/.2./g); matches // ["123", "321", "121", "428"]
Facade Pattern
The Facade pattern is used quite heavily in jQuery and involves presenting an outward appearance that hides underlying complexity. The idea is that using this pattern, we can provide a simple looking API and obscure the complexity from others. Some examples of the Facade pattern in jQuery are .css(),.animate() and other abstractions like .getJSON(), .get(), and .post(). Take a look at one of the potential implementations of $(document).ready here and you can see just how much complexity is hidden from what appears to be a simple method. In the facade pattern we abstract quite a bit of detail when the facade function is called. When you find yourself using a library or framework and using many predetermined values (especially for keys in objects), you very well may be using a facade!
Constructor Function Best Practices
The best practices for creating constructor functions in JavaScript are: All of the properties that you do not want to be shared go inside of the constructor function All properties that you want to be shared go on the prototype. Almost all of the time, you will want to put functions on the prototype. We will explain why soon! Using our person example, if we want to add a siblings array to the Person class, we would add it in the constructor: function Person(name) { this.name = name; this.siblings = []; } var janey = new Person("Janey"); janey.silbings.push("Annie"); Now each time the new keyword is used on the Person constructor, a new object is created that has its own name and siblings property. Now if we create another person it will have its own name and siblings array as well: var tim = new Person("Tim"); tim.siblings.push("Nicole"); tim.siblings.push("Jeff"); tim.siblings.push("Greg"); tim.siblings; // Returns ["Nicole", "Jeff", "Greg"]; We said earlier that when it comes to functions, you typically want to add them to the prototype. Why is this? After all, your code will function correctly if you create your function definitions in the constructor like this: // NOT A GOOD PRACTICE function Person(name) { this.name = name; this.sayHi = function() { return "Hello, " + this.name; } } The problem is that every time you use the new keyword to create a Person, a new object gets created in memory that allocates space for the person's name and also for the sayHi function. So if we have 10 Person objects that we create, there will be 10 copies of the same sayHi function. Since the function does not need to be unique per Person instance, it is better to add the function to the prototype, like this: // BEST PRACTICE!! function Person(name) { this.name = name; } Person.prototype.sayHi = function() { return "Hello, " + this.name; } Unless you have a good reason not to, always put function definitions on the constructor function's prototype.
Constructor Pattern
The constructor pattern is a creational pattern for making objects and is one that we have seen before! The constructor pattern makes use of a function called a constructor to create objects. Here is a small example of the constructor pattern with the prototype object (which is shared amongst all objects created from this constructor function using the new keyword): function Person(firstName, lastName){ this.firstName = firstName; this.lastName = lastName; } var matt = new Person("Matt", "Lane"); var elie = new Person("Elie", "Schoppik"); var tim = new Person("Tim", "Garcia"); matt.__proto__ === elie.__proto__ // true tim.constructor === Person // true Module Pattern Before we examine the module pattern, let's redefine what a "module" is. A module is a self-contained (or encapsulated), reusable piece of code. The module pattern is an alternative to creating a class, and allows us to emulative native class functionality present in other programming languages. With modules, we can declare public and private variables and methods, and avoid variable collisions in the global scope. The module pattern makes use of closure to create private variables and uses an IIFE (Immediately Invoked Function Expression) for an easier syntax when accessing the module. Here is an example of the module pattern var myModule = (function() { // A private variable inside the scope of the IIFE that is var privateVariable = "secret"; // A private function inside the scope of the IIFE function privateFunction() { console.log("You just called the private function!"); }; // everything returned in the object is accessible publicly return { // A public variable publicVariable: "You can see me!", // A public function utilizing privates displayPrivateVariable: function() { console.log(privateVariable); }, invokePrivateFunction: function() { privateFunction(); } }; })(); myModule.publicVariable; // "You can see me!" myModule.displayPrivateVariable(); // "secret" myModule.privateFunction; // undefined myModule.privateVariable; // undefined myModule.invokePrivateFunction(); // "You just called the private function!" We can also pass values to our modules from other global modules as parameters. This idea is known as importing. var myModule = (function(j, r) { // we now have imported jQuery and React and named them as j and r in our module! })(jQuery, React); The module pattern is great way to create private variables and encapsulation in our JavaScript code. A potential disadvantage is that private variables are not very flexible, which can make testing modules challenging and fixing bugs difficult when there is an issue with private variables and methods.
This
The keyword this is one of the more unique features of JavaScript. It often presents a stumbling block to people learning JavaScript, even if they already know some other language. However, the rules governing this are relatively straightforward once you've familiarized yourself with them. At its core, this is just a keyword in JavaScript that refers to some object. For instance, if you hop into the Chrome console and type this, you'll see that it refers to the window. However, the value of the keyword this depends on where in your code you use it. Let's take a look at the different ways the value for the keyword this gets set.
Observer Pattern
The observer pattern is where an object (the subject) has a list of objects that depend on it (observers/subscribers) and automatically notifies the observers when there are any changes. The subject has the ability to subscribe and unsubscribe objects from the list as well. There is also a pattern called pub/sub (publish/subscribe), which is very similar to the observer Pattern except in a pub/sub model there is additional data that sits between the subject and observer. You can find some great examples of the observer pattern here The Observer pattern is quite common in popular JavaScript libraries like RxJS and Redux. You have also already seen many examples of the Observer pattern with jQuery methods like on() and off() to subscribe and unsubscribe to events.
Promise.all
The other nice thing about Promises is that you can wait for multiple promises to resolve with the Promise.all function. Promise.all accepts an array of promises, and returns a new promise to you that will resolve only after each promise in the array has resolved. If any one of the promises in the array passed to Promoise.all gets rejected, the promise returned by Promise.all will be rejected too. Let's take a look at an example: function getJokesAbout(term) { return $.getJSON(`https://icanhazdadjoke.com/search?term=${term}`); } Promise.all([ getJokesAbout("spider"), getJokesAbout("ghost"), getJokesAbout("pizza") ]) .then(function(data) { console.log("Woah check out all this data", data); }) .catch(function(err) { console.log("Oops, something went wrong!"); }); Note that this has one clear benefit over the callback pattern we saw before - namely, we only need one callback to catch errors, rather than three! Promise.all is helpful if you want to fire off a lot of requests that don't have anything to do with one another. But sometimes you'll want to chain promises together sequentially. This might happen, for instance, if you need the response from one resolved promise in order to create another promise. Fortunately, we can also return the value of promise to another. This is another way for us to escape out of callback hell. Rather than deeply nesting callbacks, we can chain together multiple promises using .then instead! Recall our callback example from the previous section: $.getJSON( "https://icanhazdadjoke.com/search?term=spider", function(data) { console.log("Here's some data on spider jokes: ", data); $.getJSON( "https://icanhazdadjoke.com/search?term=ghost", function(data) { console.log("Here's some data on ghost jokes: ", data); $.getJSON( "https://icanhazdadjoke.com/search?term=pizza", function(data) { console.log("Here's some data on pizza jokes: ", data); }, function(error) { console.log("Oops something went wrong!", error); } ); }, function(error) { console.log("Oops something went wrong!", error); } ); }, function(error) { console.log("Oops something went wrong!", error); } ); Here's how we can refactor this code the to use the chainable nature of promises: $.getJSON("https://icanhazdadjoke.com/search?term=spider") .then(function(data) { console.log("Here's some data on spider jokes: ", data); return $.getJSON("https://icanhazdadjoke.com/search?term=ghost"); }) .then(function(data) { console.log("Here's some data on ghost jokes: ", data); return $.getJSON("https://icanhazdadjoke.com/search?term=pizza"); }) .then(function(data) { console.log("Here's some data on pizza jokes: ", data); }) .catch(function(error) { console.log("Oops something went wrong!", error); }); Not only have we eliminated the nesting, we've also reduced code duplication since we can catch all potential errors with a sincle .catch! Let's look at one final example before moving on. Here's another example that returns values from one promise to another promise: function first() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log("first is done"); resolve(10); }, 500); }); } function second(previousPromiseData) { return new Promise(function(resolve, reject) { setTimeout(function() { console.log("second is done and we just got", previousPromiseData); resolve(previousPromiseData + 10); }, 500); }); } function third(previousPromiseData) { return new Promise(function(resolve, reject) { setTimeout(function() { console.log("third is done and the total is", previousPromiseData); resolve(); }, 500); }); } first() .then(second) .then(third);
replace, search,and split
The replace function in JavaScript can accept as its first parameter either a string or a regular expression. The second parameter (a string or callback function) will be what the text is replaced with. var str = "awesome" str.replace('e','z') // "awzsome" - it does not get the last e! var str = "awesome" str.replace(/e/g,'z') // "awzsomz" - much better! // using a callback var str = "awesome" str.replace(/[aeiou]/g, function(match) { return match.toUpperCase(); }); // "AwEsOmE"; The search function in JavaScript can accept as its first parameter either a string or a regular expression. Similar to indexOf, the search function will return the first starting point of where a match is found or -1 if a match is not found. var str = "awesome" str.search('awe') // 0 str.search('z') // -1 // using a regular expression var str = "awesome" str.search(/..e/) // 0 str.search(/p/) // -1 The split function in JavaScript can accept as its first parameter either a string or a regular expression, which will be used as the string to split on (this is also known as the delimeter). var str = "My name is elie" str.split(/e/g);
Singleton Pattern
The singleton pattern is a bit different than the constructor and module patterns even though it is also a Creational pattern. In traditional object oriented languages, the singleton pattern creates a new instance, only if one does not exist already (singleton => "single instantiation"). If the instance exists, the pattern returns a reference to the instance. Therefore you can only create a single instance, or in JavaScript a single object from this pattern. The singleton pattern is used heavily in front-end frameworks like Angular. Let's see an example: var firstSingleton = (function(){ var instance; // this code will be executed ONCE function initialize(){ var count = 0; var privateName = "Samuel Singleton"; function privateMethod(){ return "Your name is " + privateName; } return { sayMyName: privateMethod, increment: function(){ count++; return count; }, getCount: function(){ return count; } } } // let's see if we need to call the initialize function return { findOrCreateInstance: function(){ if(!instance){ instance = initialize(); } return instance; } } })() var one = firstSingleton.findOrCreateInstance(); var two = firstSingleton.findOrCreateInstance(); one === two; // true one.increment(); // 1 one.increment(); // 2 one.increment(); // 3 two.getCount(); // 3 The Singleton pattern is very useful when you only want one instance possibly created, but it can be difficult to test and when used too heavily throughout a codebase, it can actually be a sign of poor design.
timers, DOM references and closures
The specific issue with timers is when a setInterval is not cleared. If the setInterval is not cleared, variables inside of the callback function can not be cleared as well. With DOM references it is a bit tricker. If you create a reference to a DOM node with a variable and then remove the element from the dom, you will still have a reference to the DOM variable in memory and it will not be able to be collected by the garbage collector. The last example of how memory leaks can occur is with closure. Remember that through closure we can access variables defined in outer functions that have returned so it is important to make sure that at some point your code dereferences these values or else they will not be able to be garbage collected. Memory leaks may seem a bit obscure, but they can happen quite easily especially when using frameworks and libraries without understanding how they really work. You can read here about memory leaks with Angular 1 applications and how you can allow for memory leaks with React here. Even if you do not yet understand how these frameworks work, just know they do not "protect" you from memory leaks. While they may try to prevent as many memory leaks as possible, you as the developer are still responsible for handling these issues if they arise.
The four ways to figure out the keyword this
The terms default binding, implicit binding, explicit binding, and new binding come from the incredible book You Don't Know JS by Kyle Simpson. You can read more about these terms in his book on prototypes and the keyword this.
Canvas - Making Things Move
To create movement in the canvas, you need to draw something, clear the screen, then draw something again that has moved slightly. Before we get there, let's clean up our code a little. In the example below, the code for drawing a square is kept inside of the square object. The square object is responsible for drawing itself based on the properties that it has: var canvas = document.getElementById("my-canvas"); canvas.width = canvas.scrollWidth; canvas.height = canvas.scrollHeight; var ctx = canvas.getContext('2d'); var square = { corner: [0,0], width: 50, height: 50, color: "red", draw: function() { ctx.fillStyle = this.color; ctx.fillRect(this.corner[0], this.corner[1], this.width, this.height); } } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); square.draw(); } draw(); The first step to adding movement is to create a drawing loop using setInterval. So rather than calling the draw function directly, setInterval will call it. The more frequently we call draw, the higher the frame rate of our motion is. In other words, the frame rate is the number of screens drawn per second. If we draw a frame every second, we would have a frame rate of 1 frame per second. Typically, if you want the motion too look natural, you want a frame rate of at least 20 frames per second: // Drawing every 50ms = 20 draws in 1 second or 20 fps var intervalId = setInterval(draw, 50); Now in the console if you do the following: square.corner = [50, 50]; You should see the square being redrawn somewhere else. Now to make the square move diagonally across the screen, modify the draw function to update the corner of the square slightly on each draw: function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); square.corner[0] += 2; square.corner[1] += 2; square.draw(); }
Canvas Setup
To create your first canvas element, let's start with this html: <!DOCTYPE html> <html> <head> <title>Canvas Intro</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <canvas id="my-canvas"></canvas> </body> </html> And this code for style.css: #my-canvas { background-color: blue; height: 400px; width: 400px; } Now, open up your javascript console, and get the canvas element by doing the following: var canvas = document.getElementById("my-canvas"); Next, we'll start adding elements to the canvas, but first we need to understand the x and y coordinate system.
Strict Mode
To help with some anti-patterns and to avoid memory leaks, we can enable strict mode, which throws errors instead of letting our code fail silently. To use strict mode we add the string "use strict". Here are some examples for how strict mode can help out. Note - if you are using "use strict" in the Chrome console, make sure it is at the top of your code and that code run is right under it, it will not work if you just run "use strict" then execute different statements. Strict mode stops us from declaring variables without a specified keyword (var / let / const) and it sets the keyword this to be undefined when inside of a function in the global context. "use strict" noVarKeyword = "bad idea" // ReferenceError "use strict" function badIdea(){ this.data = "done" } badIdea() // TypeError Strict mode also prevents us from using delete on things we should never ever delete. We will not actually delete the example below if we were not using strict mode, but it would be bad code to even write in the first place. "use strict"; delete Array.prototype; // TypeError Strict mode also lets us know if we are using incorrect parameters before a function is run. This prevents future ReferenceErrors by throwing SyntaxErrors before. function sum(a, a, c) { // !!! syntax error "use strict"; return a + b + c; // wrong if this code ran } Strict mode also prevents us from setting properties on primitive values, which we can do outside of strict mode (even though they do not actually get added) false.awesome = "yes" false.awesome // undefined "use strict" false.awesome = "no" // TypeError
Strict Mode
To prevent ourselves from creating variables in the global scope, we can add the text "use strict" to enable strict mode. This was created in ES5 to prevent developers from making mistakes and instead of silent errors, strict mode will throw errors. If strict mode is enabled, the default binding of this is undefined. "use strict" function outer() { return this; } outer(); // undefined function info(){ this.data = "something"; } info() // Uncaught TypeError: Cannot set property 'data' of undefined // this is happening since `this` is being set to undefined // and we can not access properties on undefined!
Composition
Very commonly, currying is used to combine two or more functions to produce a new function. We call this process of combining functions "composition." Let's imagine that we want to do the following to a string of data Uppercase the string filter out everything that is not a vowel join it back into a string with a ":" in between each vowel. We could do that as: function convert(str){ return str.toUpperCase().split('').filter(function(val){ return val.match(/[AEIOU]/); }).join(":") } convert("hello") // "O:E" This works totally fine, but this function is doing a lot of things. What if we could acheive the same functionality by combining several functions, each of which only does one thing? This is the idea behind composition. You may have learned about composition in math class: if you have one function f, and another function g, you can compose the two functions and apply them to an input x. The mathematical notation looks like this: f(g(x)); in other words, we start with the input x, then apply g to that input, then apply f to the output of g. In other words, when we think about composition we start from the inside out. Here is what it might look like in JavaScript; let's call our function compose. You can see an implementation of an ES5 compose here, but let's use ES2015! function compose(...functions) { return function(start){ return functions.reduceRight(function(acc, next) { return next(acc); } , start); } } Since we are composing functions from the inside out (f(g(x)) => x -> g -> f) we need to accumulate the functions from right to left, so we use reduceRight. We will see there is a more readable way of doing this later on if we go from left to right. Since we are passing function definitions we need to curry our functions in order to return one function at a time. Remember, the purpose of currying is to return a single argument back so let's bring that back: function complexCurry(fn) { return function f1(...f1innerArgs) { if (f1innerArgs.length >= fn.length) { return fn.apply(this, f1innerArgs); } else { return function f2(...f2innerArgs) { return f1.apply(this, f1innerArgs.concat(f2innerArgs)); } } }; } Now let's curry our functions and pass them into compose. var join = complexCurry(function(str, arr){ return arr.join(str); }) var filter = complexCurry(function(fn,arr){ return arr.filter(fn); }) var isVowel = complexCurry(function(char){ return char.match(/[AEIOU]/); }) var split = complexCurry(function(delimiter, str){ return str.split(delimiter); }) var toUpperCase = complexCurry(function(str){ return str.toUpperCase(); }) var convertLetters = compose(join(':'), filter(isVowel), split(''),toUpperCase()); console.log(convertLetters('This is some pretty crazy stuff')); // I:I:O:E:E:A:U If we wanted to start with the outer most function f -> g -> x we can use what is called a sequence function. This is also known as pipe or flow in libraries like Ramda.js and Lodash. The only difference compared to using compose is that we're calling reduce rather than reduceRight: function sequence(...functions) { return function(start){ return functions.reduce(function(acc, next) { return next(acc); } , start); } } We could then do something like this in what might be a more understandable order: var convertLetters = sequence(toUpperCase(), split(''), filter(isVowel), join(':')); console.log(convertLetters('This is some pretty crazy stuff')); // I:I:O:E:E:A:U
A review of Closure
When a function is able to access variables from an outer function that has already returned. We see that the bind function returns a new function to us, so what bind is doing is simply using closure!
Functional Programming
When first learning about functional programming, you're likely come across a myriad of complex terms like monads and lambda calculus, but let's start with a simpler definition. Functional programming is an alternative to object oriented programming and it consists of building programs with small, reusable functions. That may seem trivial, but there are special conditions that these functions will need to have. We'll discuss a few of these conditions in this section. Pure,
What does the new keyword do?
When new is used, the following happens: An empty object is created, The keyword this inside of the constructor function refers to the empty object that was just created, A return this is added to the constructor function (this is why you don't need to explicitly return any value), An internal link is created between the object and the .prototype property on the constructor function. We can actually access this link on the object that is created: it is called __proto__, sometimes pronounced "dunder" (double underscore) proto. function Car(make, model, year){ this.make = make; this.model = model; this.year = year; } var car = new Car("Buatti", "Chiron", 2017); car.__proto__ === Car.prototype // true
Character ranges
When we want a specific quantity of characters we can use the character range which is denoted by {}. e{2} will match the letter 'e' exactly two times. You can even specify a range with a minimum and maximum value e{1,3} will match the letter 'e' one to three times. If you omit the second number in the character range, but include a ,, it will match an infinite amount. For example, e{2,} will match the character "e" two times or more. We can combine this with other matching patterns form powerful regular expressions, but let's start with a simple one first. // only match when there is more than 1 'l' var str = "helo hello hellllo hellllllllllo" str.match(/hel{2,}o/g) // ["hello", "hellllo", "hellllllllllo"] // count how many words have two or more o's or two d's in the middle var str = "noodle caboodle testing fiddle person diddle muddle booooombox" str.match(/[od]{2,}/g).length // 6
JavaScript Property Lookup
When you attempt to access a property on an object in JavaScript, there is a lookup process that goes on in order to find your property. To find the value for a property, first the properties on the object are checked. If the property is not found, then the properties on the prototype of the constructor function are checked. Let's look at an example: function Automobile(make, model, year) { this.make = make; this.model = model; if (year !== undefined) { this.year = year; } } Automobile.prototype.year = 2016; Notice that year is set on the prototype to 2016. Also, if no year is passed into the constructor, an assignment to year will not be made. var newCar = new Automobile("Ferrari", "488 Spider"); // In this case, we did not pass in a year, // so it was never set in the constructor function newCar.hasOwnProperty("year"); // Returns false newCar.year; // returns 2016 Now, if we create a car with a year, the property on the car object will be seen first in the property lookup: var probe = new Automobile("Ford", "Probe", 1993); probe.hasOwnProperty("year"); // returns true probe.year; // returns 1993
Callbacks
When you first learn about asynchronous code, you'll typically learn to manage it using callbacks. When you make an AJAX request, for instance, you'll often want to execute some JavaScript once you've received a response to your request. In order to ensure that your code doesn't run until you've received a response, you can often place that code inside of a callback to the original request. Here's an example of what that might look like, using jQuery to make a request to the Dad Jokes API: $.getJSON( "https://icanhazdadjoke.com/", function(data) { console.log("Cool, here's some joke data: ", data); }, function(error) { console.log("Oops something went wrong!", error); } ); Similarly, when we need to manage asynchronous code in a certain order, we can nest our callbacks: $.getJSON( "https://icanhazdadjoke.com/search?term=spider", function(data) { console.log("Here's some data on spider jokes: ", data); $.getJSON( "https://icanhazdadjoke.com/search?term=ghost", function(data) { console.log("Here's some data on ghost jokes: ", data); $.getJSON( "https://icanhazdadjoke.com/search?term=pizza", function(data) { console.log("Here's some data on pizza jokes: ", data); }, function(error) { console.log("Oops something went wrong!", error); } ); }, function(error) { console.log("Oops something went wrong!", error); } ); }, function(error) { console.log("Oops something went wrong!", error); } ); One of the simplest ways of managing asynchronous code is through the use of callbacks, but callbacks get quite messy when we have to start nesting functions inside of each other. This is often referred to as the "Pyramid of Doom" or "Callback Hell." Not only do we have functions nested inside of each other, but we have another function for each possible error. It would be easier if we had more control over our asynchronous code. This is where promises can help.
What does Object.create do?
Why can't we just do Child.prototype = Parent.prototype? Remember that when we assign objects equal to each other, they are just references. This means that Child.prototype is a reference to Parent.prototype which means that if we add to the Child.prototype, objects created from the Parent.prototype will have access to them, which would be bad! Child.prototype = Parent.prototype; // true - this is BAD! Child.prototype === Parent.prototype; Child.prototype = Object.create(Parent.prototype); // false - This is GOOD! We want these to be different Child.prototype === Parent.prototype;
Default Binding
You can think of this as the last possible option when all other rules do not apply. The default binding exists when the keyword this is in the global context. As we've already seen, in the case of the browser the keyword this has a default binding to the window. var thing = this; thing; // window function outer() { return this; } outer(); // window For the other three bindings, the value of the keyword this is set to some value other than the default when it is used inside of a function. But the details of how the value gets set depend on the call-site of the function. The call-site is simply the way and location in which the function is called.
BAD PRACTICE: Using new to create a child class
You may see inheritance done by using the new keyword instead of using Object.create. This will do almost the same thing, but add additional unnecessary properties on the prototype (since it is creating an object with undefined properties just for the prototype).
Apply
apply is very similar to call, except it only takes in a maximum of two arguments. As with call, the first argument to apply is thisArg, which lets us explicitly set the value of the keyword this. Unlike call, however, the second argument to apply is always an array of parameters. In other words, fn.call(thisArg,arg1,arg2,arg3) will produce the same result as fn.apply(thisArg,[arg1,arg2,arg3]) for any function fn, this argument thisArg, and arguments arg1, arg2, and arg3. So when should you use call and when should you use apply? If you don't have to pass any additional arguments to the function on which you're invoking call or apply, it doesn't matter: you can use either one. If you do need to pass arguments to the function, do whatever is most convenient in the situation. If you have an array of parameters to pass, or you don't know exactly how many arguments you'll be passing, apply might be a better bet. If you only have one argument to pass, or you always know which arguments you'll need to pass, maybe call is a better choice. var matt = { firstName: "Matt", lastName: "Lane", instructor: true, favColor: "blue", dogOwner: true, deleteInfo: function() { console.log(arguments); for (var i = 0; i < arguments.length; i++) { delete this[arguments[i]]; } } } var tim = { firstName: "Tim", lastName: "Garcia", instructor: true, favColor: "blue", dogOwner: false }; var elie = { firstName: "Elie", lastName: "Schoppik", instructor: true, favColor: "purple", dogOwner: false }; matt.deleteInfo('instructor', 'favColor'); matt; // {firstName: "Matt", lastName: "Lane", dogOwner: true} matt.deleteInfo.apply(tim,['firstName', 'dogOwner', 'favColor']); tim; // {lastName: "Garcia", instructor: true} matt.deleteInfo.apply(elie,['instructor','favColor','dogOwner','lastName']); elie; // {firstName: "Elie"} Here are two other common cases where you'll see apply being used. One involves the use of the Math.max function. This function returns the maximum number in a comma-separated list of numbers, but you can also use apply to find the maximum value of an array: var numbersArray = [1,2,3,4,5]; Math.max.apply(this,numbersArray) // 5 Another use-case involves taking an array of arrays and flattening it, so that all the inner arrays are removed (but the values inside of them are preserved). Here's how you could do this using apply: var arrayToBeFlattened = [1,2,[3,4],[5,6]] [].concat.apply([],arrayToBeFlattened) // [1,2,3,4,5,6]
call
call will immediately invoke the function that it is attached to. If you want to change the value of the keyword this, you can pass in the desired value as the first parameter to call (oftentimes this first argument is written in documentation as thisArg). Here's an example: var animal = { introduce: function() { return this.name + " is a " + this.type + " and says " + this.sound + "!"; } }; var whiskey = { name: "Whiskey", type: "dog", sound: "woof" }; var moxie = { name: "Moxie", type: "cat", sound: "meow" }; // set the thisArg to be the object whiskey: animal.introduce.call(whiskey); // "Whiskey is a dog and says woof!" // set the thisArg to be the object moxie: animal.introduce.call(moxie); // "Moxie is a cat and says meow!" The second to n-th parameters of call are the parameters of the function you are immediately invoking, separated by commas. Here's an example passing multiple arguments into call: var person1 = { name: "Matt", greet: function(otherName) { return "Hi, " + otherName + ", I'm " + this.name + ". Nice to meet you!"; } } var person2 = { name: "Tim" } // person1 greets person2: person1.greet(person2.name); // "Hi Tim, I'm Matt. Nice to meet you! // person2 tries to greet person1, but there's a problem... person2.greet(person1.name); // TypeError: person2.greet is not a function // person2 doesn't have a greet method! So let's borrow the one from person1... person1.greet.call(person2); // "Hi, undefined, I'm Tim. Nice to meet you!" // We still need to pass person1's name to the function being called! Let's pass the argument to the function inside of call: person1.greet.call(person2, person1.name); // "Hi, Matt, I'm Tim. Nice to meet you!" One of the most common use cases for call is to convert an array-like object into an actual array. Take a look at the following example: function sumArgumentsIncorrectly(){ return arguments.reduce(function(acc,next){ return acc + next; },0); } sumArgumentsIncorrectly(1,2,3,4,5) // this throws a type error because the arguments "array-like object" does not contain the method reduce! function sumArgumentsCorrectly(){ // we are going to use the slice method which makes copies of arrays, but instead of making a copy of [], we will use the arguments array as the context that we want slice to be called in. We can immediately attach reduce and we are good to go! return [].slice.call(arguments).reduce(function(acc,next){ return acc + next; },0) } sumArgumentsCorrectly(1,2,3,4,5) // 15
Main functions we'll see the most with tests are:
describe, it, and expect
A quick review of the keyword this
four ways to determine the value of the keyword this. Here's a bit more detail on each type of binding: Default binding - catch-all rule, when the keyword this is in the global context Implicit Binding - when the keyword this is attached to a parent object Explicit Binding - when we want to explicitly set the context for the keyword this The new keyword - when the new keyword is used, the keyword this refers to an object that is created from a function after the new keyword (usually called a constructor function). So far, we've seen default binding and implicit binding in more detail. In this chapter we'll focus on the third way to determine the value of the keyword this: explicit binding. To explicitly bind the keyword this we can use one of three functions: call, apply, or bind. These functions allow us to set the value of the keyword this to be whatever we want!
Shared Prototype Example
function Person(name){ this.name = name; } Person.prototype.siblings = ["Haim", "David"]; var elie = new Person("Elie"); The above example creates a instance of a Person and sets a siblings array on the prototype. The intention is for elie to have an array of siblings. However, since the prototype is shared among all instances of a Person, any other instance will also have access to the same siblings array: elie.siblings.push("Tamar"); // returns the new length of the array => 3 // The siblings array will now be ["Haim", "David", "Tamar"] var anotherPerson = new Person("Mary"); anotherPerson.siblings.push("Leslie"); elie.siblings; // ["Haim", "David", "Tamar", "Leslie"] We can see again from this example, that anything put on the prototype is shared among all instances of that object.
Running tests in the browser
setup: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Our First Jasmine Tests</title> <link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.0.0/jasmine.css" rel="stylesheet" /> </head> <body> <div id="jasmine"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.0.0/jasmine.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.0.0/jasmine-html.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jasmine/3.0.0/boot.js"></script> <script> var earth = { isRound: true, numberFromSun: 3, density: 5.51 }; describe("Earth", function(){ it("is round", function(){ expect(earth.isRound).toEqual(true); }); it("is the third planet from the sun", function(){ expect(earth.numberFromSun).toEqual(3); }); it("is the densest of all the planets", function(){ expect(earth.density).toBeGreaterThan(5.50); }); }); </script> </body> </html>
Special characters
starting - ^ If we want to match starting from the beginning of a string, we can use the ^ character: "this is great".match(/^t.*/) // ["this is great"] "now this is not great".match(/^t.*/) // null ending - $ If we want to match something that specifically ends with a character we use $: "first.test.js".match(/.*\.test.js$/) // ["first.test.js"] "first.js".match(/.*\.test.js$/) // null excluding ^ (inside []) If we want to exclude something in a character set we use ^ inside []: "let's get rid of everything that is not a vowel".replace(/[^aeiou]/gi,'') // "eeioeeiaioaoe" or - | If we want to handle multiple conditions we can use the or operator with a |. If you find yourself using multiple | operators, there is usually a better regular expression for the job. "banana bread".match('bread|pancakes$') // ["bread"] "banana pancakes".match('bread|pancakes$') // ["pancakes"] word boundaries - \b The metacharacter \b matches the boundary between a word and a non-word character. It is used commonly when capturing entire words between non character words. The pattern for that is /\w+\b/. "my email is. . . . . . [email protected]".match(/\b/g).length // 12 - why does this return 12? Count each start and end of a word (between non character word) // my // email // is // elie // rithmschool // com // => 6 * 2 = 12 // Now let's use word boundaries a bit better! "my email is. . . . . . . [email protected]".match(/\w+\b/g) // ["my", "email", "is", "elie", "rithmschool", "com"] "http://www.google.com".match(/\w+\b/g) // ["http", "www", "google", "com"]
describe
this function is given to us by jasmine and it is what we use to organize our tests. You can think of a describe function like talking to someone and telling them "let me describe ____ to you." Very often when you're writing unit tests, you'll have one describe block per function you're testing
it
this function lives inside of describe functions. Inside of these it functions we make our expectations. Each it function corresponds to a test; if one of our expectations inside of the it function isn't met, the test fails. let's just look at an example written in plain old English. Here's how you might scaffold some tests to check whether a planet in our solar system is Earth: describe "Earth" it "is round" it "is the third planet from the sun" it "is the densest of all the planets"
expect
this is a function given to us by jasmine. When combined with describe and it, we can write tests that looks something like this: describe "Earth" it "is round" expect (earth.isRound).toEqual(true) it "is the third planet from the sun" expect(earth.numberFromSun).toEqual(3) it "is the densest of all the planets" expect(earth.density).toBeGreaterThan(5.51)
Example test code written in JS using jasmine:
var earth = { isRound: true, numberFromSun: 3, density: 5.51 }; describe("Earth", function() { it("is round", function() { expect(earth.isRound).toEqual(true); }); it("is the third planet from the sun", function() { expect(earth.numberFromSun).toEqual(3); }); it("is the densest of all the planets", function() { expect(earth.density).toBeGreaterThan(5.5); }); }); Note the syntax here: both describe and it take a string as their first parameter, and a callback as the second. The callback to a describe typically consists of several it functions. Inside of each it function is where we write our expectations.
deep.equal
when writing tests, how can we see if two arrays or objects consist of the same values? use deep.equal Deep equality checks whether the elements in two arrays are equal, rather than simply checking if the two arrays refer to the same place in memory. Along with things like toEqual, jasmine has many many more operators to check for all types of things about our data - it's quite a versatile tool!
A very common use case for bind is when
you don't want to lose the correct context of the keyword this, but also do not want to execute the function immediately: var obj = { firstName: "Elie", sayHi: function(){ setTimeout(function(){ console.log(this.firstName + " says hi!"); }.bind(this),1000); } } In this example, we are ensuring that we get the correct context of the keyword this. If we did not use the bind(this), the context of the keyword this would be the window object, because when the callback inside of the setTimeout executes, it does not do so as a method on obj, so it loses the implicit binding. In particular the window does not have firstName property! So why did we not use call or apply? Remember that bind does not execute the function right away. Instead it just returns a function definition. For the example above that is exactly what we want, because we don't want to run the function right away: we want to wait 1000 milliseconds! Here are some other examples highlighting the usefulness of bind in setting the value for the keyword this: