From dea4be7477d09fa6f0a7bf454c10bc8643420685 Mon Sep 17 00:00:00 2001 From: Jourdan Clark Date: Fri, 8 Sep 2017 13:00:37 -0600 Subject: [PATCH 1/2] Advanced-JavaScript Completed --- notes/inheritance.js | 135 +++++++++++++++++++++++++++++++++++++++++++ notes/this-scope.js | 57 ++++++++++++++++++ src/arrays.js | 34 +++++++++++ src/class.js | 27 +++++++++ src/closure.js | 18 ++++++ src/es6.js | 50 +++++++--------- src/objects.js | 27 +++++++++ src/recursion.js | 26 ++++++++- src/this.js | 23 ++++++++ 9 files changed, 366 insertions(+), 31 deletions(-) create mode 100644 notes/inheritance.js create mode 100644 notes/this-scope.js diff --git a/notes/inheritance.js b/notes/inheritance.js new file mode 100644 index 0000000..bcbac26 --- /dev/null +++ b/notes/inheritance.js @@ -0,0 +1,135 @@ +/** + Let's say we have a class Monster. This class will define all of our Monsters at a generic level. + It will hold attributes that each Monster, be it a Zombie, a Skeleton, a Banshee, etc should have. +**/ +class Monster { + constructor(name, hitpoints, level, inventory, nickname) { + this.name = name; + this.hitpoints = hitpoints; + this.level = level; + this.inventory = inventory || []; + this.nickname = nickname; + } + getDisplayName() { + return this.nickname || this.name; //if no nickname was provided, nickname will be undefined so name will be returned. + } + onAttack() { + console.log(`${this.getDisplayName()} attacked for 1 damage.`); + } + onDamage() { + console.log(`${this.getDisplayName()} took ${this.hitpoints - --this.hitpoints} damage!`); + if (this.hitpoints <= 0) { + this.onDeath(); + } + } + onDeath() { + console.log(`${this.getDisplayName()} died!`); + if(this.inventory.length > 0) { + console.log(`${this.getDisplayName()} dropped: `); + this.inventory.forEach((item) => console.log(`\t${item}`)); + } else { + console.log(`${this.getDisplayName()} didn't drop anything :(`); + } + } +} + +/** + We now have the basic features a monster should have as well as default onAttack and onDamage methods that will apply to any + Monsters that don't come with their own. Lets create our different Monsters and extend the Monster class so they all get these features. +**/ +class Zombie extends Monster { + constructor(stats) { + // You call `super` which calls Monster's constructor and lets Zombie inherit everything from Monster + super("Zombie", stats.hitpoints, stats.level, stats.inventory || ['sword'], stats.nickname); + this.rotten = 'hella'; + } +} + +class Skeleton extends Monster { + constructor(hitpoints, level, inventory = ["bow"], nickname) { + // You call `super` which calls Monster's constructor and lets Skeleton inherit everything from Monster + super("Skeleton", hitpoints, level, inventory, nickname); + } +} + +class Banshee extends Monster { + constructor(hitpoints, level, inventory = undefined, nickname) { + // You call `super` which calls Monster's constructor and lets Banshee inherit everything from Monster + super("Banshee", hitpoints, level, inventory, nickname); + } + onAttack() { + this.scream(); + // even though we are making our own onAttack method, we can still call the one from super + super.onAttack(); + } + scream() { + console.log("AHHHHHH!!!!") + } +} + +// Set up our dope battle simulator right here +function battle(mon1, mon2) { + if(Math.random() >= 0.5) { + mon1.onAttack(); + mon2.onDamage(); + } else { + mon2.onAttack(); + mon1.onDamage(); + } +} + +// Now we make some Monsters +const zombie = new Zombie({hitpoints: 5, level: 3}); +const skeleton = new Skeleton(5, 3); +const banshee = new Banshee(10, 3); +const fred = new Zombie({hitpoints: 1, level: 1, nickname: "Fred"}); +const jill = new Banshee(2000, 1000, inventory = ['The God Staff of Omar Illingisisisisisisi', 'The Magic School Bus Season 1 Collectors Edition'], "Hillary, Joe's Mom"); + +while (zombie.hitpoints > 0 && skeleton.hitpoints > 0) { + battle(skeleton, zombie); + console.log(" "); +} + +while (fred.hitpoints > 0 && banshee.hitpoints > 0) { + battle(fred, banshee); + console.log(" "); +} + +while (banshee.hitpoints > 0 && jill.hitpoints > 0) { + battle(banshee, jill); + console.log(" "); +} + +console.log(" "); +console.log("He has risin! Oh glorious Fred, may he bring glory to the heavens and rain fire on all those who appose him!!!"); +console.log(" "); +console.log(" "); + +fred.hitpoints = 200000; +fred.level = 9000+1; +fred.inventory.push('The God Staff of Omar Illingisisisisisisi +2'); + +fred.scream = function() { + console.log("Ooooga Boooggggaaa!!"); +} + +fred.onAttack = function() { + this.scream(); + console.log(`${this.getDisplayName()} attacked for !@#!$%@#~! damage.`); + jill.hitpoints = -100000; +} + +while (fred.hitpoints > 0 && jill.hitpoints > 0) { + battle(fred, jill); + console.log(" "); +} + +// Some further tests to show how these interact +console.log(`typeof zombie: ${typeof zombie}`); +console.log(`zombie instanceof Zombie: ${zombie instanceof Zombie}`); +console.log(`zombie instanceof Monster: ${zombie instanceof Monster}`); +console.log(`zombie instanceof Skeleton: ${zombie instanceof Skeleton}`); +console.log(`zombie instanceof Banshee: ${zombie instanceof Banshee}`); +console.log(`typeof zombie === typeof skeleton: ${typeof zombie === typeof skeleton}`); +console.log(`typeof zombie === typeof fred: ${typeof zombie === typeof fred}`); +// console.log(`zombie instanceof fred: ${zombie instanceof fred}`); //error because fred isn't a class/constructor. \ No newline at end of file diff --git a/notes/this-scope.js b/notes/this-scope.js new file mode 100644 index 0000000..869a914 --- /dev/null +++ b/notes/this-scope.js @@ -0,0 +1,57 @@ +class Fruit{ + constructor(type, color){ + this.getType = function(){return type}; + this.getColor = () => color; + } +} + +const apple = new Fruit("Apple", "Red"); + +// Fruit.prototype.message = () => `I am a ${this.getColor()} ${this.getType()}`; +// console.log(apple.message()); +/** + Uncaught TypeError: this.getColor is not a function + at Fruit.message (:10:48) + at :12:19 +**/ + +Fruit.prototype.message = function() { + return `I am a ${this.getColor()} ${this.getType()}`; +}; + +console.log(apple.message()); +/** + I am a Red Apple +**/ + +const orange = new Fruit("Orange", "Orange"); + +let fruitMessage = apple.message; + +console.log(fruitMessage); +// [Function] + +console.log(fruitMessage.call(orange)); +// I am a Orange Orange +console.log(fruitMessage.apply(orange)); +// I am a Orange Orange + +// console.log(fruitMessage()); +/** TypeError: this.getColor is not a function + `this` is evaluated when the code is run rather than when + the object is created. By calling fruitMessage() in this way, + you have taken apple.message out of apple's scope and moved it + into the global scope. `this.getColor()` is now looking for `getColor()` + in the global scope which is undefined. +**/ + +fruitMessage = apple.message.bind(orange); +/** bind doesn't execute the function + it hold onto it and will apply the `this` scope when you call it later +**/ + +console.log(fruitMessage); +// [Function] + +console.log(fruitMessage()); +// I am a Orange Orange! \ No newline at end of file diff --git a/src/arrays.js b/src/arrays.js index 26aaed3..2893e34 100644 --- a/src/arrays.js +++ b/src/arrays.js @@ -8,33 +8,67 @@ const each = (elements, cb) => { // Iterates over a list of elements, yielding each in turn to the `cb` function. // This only needs to work with arrays. // based off http://underscorejs.org/#each + for (let i = 0; i < elements.length; i++) { + cb(elements[i], i); + } }; const map = (elements, cb) => { // Produces a new array of values by mapping each value in list through a transformation function (iteratee). // Return the new array. + const arr = []; + each(elements, (item) => { arr.push(cb(item)); }); + return arr; }; const reduce = (elements, cb, memo = elements.shift()) => { // Combine all elements into a single value going from left to right. // Elements will be passed one by one into `cb`. // `memo` is the starting value. If `memo` is undefined then make `elements[0]` the initial value. + if (typeof memo === 'undefined') { + memo = elements[0]; + } + each(elements, (item) => { memo = cb(memo, item); }); + return memo; }; const find = (elements, cb) => { // Look through each value in `elements` and pass each element to `cb`. // If `cb` returns `true` then return that element. // Return `undefined` if no elements pass the truth test. + for (let i = 0; i < elements.length; i++) { + if (cb(elements[i])) { + return elements[i]; + } + } + return undefined; }; const filter = (elements, cb) => { // Similar to `find` but you will return an array of all elements that passed the truth test // Return an empty array if no elements pass the truth test + const array = []; + for (let i = 0; i < elements.length; i++) { + if (cb(elements[i])) { + array.push(elements[i]); + } + } + return array; }; const flatten = (elements) => { // Flattens a nested array (the nesting can be to any depth). // Example: flatten([1, [2], [3, [[4]]]]); => [1, 2, 3, 4]; + const array = []; + const cb = (item) => { + if (item instanceof Array) { + each(item, cb); + } else { + array.push(item); + } + }; + each(elements, cb); + return array; }; /* eslint-enable no-unused-vars, max-len */ diff --git a/src/class.js b/src/class.js index 8276e29..fa1947f 100644 --- a/src/class.js +++ b/src/class.js @@ -5,7 +5,17 @@ // Add a method called `comparePasswords`. `comparePasswords` should have a parameter // for a potential password that will be compared to the `password` property. // Return true if the potential password matches the `password` property. Otherwise return false. +const arrayMethods = require('../src/arrays'); +class User { + constructor(options) { + this.email = options.email; + this.password = options.password; + } + comparePasswords(compare) { + return this.password === compare; + } +} /* eslint-disable no-undef */ // Remove this comment once you write your classes. @@ -18,7 +28,24 @@ // Cat should have the property `name` that is set in the constructor and the method // `meow` that should return the string ` meowed!` where `` is the `name` // property set on the Cat instance. +class Animal { + constructor(options) { + this.age = options.age; + } + growOlder() { + return ++this.age; + } +} +class Cat extends Animal { + constructor(options) { + super(options); + this.name = options.name; + } + meow() { + return `${this.name} meowed!`; + } +} module.exports = { User, diff --git a/src/closure.js b/src/closure.js index 4c98af0..e0b9dfc 100644 --- a/src/closure.js +++ b/src/closure.js @@ -1,21 +1,32 @@ // Complete the following functions. +const arrayMethods = require('../src/arrays'); const counter = () => { // Return a function that when invoked increments and returns a counter variable. // Example: const newCounter = counter(); // newCounter(); // 1 // newCounter(); // 2 + let c = 0; + return () => ++c; }; const counterFactory = () => { // Return an object that has two methods called `increment` and `decrement`. // `increment` should increment a counter variable in closure scope and return it. // `decrement` should decrement the counter variable and return it. + let c = 0; + return { increment: () => ++c, decrement: () => --c }; }; const limitFunctionCallCount = (cb, n) => { // Should return a function that invokes `cb`. // The returned function should only allow `cb` to be invoked `n` times. + return (...rest) => { + if (n-- > 0) { + return cb(...rest); + } + return null; + }; }; const cacheFunction = (cb) => { @@ -25,6 +36,13 @@ const cacheFunction = (cb) => { // If the returned function is invoked with arguments that it has already seen // then it should return the cached result and not invoke `cb` again. // `cb` should only ever be invoked once for a given set of arguments. + const cache = {}; + return (arg) => { + if (!(arg in cache)) { + cache[arg] = cb(arg); + } + return cache[arg]; + }; }; /* eslint-enable no-unused-vars */ diff --git a/src/es6.js b/src/es6.js index eb846ab..73263ce 100644 --- a/src/es6.js +++ b/src/es6.js @@ -6,51 +6,41 @@ //---------------- // const, =>, default parameters, arrow functions default return statements using () +const arrayMethods = require('../src/arrays'); -var food = 'pineapple'; +let food = 'pineapple'; -var isMyFavoriteFood = function(food) { - food = food || 'thousand-year-old egg'; //This sets a default value if `food` is falsey - return food === 'thousand-year-old egg'; -}; +const isMyFavoriteFood = (food) => (food || 'thousand-year-old egg') === 'thousand-year-old egg'; -var isThisMyFavorite = isMyFavoriteFood(food); +const isThisMyFavorite = isMyFavoriteFood(food); //---------------- //const, class, template literals, enhanced object literals (foo: foo, -> foo,) -var User = function(options) { - this.username = options.username; - this.password = options.password; - this.sayHi = function() { - return this.username + ' says hello!'; - }; +class User { + constructor(options) { + this.username = options.username; + this.password = options.password; + this.sayHi = () => `${this.username} says hello!`; + } } -var username = 'JavaScriptForever'; -var password = 'password'; +const username = 'JavaScriptForever'; +const password = 'password'; -var me = new User({ - username: username, - password: password, -}); +const me = new User({ username, password }); // ---------------- // let, const, =>, ... (spread operator) -var addArgs = function () { - var sum = 0; - for (var i = 0; i < arguments.length; i++) { - sum += arguments[i]; - } - return sum; -}; +const addArgs = (...rest) => arrayMethods.reduce(...rest, (total, num) => total += num); -var argsToCb = function (cb) { - var args = Array.prototype.slice.call(arguments); - return cb.apply(null, args.splice(1)); -}; +const argsToCb = (cb, ...rest) => cb(...rest); -var result = argsToCb(addArgs, 1, 2, 3, 4, 5); //result should be 15 +const result = argsToCb(addArgs, 1, 2, 3, 4, 5); //result should be 15 /* eslint-enable */ +// console.log(isThisMyFavorite); // false +// console.log(me); // User {.......} +// console.log(me.sayHi()); // JavaScriptForever says hello! +// console.log(result); // 3 diff --git a/src/objects.js b/src/objects.js index ba39c6c..8a803b6 100644 --- a/src/objects.js +++ b/src/objects.js @@ -1,38 +1,65 @@ // Complete the following underscore functions. // Reference http://underscorejs.org/ for examples. +const arrayMethods = require('../src/arrays'); const keys = (obj) => { // Retrieve all the names of the object's properties. // Return the keys as strings in an array. // Based on http://underscorejs.org/#keys + + // not sure how to do this without just using an Object method + return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => array.concat(key), []); }; const values = (obj) => { // Return all of the values of the object's own properties. // Ignore functions // http://underscorejs.org/#values + return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => array.concat(value), []); }; const mapObject = (obj, cb) => { // Like map for arrays, but for objects. Transform the value of each property in turn. // http://underscorejs.org/#mapObject + const newObj = {}; + for (let i = 0; i < keys(obj).length; i++) { + newObj[keys(obj)[i]] = cb(values(obj)[i], keys(obj)[i]); + } + return newObj; }; const pairs = (obj) => { // Convert an object into a list of [key, value] pairs. // http://underscorejs.org/#pairs + const list = []; + mapObject(obj, (value, key) => { + list.push([key, value]); + }); + return list; }; const invert = (obj) => { // Returns a copy of the object where the keys have become the values and the values the keys. // Assume that all of the object's values will be unique and string serializable. // http://underscorejs.org/#invert + const newObj = {}; + mapObject(obj, (value, key) => { + newObj[value] = key; + }); + return newObj; }; const defaults = (obj, defaultProps) => { // Fill in undefined properties that match properties on the `defaultProps` parameter object. // Return `obj`. // http://underscorejs.org/#defaults + mapObject(defaultProps, (value, key) => { + if (!(key in obj)) { + obj[key] = value; + } + return value; + }); + return obj; }; /* eslint-enable no-unused-vars */ diff --git a/src/recursion.js b/src/recursion.js index a3e997e..24120c2 100644 --- a/src/recursion.js +++ b/src/recursion.js @@ -1,18 +1,42 @@ // Complete the following functions. +const arrayMethods = require('../src/arrays'); +const objectMethods = require('../src/objects'); const nFibonacci = (n) => { // fibonacci sequence: 1 2 3 5 8 13 ... // return the nth number in the sequence + if (n < 2) { + return 1; + } + return nFibonacci(n - 1) + nFibonacci(n - 2); }; const nFactorial = (n) => { // factorial example: !5 = 5 * 4 * 3 * 2 * 1 // return the factorial of `n` + if (n === 1) { + return 1; + } + return n * nFactorial(n - 1); }; const checkMatchingLeaves = (obj) => { - // return true if every property on `obj` is the same + // return true if every property on obj is the same // otherwise return false + const leaves = []; + const getLeaves = (branch) => { + if (leaves.length <= 1) { + if (typeof branch === 'object') { + arrayMethods.each(objectMethods.values(branch), twig => getLeaves(twig)); + } else if (leaves.indexOf(branch) < 0) { + leaves.push(branch); + } + } + }; + + getLeaves(obj); + + return leaves.length <= 1; }; /* eslint-enable no-unused-vars */ diff --git a/src/this.js b/src/this.js index 8ea3020..d0d6d5c 100644 --- a/src/this.js +++ b/src/this.js @@ -5,10 +5,15 @@ class User { constructor(options) { // set a username and password property on the user object that is created + this.username = options.username; + this.password = options.password; } // create a method on the User class called `checkPassword` // this method should take in a string and compare it to the object's password property // return `true` if they match, otherwise return `false` + checkPassword(passwordToCompare) { + return this.password === passwordToCompare; + } } const me = new User({ username: 'LambdaSchool', password: 'correcthorsebatterystaple' }); @@ -19,13 +24,31 @@ const checkPassword = function comparePasswords(passwordToCompare) { // use `this` to access the object's `password` property. // do not modify this function's parameters // note that we use the `function` keyword and not `=>` + return this.password === passwordToCompare; }; // invoke `checkPassword` on `me` by explicitly setting the `this` context // use .call, .apply, and .bind // .call +checkPassword.call(me, 'correcthorsebatterystaple'); +checkPassword.call(me, 'thisshouldbefalse'); // .apply +checkPassword.apply(me, ['correcthorsebatterystaple']); +checkPassword.apply(me, ['thisshouldbefalse']); // .bind +checkPassword.bind(me, 'correcthorsebatterystaple')(); +checkPassword.bind(me, 'thisshouldbefalse')(); + +// console.log(result); // true + +// console.log(checkPassword.call(me, 'correcthorsebatterystaple')); // true +// console.log(checkPassword.call(me, 'thisshouldbefalse')); // false + +// console.log(checkPassword.apply(me, ['correcthorsebatterystaple'])); // true +// console.log(checkPassword.apply(me, ['thisshouldbefalse'])); // false + +// console.log(checkPassword.bind(me, 'correcthorsebatterystaple')()); // true +// console.log(checkPassword.bind(me, 'thisshouldbefalse')()); // false From e38b7da82515c2864b2eacec59a9715b47a81625 Mon Sep 17 00:00:00 2001 From: Jourdan Clark Date: Mon, 11 Sep 2017 13:32:41 -0600 Subject: [PATCH 2/2] Replace concat with push cause it made more sense to not risk modifying stuff being put into the array. Passed a index into sevel arrays.js methods cause I forgot to do that before. --- src/arrays.js | 4 ++-- src/objects.js | 10 ++++++++-- src/recursion.js | 11 ++++++++++- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/arrays.js b/src/arrays.js index 2893e34..c76dda0 100644 --- a/src/arrays.js +++ b/src/arrays.js @@ -17,7 +17,7 @@ const map = (elements, cb) => { // Produces a new array of values by mapping each value in list through a transformation function (iteratee). // Return the new array. const arr = []; - each(elements, (item) => { arr.push(cb(item)); }); + each(elements, (item, index) => { arr.push(cb(item, index)); }); return arr; }; @@ -28,7 +28,7 @@ const reduce = (elements, cb, memo = elements.shift()) => { if (typeof memo === 'undefined') { memo = elements[0]; } - each(elements, (item) => { memo = cb(memo, item); }); + each(elements, (item, index) => { memo = cb(memo, item, index); }); return memo; }; diff --git a/src/objects.js b/src/objects.js index 8a803b6..e980212 100644 --- a/src/objects.js +++ b/src/objects.js @@ -8,14 +8,20 @@ const keys = (obj) => { // Based on http://underscorejs.org/#keys // not sure how to do this without just using an Object method - return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => array.concat(key), []); + return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => { + array.push(key); + return array; + }, []); }; const values = (obj) => { // Return all of the values of the object's own properties. // Ignore functions // http://underscorejs.org/#values - return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => array.concat(value), []); + return arrayMethods.reduce(Object.entries(obj), (array, [key, value]) => { + array.push(value); + return array; + }, []); }; const mapObject = (obj, cb) => { diff --git a/src/recursion.js b/src/recursion.js index 24120c2..d8cc533 100644 --- a/src/recursion.js +++ b/src/recursion.js @@ -22,9 +22,17 @@ const nFactorial = (n) => { const checkMatchingLeaves = (obj) => { // return true if every property on obj is the same - // otherwise return false + // otherwise return + // + // Used an array to store an undefined value. + // If I used something like leaf = undefined and looped + // through checking if leaf was still undefined, + // it would return true in cases where some + // object's values were set to undefined + // and all others were set to 1 or whatever else const leaves = []; const getLeaves = (branch) => { + // if it finds more than two leaves, it skips over everything else if (leaves.length <= 1) { if (typeof branch === 'object') { arrayMethods.each(objectMethods.values(branch), twig => getLeaves(twig)); @@ -36,6 +44,7 @@ const checkMatchingLeaves = (obj) => { getLeaves(obj); + // empty object is truthy return leaves.length <= 1; };