Objects are the most important concept in JavaScript. But wait, didn't we already say that functions are the most important concept in JavaScript?! Yes we did. But functions are actually objects in JavaScript (and so are arrays)! In fact, almost everything in JavaScript is an object.
Like arrays, objects can store a wide variety of data. We can imagine that an
array is like a locker room where each locker has a number. When you look into
the locker, there could be anything in there. But with objects, each locker can
have a string label. Wouldn't it be better if your locker simply had your name
instead of a number you had to memorize? The label is called a key
and the
locker content is called the value
.
To create an object you use {}
.
const student1 = {first: 'Harry',last: 'Potter',age: 25}const student2 = {first: 'Ron',last: 'Weasley',age: 24}const total = student1.age + student2.age // total is 49
In the example above, we created 2 student objects:
Then, we used total
to show you how to access a value associated with a key.
This notation might look familiar; it turns out that by this point, you have
used many objects already.
console.log('hello')/*console is an object created by an engineer for you to use.console has a key called log, and the value at that key is a function.console.log("hello") runs the function thatis stored in the console object under the key "log".*/
Example implementation - using console2
as the object
const console2 = {log: data => {alert(data)}}// Now, you can run the function by calling console2.logconsole2.log('hello')console2.log('World')
Examples of other objects
document.querySelector
querySelector
, and the value of that key is a
function.document.querySelector('.submit')
runs the function that is stored in the
document object under the key querySelector
.localStorage.getItem
getItem
, and the value of that key is a
function.localStorage.getItem('mydata')
runs the function that is stored in the
localStorage object under the key getItem
.const obj = {name: 'joe',age: 940,isStudent: false}
Friend Age Notebook (part 2): Modify your friend age notebook to use an array of objects instead of a 2D array.
If you haven’t cleared your localStorage yet, you’ll need to do it now or use a different key to store your data for part 2. Any data already in there won’t be compatible with the new object model and will show up as ‘undefined.’
buttonElement.onclick = () => {friends.unshift({name: nameInput.value,age: ageInput.value})}
You can also access the values of an object using the []
notation that you're
familiar with from arrays.
document.querySelector('.button')
document['querySelector']('.button')
console.log('hello')
console['log']('hello')
To get a value in an object, the code is very similar to getting a value in an array. We pass in a string (key) instead of an index number.
const student = {first: 'Harry',last: 'Potter',age: 25}const firstName = student['first'] // firstName has the value 'Harry'const student2 = studentstudent2['name'] = 'last'// what is student2?// what is student?// The benefit of using [] for getting values is that you can use variables as keys.student2[student2.name] = 'Weasley'// what is student2?// what is student?
The benefit of using [] for getting values is that you can use variables as keys.
const student = {first: 'Harry',last: 'Potter',age: 25}const firstName = student['first'] // firstName has the value 'Harry'const student2 = studentstudent2['name'] = 'last'/*student2 is the same as student, which is:{name: 'last',first: 'Harry',last: 'Potter',age: 25}*/student2[student2.name] = 'Weasley'/*Notice how student2.name is a variable that contains the string 'last'Therefore, it evaluates tostudent2.last = 'Weasley'student2 is the same as student, which is:{name: 'last',first: 'Harry',last: 'Weasley',age: 25}*/
Like arrays, objects are non-primitive.
const star = { name: 'Tarzan' }const star2 = starstar2['friend'] = starstar2['friend']['lover'] = 'Jane'// what is star2?// what is star?star['name'] = 'Sarah'// what is star2?// what is star?const allStars = [star, star2]allStars[0]['lover'] = allStars[1]['name']// what is star2?// what is star?// what is allStars?
// Section 1:/*star is the same as star2, which is:{name: 'Tarzan',friend: Object,lover: "Jane"}*/// Section 2:/*star is the same as star2, which is:{name: 'Sarah',friend: Object,lover: "Jane"}*/// section 3/*star is the same as star2, which is:{name: 'Sarah',friend: Object,lover: "Sarah"}*//*allStars is:[{ name: 'Sarah', friend: [Circular], lover: 'Sarah'},{ name: 'Sarah', friend: [Circular], lover: 'Sarah'}]*/
Values can be anything, including a function!
const snacks = {nutella: () => {return 200},pixyStix: () => {return 9},lays: () => {return 135}}let calories = snacks['nutella']() // what is calories?calories = snacks['pixyStix']() // what is calories?calories = snacks['lays']() // what is calories?
// calories -> 200// calories -> 9// calories -> 135
Here are some more examples with functions for you to work through. In the first
one, we introduce the building blocks of a concept called promises that
we'll get to later in this chapter. We'll learn a function called fetch()
that
returns a promise object. The promise object has a then()
property, which
returns a promise object so you can keep chaining more then() properties
together until what you want to accomplish is completed:
fetch().then().then().then()
etc. Here we introduce this kind of behavior
where an object's function returns the object itself.
// Problem 1:const magician = {perform: () => {return magician}}const houdini = magician.perform().perform() // what is houdini?const same = magician === houdini // what is same?/* Problem 2: Create a prepareStage object with a then property so the codebelow will not cause an error.*/prepareStage.then().then().then()/* Problem 3: Create the prepareStage object with a then property thatconsole.logs each input: Should log Squirtle, Wartortle, Blastoise*/prepareStage.then('Squirtle').then('Wartortle').then('Blastoise')/* Problem 4: Create the prepareStage object with a then property thatexecutes its function argument: Should log Abracadabra! 3 times*/const performMagic = () => {console.log('Abracadabra!')}prepareStage.then(performMagic).then(performMagic).then(performMagic)
/* Problem 1: houdini is:{perform: () => {return magician}}same is true*//* Problem 2:const prepareStage = {then: () => {return prepareStage}}*//* Problem 3:const prepareStage = {then: (input) => {console.log(input)return prepareStage}}*//* Problem 4:const prepareStage = {then: (input) => {input()return prepareStage}}*/
Tests
describe('addKV function', () => {it('should add a key and value to an object', () => {const marvel = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}fn.addKV(marvel, 'antman', 'funny')expect(marvel.antman).toEqual('funny')})it('should add a key and value to an object', () => {const marvel = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong',antman: 'funny'}fn.addKV(marvel, 'wonderwoman', 'smart')expect(marvel.wonderwoman).toEqual('smart')})it('should add a key and value to an object', () => {const marvel = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong',antman: 'funny',wonderwoman: 'smart'}const b = ['leader', 'honest']fn.addKV(marvel, 'captainamerica', ['leader', 'honest'])expect(marvel.captainamerica).toEqual(b)})})
Shape
const addKV = (obj, key, val) => {obj[key] = val}
Explanation
obj
, a string key
and a string val
.val
to obj
at a given key
.Code
const addKV = (obj, key, val) => {obj[key] = val}
Did you try a.b = c? Remember that using a . accesses exactly the key that comes after the .. To look inside a variable and access that key, you need [].
Write a function called filterNonKeys that filters an array to only include strings that are also keys in a given object.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const avengers = ['ironman', 'strange', 'thor', 'spiderman', 'hulk']const result = filterNonKeys(avengers, info)// result is ["ironman", "spiderman", "hulk"]
To check if a key exists in an object, just try to access it—if it doesn’t exist you’ll get undefined, which is falsey.
Tests
describe('filterNonKeys function', () => {const avengers = ['ironman', 'strange', 'thor', 'spiderman', 'hulk']const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}it('should return an empty array when filtering on an empty object', () => {const result = fn.filterNonKeys(avengers, {})expect(result).toEqual([])})it('should return an empty array when starting with an empty array', () => {const result = fn.filterNonKeys([], info)expect(result).toEqual([])})it('should return an empty array if no matches are found', () => {const b = ['batman', 'superman', 'flash']const result = fn.filterNonKeys(b, info)expect(result).toEqual([])})})
Shape
const filterNonKeys = (arr, obj) => {}
Explanation
arr
and an object obj
.filter
to filter out the matching keys from the
obj
.Code
const filterNonKeys = (arr, obj) => {return arr.filter(e => {return obj[e]})}
Write a function called addDescriptions that adds a description key to each object in an array. The description should go with the name that matches the key in the input object.
const characters = [{ name: 'ironman' },{ name: 'spiderman' },{ name: 'hulk' }]const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}addDescriptions(characters, info)/* characters is changed to:[{name: "ironman", description: "arrogant"},{name: "spiderman", description: "naive"},{name:"hulk", description: "strong"}]*/
Tests
describe('addDescriptions function', () => {it('should add 3 descriptions to corresponding names', () => {const characters = [{ name: 'ironman' },{ name: 'spiderman' },{ name: 'hulk' }]const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}fn.addDescriptions(characters, info)expect(characters).toEqual([{ name: 'ironman', description: 'arrogant' },{ name: 'spiderman', description: 'naive' },{ name: 'hulk', description: 'strong' }])})it('should not add descriptions to objects without names', () => {const characters = [{ tonyStark: 'ironman' },{ peterParker: 'spiderman' },{ name: 'hulk' }]const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}fn.addDescriptions(characters, info)expect(characters).toEqual([{ tonyStark: 'ironman' },{ peterParker: 'spiderman' },{ name: 'hulk', description: 'strong' }])})it('should ignore unmatched keys', () => {const characters = [{ name: 'ironman' },{ name: 'rocket' },{ name: 'drax' }]const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}fn.addDescriptions(characters, info)expect(characters).toEqual([{ name: 'ironman', description: 'arrogant' },{ name: 'rocket' },{ name: 'drax' }])})})
Shape
const addDescription = (chars, obj) => {}
Explanation
chars
and an object obj
.chars
and assign "description"
key.description
is the value of the obj
at a given name.Code
const addDescriptions = (chars, obj) => {chars.forEach(e => {e.description = obj[e.name]})return chars}
Write a function called countOccurrences that returns an object that counts how many times each item occurs in an array.
const abc = ['abc', 'a', 'abc', 'b', 'abc', 'a', 'b', 'c', 'abc']const result = countOccurrences(abc)// should return: {abc: 4, a: 2, b: 2, c: 1}
Tests
describe('countOccurrences function', () => {it('should count occurrences of strings', () => {const abc = ['abc', 'a', 'abc', 'b', 'abc', 'a', 'b', 'c', 'abc']const result = fn.countOccurrences(abc)expect(result).toEqual({ abc: 4, a: 2, b: 2, c: 1 })})it('should count occurrences of numbers', () => {const nums = [0, 3, 3, 1, 0, 0, 3, 0, 0, 2]const result = fn.countOccurrences(nums)expect(result).toEqual({ 0: 5, 3: 3, 1: 1, 2: 1 })})it('should return an empty object for an empty array', () => {const result = fn.countOccurrences([])expect(result).toEqual({})})})
Shape
const count0occurences = arr => {}
Explanation
arr
.reduce
Code
const countOccurrences = arr => {return arr.reduce((acc, e) => {acc[e] = (acc[e] || 0) + 1return acc}, {})}
We could have also done this using forEach
: initialize an empty object
outside, then call arr.forEach
and pass in a function that adds to the object,
then return it after the forEach call. But reduce
is an extremely versatile
function, and can simplify this task down to one function call.
Note that we added 1 to acc[e] || 0
in case acc[e]
isn't defined yet--adding
1 to undefined
results in NaN
.
The Object type comes with several "helper" functions that let you find and modify objects' keys and values:
This function takes in an object and returns an array of keys in the object.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = Object.keys(info)// result is ["ironman", "spiderman", "hulk"]
Why isn't it info.keys()
?
Most of the helper functions for Object are static functions rather than the prototype functions we learned about for arrays. One reason is that objects provide more opportunities for key collisions—for example your object could have a key called keys or values.
This function takes in an object and returns an array of values in the object.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = Object.values(info)// result is ["arrogant", "naive", "strong"]
This function takes in an object and returns an array. Each element of the array is an array with 2 elements, a key and its corresponding value in the object.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = Object.entries(info)// result is [["ironman", "arrogant"], ["spiderman", "naive"], ["hulk", "strong"]]
Notice how this method is attached to the prototype
of Object. Really, all
we need to understand about this is that every Object instance (which is
basically everything in JavaScript, since everything is an Object) has this
method available to them.
The hasOwnProperty
method allows us to check if a particular property exists
ONLY on the object in context and not down the prototype chain of the
object. Go here to read more on
prototypes.
Let's look at an example to see why we may use this method.
const myObj = {name: 'test',age: 100}myObj.hasOwnProperty('name') // true!!myObj['name'] // truemyObj.hasOwnProperty('age') // true!!myObj['age'] // truemyObj.hasOwnProperty('height') // false!!myObj['height'] // falsemyObj.hasOwnProperty('weight') // false!!myObj['weight'] // falsemyObj.hasOwnProperty('toString') // FALSE!!myObj['toString'] // TRUE
In the example above we are comparing hasOwnProperty
vs just trying to use
the []
notation to find a key inside of an Object. Putting !!
in front just
allows us to convert the value into a boolean to compare the returns values.
Now, the question is why does myObj.hasOwnProperty('toString')
return
false but !!myObj['toString']
returns true? When you use []
notation, you are looking for any property that exists on the Object OR down
that Object's prototype chain! Using .hasOwnProperty()
ONLY looks at the
Object's properties.
When you start working with libraries and using Objects that have been defined
somewhere else for you, you have no idea what is defined on the prototype
chains. You may be able to get by with just checking properties using []
notation, but it's always better to be safe than sorry.
It is best practice to use .hasOwnProperty()
in cases where you ONLY
want to see if a property exists on the Object itself and not down the
prototype chain.
This function deletes the key (and its value) from an object. Example:
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong',thanos: 'powerful'}delete info.thanos// after delete, info becomes:// {ironman: "arrogant", spiderman: "naive", hulk: "strong"}
Shouldn't it be Object.delete(info)
?
Just when you were getting used to everything being a static function of the Object type, we switched it up by introducing delete! delete is actually an operator, and it can work on array elements and some variables, too—but in practice, you'll mostly see it used on objects.
Write a function called longestString that finds the longest value string in an object.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = longestString(info) // result should be "arrogant"
Tests
describe('longestString function', () => {it('should find the longest string from the beginning of an object', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}expect(fn.longestString(info)).toEqual('arrogant')})it('should find the longest string from the end of an object', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}expect(fn.longestString(leaders)).toEqual('Sabrina')})it('should return the empty string for an empty object', () => {expect(fn.longestString({})).toEqual('')})})
Shape
const longestString = obj => {}
Explanation
obj
.reduce
to go through each string value and find
the longest string in the obj
.Code
const longestString = obj => {return Object.values(obj).reduce((str, e) => {if (e.length > str.length) return ereturn str}, '')}
This was easy because we were just working with values, and could "throw away" the object itself. The next one will require you to make use of the keys array while still referring back to the entire object, and there are a couple of different ways to do it. See if you can figure one out!
Write a function called keyOfLongestString that finds the longest value string but returns its key.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = keyOfLongestString(info) // result should be "ironman"// because "arrogant" is longer than "naive" and "strong"const info2 = {a: 'xxxxxx',bc: 'xx',abc: 'xxx'}const result2 = keyOfLongestString(info2) // result2 should be "a"// "xxxxxx" is longer than "xx" and "xxx"
Tests
describe('keyOfLongestString function', () => {it('should find key of longest string in the beginning of an object', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}expect(fn.keyOfLongestString(info)).toEqual('ironman')})it('should find key of longest string at the end of an object', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}expect(fn.keyOfLongestString(leaders)).toEqual('saffron')})it('should return undefined (no key) for an empty object', () => {expect(fn.keyOfLongestString({})).toEqual(undefined)})})
Shape
const keyOfLongestString = obj => {}
Explanation
obj
.allKeys
) and it will store all the
keys from the obj
.allKeys
reaches 0, return undefined.longestKey
. It finds the
longest key by using array method reduce
to compare the length of the
values and return the key with the longest value.allKeys
.Code
const keyOfLongestString = obj => {const allKeys = Object.keys(obj)if (allKeys.length === 0) return undefinedconst longestKey = allKeys.reduce((lKey, key) => {if (obj[key].length > obj[lKey].length) {return key}return lKey}, allKeys[0])return longestKey}
By now whenever you see an array (or something that should be turned into an
array) and need to look at all elements and boil them down to one answer—whether
it’s a length; a sum; another array; or an answer like the longest, greatest, or
shortest, or smallest—you should be thinking of reduce
. It’s quite a useful
function!
Like we said in the last debrief, there are several other ways you could have
written this function, such as reducing as Object.entries(obj)
.
Use your keyOfLongestString
function to write a function called
removeLongestString.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}removeLongestString(info)// info is changed to:// {spiderman: "naive", hulk: "strong"}
Tests
describe('removeLongestString function', () => {it('should remove the longest string in the beginning of an object', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}fn.removeLongestString(info)expect(info).toEqual({ spiderman: 'naive', hulk: 'strong' })})it('should remove the longest string at the end of an object', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}fn.removeLongestString(leaders)expect(leaders).toEqual({vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga'})})it('should work on an empty object', () => {const imEmpty = {}fn.removeLongestString(imEmpty)expect(imEmpty).toEqual({})})})
Shape
const removeLongestString = obj => {}
Explanation
obj
.longestKey
). It stores the
result of passing in the obj
as an argument.obj
is at key longestKey
.Code
const removeLongestString = obj => {const longestKey = keyOfLongestString(obj)delete obj[longestKey]}
Write a function called commas that returns a string of all of an object's values separated by commas.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = commas(info)// result is 'arrogant, native, strong'
Tests
describe('commas function', () => {it('should separate three items', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}expect(fn.commas(info)).toEqual('arrogant, naive, strong')})it('should put no commas if only one element', () => {expect(fn.commas(['funny'])).toEqual('funny')})it('should return an empty string if no elements', () => {expect(fn.commas([])).toEqual('')})})
Shape
const commas = obj => {}
Explanation
obj
.obj
. Attach reduce
method to it.reduce
method,firstComma
) which starts at empty
string.i
reaches 0, firstComma equals empty string.acc
+ firstComma
+ e
(In this case accumulator is result
)Code
const commas = obj => {return Object.values(obj).reduce((result = '', e, i) => {let firstComma = ', 'if (i === 0) {firstComma = ''}return result + firstComma + e}, '')}
Feeling good about manipulating objects? In the last exercise you'll start building some skills that you’ll need to make use of objects in an HTML environment.
Write a function called headers that joins all of an object's keys inside
<h1>
tags.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = headers(info)// result is '<h1>ironman</h1><h1>spiderman</h1><h1>hulk</h1>'
Tests
describe('headers function (part 1)', () => {it('should create h1s for 3 items', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}expect(fn.headers(info)).toEqual('<h1>ironman</h1><h1>spiderman</h1><h1>hulk</h1>')})it('should create headers for 4 elements', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}expect(fn.headers(leaders)).toEqual('<h1>vermilion</h1><h1>cinnabar</h1><h1>fuchsia</h1><h1>saffron</h1>')})it('should return an empty string if no elements', () => {expect(fn.headers([])).toEqual('')})})
Shape
const headers = () => {}
Explanation
obj
.obj
and attach reduce
method to it.reduce
function,h1
tag wrapped around each element.Code
const headers = obj => {return Object.keys(obj).reduce((result, e, i) => {return result + '<h1>' + e + '</h1>'}, '')}
Part 2
Modify your function so that it returns both keys and values inside the
<h1>
tags.
const result2 = headers(info)// result2 is '<h1>ironman: arrogant</h1><h1>spiderman: naive</h1><h1>hulk: strong</h1>'
Tests
describe('headers function (part 2)', () => {it('should create h1s for 3 items', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}// The line breaks are just for ease of reading;// they won't count as part of the expected// solution since they're escaped with \const exp ='<h1>ironman: arrogant</h1>\<h1>spiderman: naive</h1><h1>hulk: strong</h1>'expect(fn.headers(info)).toEqual(exp)})it('should create headers for 4 elements', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}const exp ='<h1>vermilion: Surge</h1>\<h1>cinnabar: Blaine</h1><h1>fuchsia: Koga</h1>\<h1>saffron: Sabrina</h1>'expect(fn.headers(leaders)).toEqual(exp)})it('should return an empty string if no elements', () => {expect(fn.headers([])).toEqual('')})})
Shape
const headers = obj => {}
Explanation
obj
.obj
using object helper method entries
. Attach
reduce
method to it.reduce
function,h1
tag.Code
const headers = obj => {return Object.entries(obj).reduce((result, e, i) => {return `${result}<h1>${e[0]}: ${e[1]}</h1>`}, '')}
Part 3
Finally, modify your function so that it returns a string of joined keys and
values, separated into <div>
s and with keys in <h1>
s and values in
<h2>
s.
const result3 = headers(info)// result3 is '<div><h1>ironman</h1><h2>arrogant</h2></div><div><h1>spiderman</h1><h2>naive</h2></div><div><h1>hulk</h1><h2>strong</h2></div>'
Tests
describe('headers function (part 3)', () => {it('should create h1s for 3 items', () => {const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const exp ='<div><h1>ironman</h1><h2>arrogant</h2></div>\<div><h1>spiderman</h1><h2>naive</h2></div>\<div><h1>hulk</h1><h2>strong</h2></div>'expect(fn.headers(info)).toEqual(exp)})it('should create headers for 4 elements', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}const exp ='<div><h1>vermilion</h1><h2>Surge</h2></div>\<div><h1>cinnabar</h1><h2>Blaine</h2></div>\<div><h1>fuchsia</h1><h2>Koga</h2></div>\<div><h1>saffron</h1><h2>Sabrina</h2></div>'expect(fn.headers(leaders)).toEqual(exp)})it('should return an empty string if no elements', () => {expect(fn.headers([])).toEqual('')})})
Shape
const headers = obj => {}
Explanation
obj
.obj
. Attach reduce
method to it.reduce
function,h1
tag
and wrap the obj
at that key with h2
tags.h1
and h2
tags with div
tag.Code
const headers = obj => {return Object.keys(obj).reduce((result, e, i) => {return `${result}<div><h1>${e}</h1><h2>${obj[e]}</h2></div>`}, '')}
Prototype functions are great for memory efficiency when you plan on constructing many Objects.
Want to add functions into every object that you create? Just assign them to
Object.prototype
. To make this work, you must:
Define your function using function( ... params ... ) { ... code ...}
It is important NOT to use an arrow-function () => {}
here because
of how it treats this
- You can read more on arrow-functions here
MDN Arrow Functions
Assign your function to Object.prototype
Access object properties using the this
keyword
Object.prototype.forEach = function (fun,i = 0,entries = Object.entries(this)) {if (i === entries.length) returnfun(entries[i][1], entries[i][0])return this.forEach(fun, i + 1)}
You can also add new properties to this
using prototype functions.
Object.prototype.eat = function (value) {const num = this.data || 0if (value < num) {return}this.data = value}const a = { name: 'iron' }a.eat(5)// a is: { name: "iron", data: 5 }a.eat(3)// nothing happens because 3 is smaller than 5// a is: { name: "iron", data: 5 }a.eat(30)// 30 is bigger than 5, so update data property// a is: { name: "iron", data: 30 }
new
" KeywordWhen you construct a new Object with the new
keyword, there are a few things
that are happening in the background for you. This involves how the this
is
defined and used.
Let's look at an example to see what's happening.
function Person(name, age) {this.name = namethis.age = age}const me = new Person('Joe', 24)
The Person
function is nothing special, it is just like any other function we
use in JavaScript. What makes it special is HOW we use it, and the new
keyword.
When I do something like new Person()
JavaScript will handle constructing a
new Object for me and set up the Prototype Inheritance for me.
This is how the Person function is being used when we call it with new
.
function Person(name, age) {// this part is done for us implicitly "behind the scenes"const this = Object.create(Person.prototype)this.name = namethis.age = age// this part is done for usreturn this}
With the above code, I could use the Person function the same way as before
WITHOUT the new
keyword and construct a new Object just by calling
Person()
. Of course, why do the extra work when we can let JavaScript handle
it for us by using the new
keyword?
The main takeaway from this is what's happening behind-the-scenes and also, what Object.create() does for us. Object.create is how inheritance is set up in JavaScript, it allows you to create the "prototype chain" for a new Object. You can read more about it here MDN Object.create.
This is a view of the JavaScript console on the Chrome browser. The >
is where
code was input into the console and the <-
is the output from the code that
preceded.
Person
function to use as an Object constructor.myName
function on the prototype property of Person.
(prototype is a property that exists on functions already, see Note
below)j
variable.j
contains. (from the code output you can see it is a
Person object)j
which will
show us the prototype chain (**proto** is deprecated and
Object.getPrototypeOf(obj)
should be used instead, see Note below)Below is a note from the MDN page which talks all about prototypes in JavaScript. MDN JS Prototypes:
Note: It's important to understand that there is a distinction between an object's prototype (available via Object.getPrototypeOf(obj), or via the deprecated proto property) and the prototype property on constructor functions.
The constructor function Foobar() has its own prototype, which can be found by calling Object.getPrototypeOf(Foobar). However this differs from its prototype property, Foobar.prototype, which is the blueprint for instances of this constructor function.
If we were to create a new instance — let fooInstance = new Foobar() — fooInstance would take its prototype from its constructor function's prototype property. Thus Object.getPrototypeOf(fooInstance) === Foobar.prototype.
Time to be creative and write some prototype functions!
Write a forEach function for objects.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = info.forEach((key, value, i) => {console.log(key, value, i)})// Will print out the following:/*ironman arrogant 0spiderman naive 1hulk strong 2*/// No need to set module.exports because there is nothing to export
Tests
describe('forEach function', () => {it('should run a function 3 times on 3 elements', () => {const fun = jest.fn()const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}info.forEach(fun)expect(fun).toHaveBeenCalledTimes(3)})it('should run a function 0 times on an empty object', () => {const fun = jest.fn()const imEmpty = {}imEmpty.forEach(fun)expect(fun).not.toHaveBeenCalled()})it('should let functions access object values & positions', () => {const vals = []const fun = (_k, v, i) => {vals.push(i + v)}const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}info.forEach(fun)expect(vals).toEqual(['0arrogant', '1naive', '2strong'])})})
Shape
Object.prototype.forEach = function (cb) {}
Explanation
cb
.i
) to keep track of index
which starts at 0.keys
) to get all the
keys of the object given this
.i
reaches the length of keys
, return.Code
Object.prototype.forEach = function (cb, i = 0, keys = Object.keys(this)) {if (i === keys.length) returncb(keys[i], this[keys[i]], i, this)return this.forEach(cb, i + 1, keys)}
Write a filter function for objects.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = info.filter((key, value) => {return key[0] === 'i' || value[0] === 'n'})console.log(result) // should print out:/*{ironman: "arrogant",spiderman: "naive"}*/
Tests
describe('filter function', () => {const leaders = {vermilion: 'Surge',cinnabar: 'Blaine',fuchsia: 'Koga',saffron: 'Sabrina'}it('should filter based on keys', () => {const seven = k => {return k.length === 7}const result = leaders.filter(seven)expect(result).toEqual({ fuchsia: 'Koga', saffron: 'Sabrina' })})it('should filter based on keys', () => {const six = (_k, v) => {return v.length < 6}const result = leaders.filter(six)expect(result).toEqual({ vermilion: 'Surge', fuchsia: 'Koga' })})it('should return an empty object if no matches', () => {const celadon = k => {return k === 'Celadon'}const result = leaders.filter(celadon)expect(result).toEqual({})})})
Shape
Object, (prototype.filter = function (cb) {})
Explanation
cb
.result
) and it starts with
an empty object.i
) to track index and it
starts at 0.keys
) to get all the
keys of the object given this
.i
reaches the length of keys
, return result
.currentKey
) and it starts with
first index of keys
.element
) and it stores the result
of calling the callback function.element
is true, result
at key currentKEy
is equal to this
at
key currentKey
.Code
Object.prototype.filter = function (cb,result = {},i = 0,keys = Object.keys(this)) {if (i === keys.length) return resultconst currentKey = keys[i]const element = cb(currentKey, this[currentKey])if (element == true) result[currentKey] = this[currentKey]return this.filter(cb, result, i + 1, keys)}
Write a reduce function for objects.
const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = info.reduce((acc, key, value, i) => {return acc + `${key}-${i}-${value},`}, '')console.log(result) // should print out:/*ironman-0-arrogant,spiderman-1-naive,hulk-2-strong,*/
Tests
describe('reduce function', () => {it('should let functions access keys, values, & positions', () => {const fun = (acc, key, value, i) => {return acc + `${key}-${i}-${value},`}const info = {ironman: 'arrogant',spiderman: 'naive',hulk: 'strong'}const result = info.reduce(fun, '')const exp = 'ironman-0-arrogant,spiderman-1-naive,hulk-2-strong,'expect(result).toEqual(exp)})it('should return the starting value if the object is empty', () => {const imEmpty = {}const result = imEmpty.reduce(() => {}, 'I am Groot')expect(result).toEqual('I am Groot')})})
Shape
Object.prototype.reduce = function (cb) {}
Explanation
cb
.acc
.i
) to track index and it
starts at 0.keys
) to get all the
keys of the object given this
.i
equals the length of keys, return acc
.next
) and it stores the result of
calling the callback function.Code
Object.prototype.reduce = function (cb,acc,i = 0,keys = Object.keys(this)) {if (i === keys.length) return accconst next = cb(acc, keys[i], this[keys[i]], i)return this.reduce(cb, next, i + 1, keys)}
You’ve now gotten some insight into how three of the array helper functions work
by implementing them for objects. They’re pretty simple once you get the hang of
them! All our solutions used recursion, and some sort of result or accumulator
variable to keep track of what the function is doing. The main decision was how
to iterate through the object; keys don’t work like indices where we could say
obj[0]
, obj[1]
, etc.
For all our solutions we chose to pull out the keys into an array, then iterate
through the keys, using the keys to pull their corresponding values from the
object (key = keys[i]
, value = this[keys[i]]
). We could have also pulled the
values separately using something like values = Object.values(this)
and
values[i]
to get each value. We could have even used Object.entries(this)
and then entries[i][0]
for the keys and entries[i][1]
for the values.
Write a getCharCount prototype function for arrays of strings that returns an object of character counts.
;['Charmander', 'Charmeleon', 'Charizard'].getCharCount()/** Returns{C: 3,h: 3,a: 5,r: 5,m: 2,n: 2,d: 2,e: 3,l: 1,o: 1,i: 1,z: 1}*/
Tests
describe('getCharCount function', () => {it('should count letters in an array of 3 strings', () => {const result = ['Charmander', 'Charmeleon', 'Charizard'].getCharCount()expect(result).toEqual({C: 3,h: 3,a: 5,r: 5,m: 2,n: 2,d: 2,e: 3,l: 1,o: 1,i: 1,z: 1})})it('should handle an empty array', () => {const result = [].getCharCount()expect(result).toEqual({})})it('should count characters in empty strings', () => {const result = ['Pallet', '', 'Pewter', '', 'Saffron'].getCharCount()expect(result).toEqual({P: 2,a: 2,l: 2,e: 3,t: 2,w: 1,r: 2,S: 1,f: 2,o: 1,n: 1})})})
Shape
Array.prototype.getCharCount = function () {}
Explanation
addChar
)addChar
takes in three parameters: map
which starts with an empty
object, string str
, and an index i
which starts at 0.i
reaches the length of str
, return map.letter
) which stores the str
at index i
.map
is equal to the value of map
or 0.map
is equal to the value of map
plus 1.reduce
method to this
.addChar
function.Code
Array.prototype.getCharCount = function () {const addChar = (map = {}, str, i = 0) => {if (i === str.length) {return map}const letter = str[i]map[letter] = map[letter] || 0map[letter] = map[letter] + 1return addChar(map, str, i + 1)}return this.reduce((acc, e) => {return addChar(acc, e)}, {})}
You might have seen an array and noticed an opportunity to use reduce
. Great!
Another simple way to solve this problem would have been to use two levels of
recursion, like in zeroSquare—for example an i
counter for the strings and a
j
counter for the letters. You also probably noticed that whether you used
recursion or reduce
, you would need an accumulator, starting as an empty
object {}
, to store the results.
getMostCommon
prototype function for arrays that returns the most
common element.;[9, 8, 7, 8, 7, 7, 7].getMostCommon()// returns 7 because it is the most common element;['Batman', 8, 7, 'Batman', 'Robin'].getMostCommon()// returns "Batman" because it is the most common element
Start by counting the frequency of each unique element in the array (you can
make a new array to hold these frequencies). Then use reduce
on the
frequencies to find out which is the highest. Because object keys are always
strings, you won't be able to use the elements themselves as keys; doing so
would give "7"
as the most common element of the first example above instead
of 7
. But you can make a little object to store an element along with its
frequency, then store these little objects in an array.
Tests
describe('getMostCommon function', () => {it('should return a number as the most common', () => {const result = [9, 8, 7, 8, 7, 7, 7].getMostCommon()expect(result).toEqual(7)})it('should return a string as the most common', () => {const arr = ['Batman', 8, 7, 'Batman', 'Robin']const result = arr.getMostCommon()expect(result).toEqual('Batman')})it('should return first element if all equally common', () => {const types = ['grass', 'poison', 'fire', 'flying', 'water', 'bug']const result = types.getMostCommon()expect(result).toEqual('grass')})it('should return null on an empty array', () => {const result = [].getMostCommon()expect(result).toEqual(null)})})
Shape
Array.prototype.getMostCommon = function () {}
Explanation
Steps for Solution 1:
mapOfElements
) which stores the
reduce
method attached to this
.reduce
method, build the object with the given array.mostCommon
) which stores:Object.entries
method to mapOfElements
.reduce
method to the the result of the step above.reduce
method.element
) which equals first
element of the array.count
) which equals the
second element of the array.count
is less than the accumulator at index 1, return the element.[undefined, 0]
.mostCommon
at index 0.Steps for Solution 2:
arrMapCommon
) which stores the
result of attaching reduce
method to the given array.reduce
method,map
) and it is equal to the
first element of the starting array.count
) and it is equal to the
third element of the starting array.map
at given element is greater than count
,map
at that
key element.reduce
method.arrMapCommon
at second element.Code
Array.prototype.getMostCommon = function () {const mapOfElements = this.reduce((acc, e) => {acc[e] = acc[e] || 0acc[e] = acc[e] + 1return acc}, {})const mostCommon = Object.entries(mapOfElements).reduce((acc, e) => {const element = e[0]const count = e[1]if (count > acc[1]) {if (parseInt(element)) {return [parseInt(element), count]}return e}return acc},[null, 0])return mostCommon[0]}// SOLUTION 2 -> Customize starting value of reduceArray.prototype.getMostCommon = function () {const arrMapCommon = this.reduce((acc, e) => {const map = acc[0]map[e] = map[e] || 0map[e] = map[e] + 1const count = acc[2]if (map[e] > count) {acc[2] = map[e]acc[1] = e}return acc},[{}, null, 0])return arrMapCommon[1]}
This function has two parts: First we have to count the frequency of each item
in the array. Then we have to find the item with the highest frequency. To count
the frequency, we can use a very similar reduce
call to the one in problem 4.
Then we have to go back through the returned object and find which key occurs
the most. For that, we can use the function that we wrote for Object Helpers,
exercise 1 (longestString) with some silght modifications.
You’ll notice that an alternative solution is to let the one reduce call do all
the work for us. In the first solution, we sort of tricked reduce
into passing
along 2 accumulator arguments, and in this one we're making it pass along all 3
things: all the frequencies as well the current most common element and its
frequency. Even though reduce technically only takes one accumulator, we can get
away with this by wrapping the 3 pieces of information we need in an array!
removeDupes
prototype function for arrays that removes duplicate
elements from the array.const a = [9, 8, 7, 8, 7, 7, 7]a.removeDupes() // a becomes [9]
Tests
describe('removeDupes function', () => {it('should remove 2 sets of duplicate numbers', () => {const data = [9, 8, 7, 8, 7, 7, 7]data.removeDupes()expect(data).toEqual([9])})it('should remove 1 set of duplicate strings', () => {const data = ['ice', 'electric', 'psychic', 'ice', 'ground', 'ice']data.removeDupes()expect(data).toEqual(['electric', 'psychic', 'ground'])})it('should remove duplicate boolean values', () => {const data = ['grass', false, 'poison', 'electric', false]data.removeDupes()expect(data).toEqual(['grass', 'poison', 'electric'])})it("shouldn't remove anything from an array with no dups", () => {const data = ['Pewter', 'Saffron', 'Vermilion', 'Veridian']data.removeDupes()expect(data).toEqual(['Pewter', 'Saffron', 'Vermilion', 'Veridian'])})it('should leave an empty array unchanged', () => {const data = []data.removeDupes()expect(data).toEqual([])})})
Shape
//Main FunctionArray.prototype.removeDupes = function () {}
Explanation
i
) to keep track of index
and it starts at 0.i
is at the the length of given array, we are done so returni
, and run the function again
without increasing i
Code
//Main FunctionArray.prototype.removeDupes = function () {const map = this.reduce((acc, e) => {acc[e] = (acc[e] || 0) + 1return acc}, {})const remove = (i = 0) => {if (i === this.length) {return}if (map[this[i]] === 1) {return remove(i + 1)}this.splice(i, 1)return remove(i)}remove()}
Examples: We've seen a couple examples of how this function should work. Note that it removes not just extra elements, but all copies of an element that occurs more than once.
Function shape: This is a prototype function, so the array we’re working on
will be this
. Since it modifies the array in place, it has no return value and
takes no arguments.
Array.prototype.removeDupes = function () {}
Think: We'll need some code that counts how many times each element occurs
in an array, then some code to remove all occurences of a given value from an
array. We haven't done this exactly, but you might remember removeEvens
from
JS 2; we can use code similar to this. That function was recursive, but we
probably shouldn't make this entire function recursive—we don't need to count
the occurences multiple times!
So let's write a helper function inside Array.prototype.removeDupes
to
recursively remove the duplicate values once we've counted them. We can call it
removeDupes
without a name collision because it won't be attached to the array
prototype. This will return nothing since it also modifies the array in place,
and it will need only one parameter, an iterator or counter.
const removeDupes = (i = 0) => {}
Notice how we made this a Fat Arrow function—that way we can still use this
to
access the array we're working on! (Our little helper function won't get its own
this
that would override it.)
Code: The rest is quick because we've already written everything we need in
other exercises—now we just have to put it together. There are several other
ways we could have done this, such as using reduce
to make an array of only
values that occur more than once, then using forEach
to remove each one from
the original array.
Test: Testing your code should cover as many types of input as possible. Let's start with the examples and see if we can think of some more:
[9,8,7,8,7,7,7] // items occur 1, 2, and 3 times["Batman", 8,7, "Batman", "Robin"] // one item occurs twice["Spearow", "Fearow", "Ekans", "Arbok"] // no duplicates["I am groot", "I am groot", "I am groot"] // all the same; should become [][] // it's always good to test empty arrays
In reality, an array is actually an object, with special status (you can use numbers as keys, etc). You can add keys and values to arrays! The following examples are for teaching purposes only—treating arrays as objects is not a good idea, as it will confuse other engineers on your team.
const a = [9, 8, 7, 5]a.name = 'Tony Stark' // You assign a key and value to an array like an objectconsole.log(a) // will print out [ 9, 8, 7, 5, name: 'Tony Stark' ]
Here's a countForEach
function that keeps a counter of how many times it's
called:
// This creates a new array function, countForEach// This function calls the normal forEach function and returns// how many times the function has been called on the arrayArray.prototype.countForEach = function (cb) {// The first time this function is called, this.forEachCount will be undefined// When that happens, we use 0this.forEachCount = (this.forEachCount || 0) + 1// Calls the regular forEach function in the arraythis.forEach(cb)return this.forEachCount}const villains = ['Joker', 'Catwoman', 'Penguin', 'Riddler']villains.countForEach(() => {}) // returns 1villains.countForEach(() => {}) // returns 2villains.countForEach(() => {}) // returns 3const moreVillains = ['Two-Face', 'Bane']moreVillains.countForEach(() => {}) + villains.countForEach(() => {})// Returns 1 + 4, which is 5
Notice how we gave the array a property, forEachCount
, as if were an object.
Write a getNext
prototype function for arrays that returns the next element
of the array.
const a = ["Edna", "Optimus", "Minion"]a.getNext() // returns "Edna"a.getNext() // returns "Optimus"a.getNext() // returns "Minion"a.getNext() // returns "Edna"a.getNext() // returns "Optimus"a.getNext() // returns "Minion"a.getNext() // returns "Edna"...
Tests
describe('getNext function', () => {it('should iterate through 3 elements', () => {const arr = ['Edna', 'Optimus', 'Minion']let result = arr.getNext()expect(result).toEqual('Edna')expect(arr.getNext()).toEqual('Optimus')expect(arr.getNext()).toEqual('Minion')})it('should return to beginning once done', () => {const arr = [9, 80, 12, 2]expect(arr.getNext()).toEqual(9)expect(arr.getNext()).toEqual(80)expect(arr.getNext()).toEqual(12)expect(arr.getNext()).toEqual(2)expect(arr.getNext()).toEqual(9)expect(arr.getNext()).toEqual(80)})it('should return undefined for an empty array', () => {const arr = []expect(arr.getNext()).toEqual(undefined)})it('should iterate through one element', () => {const arr = ['Ironman']expect(arr.getNext()).toEqual('Ironman')expect(arr.getNext()).toEqual('Ironman')})it(`shouldn't iterate`, () => {const arr = []expect(arr.getNext()).toEqual()expect(arr.getNext()).toEqual()expect(arr.getNext()).toEqual()expect(arr.getNext()).toEqual()})})
Shape
Array.prototype.getNext = function () {}
Explanation
this.indexCounter
or 0.this.indexCounter
to the currentIndex + 1 and modulo that result
by this.lengththis
array.Code
Array.prototype.getNext = function () {const index = this.indexCounter || 0this.indexCounter = (index + 1) % this.lengthreturn this[index]}
Look familiar? This is very similar to Array.prototype.getIterator
from JS 2.
How is it different? Here, we’re not returning a function, so we need our
iterator/counter variable to be defined outside of this function.
Array.prototype.getNext(i=0)
is no good because every time we run it, i
will
go back to 0 and we'll just have a lot of "Edna". But now that we know how to
put named keys in arrays, we can define a counter outside the function just by
setting this.indexCounter
.
Because it's part of the Array prototype, it's not part of the array itself,
and won't show up if you console.log the array! But just like every array can
access prototype functions like map
and pop
, your array will now be able to
access its indexCounter
.
Write an Array setMaxSize
prototype function that gives arrays a max length
beyond which new elements can no longer be pushed.
const a = ['Edna', 'Optimus', 'Minion']a.setMaxSize(4)a.push('Groot') // push returns 4.// Array is ["Edna", "Optimus", "Minion", "Groot"]a.push('hello') // Nothing happens. push returns 4, array stays the same.
Tests
describe('setMaxSize prototype', () => {it('maxSize should stay four', () => {const arr = ['Michelangelo', 'Leonardo', 'Raphael']arr.setMaxSize(4)arr.push('Donatello')arr.setMaxSize(3)arr.push('Shredder')arr.setMaxSize(1)arr.push('Splinter')expect(arr.length).toEqual(4)})it('maxSize should increase', () => {const arr = ['Michelangelo']arr.setMaxSize(2)arr.push('Donatello')expect(arr.length).toEqual(2)})it('maxSize keeps array empty', () => {const arr = []arr.setMaxSize(0)arr.push('M', 'L', 'R')expect(arr.length).toEqual(0)})})
Shape
Array.prototype.setMaxSize = function (max) {}
Explanation
Array.prototype.setMaxSize
that passes in a max variable.this.push
in a variable called
this.oldPush
.this.push
to an arrow function that
passes in a new element.this.length
return this.oldPush
envoked with your new element parameter.this.length
Code
Array.prototype.setMaxSize = function (size) {this.oldPush = this.pushthis.push = newElement => {if (this.length < size) {return this.oldPush(newElement)}return this.length}}
Why doesn't Array.prototype.push
work?:
Array.prototype.setMaxSize = function(max) {this.max = maxthis.oldPush = this.push}Array.prototype.push = function(newElement) {if (this.length < this.max) {return this.oldPush(newElement)}return this.length}}
The problem is that you're setting Array.prototype.push
to some function that
you defined.
Then you're calling Array.prototype.setMaxSize
, which sets this.oldPush
to
your own defined Array.prototype.push
(not the original
Array.prototype.push
).
So this.oldPush
and Array.prototype.push
are both the same function which is
the function you defined.
So your defined Array.prototype.push function will call this.oldPush
which is
also your defined Array.prototype.push
function, so it just keeps calling
itself forever.
This is an example of why it is considered bad practice to overwrite built-in Javascript prototypes and methods.
Write a tiredForEach
prototype function that runs a function on each
element of an array, but makes the user wait a specified amount of time
before calling it again.
const a = ['chinese', 'african', 'korean']const callback = (e, i) => {console.log(e + i)}a.tiredForEach(callback, 180)/*prints out:chinese0african1korean2*/a.tiredForEach(callback, 180)/*prints out:"Too tired. Please wait 180ms.*/setTimeout(() => {// run tiredForEach after 190msa.tiredForEach(callback, 180)}, 190)/*... 190ms later....prints out:chinese0african1korean2*/
Tests
describe('tiredForEach function', () => {jest.useFakeTimers()it('should call callback immediately when not tired', () => {const callback = jest.fn()const arr = ['Edna', 'Optimus', 'Minion']arr.tiredForEach(callback, 200)expect(callback).toHaveBeenCalled()})it('should not run function before time has passed', () => {const callback = jest.fn()const callback2 = jest.fn()const arr = ['Edna', 'Optimus', 'Minion']arr.tiredForEach(callback, 200)arr.tiredForEach(callback2, 200)expect(callback2).not.toHaveBeenCalled()})it('should work again once time has passed', () => {const callback = jest.fn()const arr = ['Edna', 'Optimus', 'Minion']arr.tiredForEach(callback, 200)jest.advanceTimersByTime(200)arr.tiredForEach(callback, 200)expect(callback).toHaveBeenCalledTimes(6)})})
Shape
Array.prototype.tiredForEach = function (cb, time) {return // ...}
Explanation
this.isTired
is true,this.isTired
to true and this.waiTime
to time.this.isTired
to false.this.forEach(cb)
.Code
Array.prototype.tiredForEach = function (cb, time) {if (this.isTired)return console.log(`Too tired. Please wait ${this.waitTime}ms.`)this.isTired = truethis.waitTime = time/*NOTE: The argument function to setTimeout is writtenwith () => {....}.If you write it usingsetTimeout(function {this.isTired....then the code will not work.Explanation provided in the next section.*/setTimeout(() => {this.isTired = false}, time)return this.forEach(cb)}
We haven't covered telling what time it is per se, but we knew we would be
storing some information as a property of the array, and we remembered how to
make a function run after a certain amount of time using setTimeout
. So we
made a flag to be turned on when tiredForEach
is called and turned off after
the requested wait time.
Remember that when using an if
check, if a variable is undefined it’ll just
evaluate as falsey so it’s OK to say if (this.tired)
even if this.tired
has
not been defined yet.
Complete the first eight JS3 challenges