Closed QuincyLarson closed 8 years ago
I'm willing to take this on as the project owner.
@utsab awesome! Yes - we need to expand this. First things first, could you create a list of challenge titles? OOP is probably complicated enough that we could have 20 or 30 challenges associated with it.
@utsab I've promoted you to topic owner.
@QuincyLarson yes, I'll compile a list of challenge titles. I'll try to have this done by Wednesday.
Here's a 1st draft of the topics for OOJS. (The pre-existing challenges are marked with hash tag #old):
Constructors
More on "this"
Prototypes
Prototypes with Constructors
Inheritance
Encapsulation (Data hiding)
Other
Looking forward to hearing your feedback @QuincyLarson and anyone else who would like to discuss.
@utsab I am interested! It's midday in India now, and maybe we can have a call tonight? I will also update the list above which started this thread, with your list.
You can reach me via gitter
@alayek, great! Glad to have your help on this. I messaged you on gitter about setting up a time to chat.
@utsab Thanks for your patience with my slow response.
This is an excellent list of OOP concepts! I am so excited about this. We will finally give OOP the level of detail it deserves.
Let me know if I can be of any help with designing these challenges, their descriptions, and tests. I will look forward to seeing them here as you have time to add them :)
@utsab Thanks for the call. I will be working on Prototypes and Prototypes with Constructors as discussed.
@utsab great talking with you! I will take on Mixins and Encapsulation (Data hiding). Will post final solutions after the team review :)
@utsab Great talk, thanks for onboarding me. As discussed, I will be covering Inheritance.
@utsab Thanks for chatting, glad to be contributing! I am going to cover Constructors.
@utsab Great to be helping! We made a bit of a change to the curriculum and created an Objects section, which I'll be working on.
Hey Quincy! I'll be posting my challenges now. We went ahead and made a basic Objects section that we'll need to update the original curriculum list to include.
Challenge Name: Introduction to Objects
Challenge
Create a basic JavaScript object.
Challenge Description
Think about things we encounter in our everyday life, like cars, shops, and birds. These are all objects: tangible things we can observe and interact with.
What are some qualities of these objects
? A car has wheels. Shops sell items. Birds have wings.
These qualities, or properties
, define what makes up an object
. It's important to note that cars might all have wheels, but not all cars will have the same number of wheels.
We can use objects
in JavaScript to model real-world objects, endowing them with properties and behavior just like their real-world counterparts. We’ll use the previous concepts to create a “duck” object
:
var duck = {
name: "Aflac",
numLegs: 2
}
We created a “duck” object with two property/value pairs: a name
of Aflac and a numLegs
of 2.
Instructions
Create a “dog” object
with name
and numLegs properties,
and set them to a string and a number, respectively.
Challenge Seed
var dog = {
//Your code here
}
Challenge Tests
Test to verify that the dog object is defined.
assert(typeof(dog) === “object”), ‘message: Dog
should be an object.’;
Test to verify that the dog object has name and numLegs properties.
assert(typeof(dog.name) === ‘string’), ‘message: dog.name
should be a string;
assert(typeof(dog.numLegs) === ‘number’), ‘message: dog.numLegs
should be a number;
Challenge Solution
var dog = {
name: //string,
numLegs: //number
}
Challenge Name: Accessing Properties on Objects
Challenge
Use dot notation to access object’s properties.
Challenge Description
Great job on defining that “dog” object
. Now that we have created an object
, let's talk about accessing some of these properties!
It's very simple to access the values of a property on an object
. See the following example:
var duck = {
name: "Aflac",
numLegs: 2
}
console.log(duck.name)
//This will print "Aflac" to the console.
By using our object
name, duck
, along with the property whose value we want to utilize, color
, we access the property
of your object
.
Instructions
Print the properties of the Dog object below to your console.
Challenge Seed
var dog = {
name: “Spot”,
numLegs: 4
}
//Add your two console calls here.
Challenge Tests
Test to see that the Dog’s name prints to the console.
assert(console.log(dog.name) === “Spot”), ‘message: console.log(dog.name)
should print out “Spot”
Test to see that the Dog’s numLegs prints to the console.
assert(console.log(dog.numLegs) === 4), ‘message: console.log(dog.numLegs)
should print out the number 4.’
Challenge Solution
var dog = {
name: “Spot”,
numLegs: 4
}
console.log(dog.name);
console.log(dog.numLegs);
Challenge Name: JavaScript Object Methods
Challenge
Create a method on an object.
Challenge description
Now that we know how to create an object
and access its properties
, let's explore a special property of objects
. Namely, we will talk about adding a method
to our existing objects
!
Methods
are properties
that are functions
. We can do all sorts of things with these methods
. Let's look at the previous duck
example, and add a method
:
var duck = {
name: "Aflac",
numLegs: 2
sayName: function() {return "The name of this duck is " + duck.name + "."}
}
duck.sayName();
//This will return "The name of this duck is Aflac."
As you can see above, we added the sayName method
, which is a function
that we can call to return a sentence telling us the color of the duck. This is a straightforward method of adding a method
to an object
in JavaScript.
Notice that we access the name
of the duck in the method
. We'll get back to that in the next lesson, but for now, this is the way we will insert that value into the string.
Instructions
In the following exercise, draw upon the previous “dog” object
you created, and give it a method sayLegs
. This method
should return a sentence that says "This dog has 4 legs."
Challenge Seed
var dog = {
name: “Spot”,
numLegs: 4
//Add your method here.
}
dog.sayLegs();
Challenge Tests
Test to see if the Dog object
has a sayLegs method
that returns the desired string
.
assert(typeof(dog.sayLegs() === ‘function’), ‘message: dog.sayLegs()
should be a function.’
assert(dog.sayLegs() === “This dog has 4 legs.”), ‘message: dog.sayLegs()
should return the desired string.’
Challenge Solution
var dog = {
name: “Spot”,
numLegs: 4
sayLegs: function() {return "This dog has " + dog.legs + " legs."}
}
dog.sayLegs();
Challenge Name: Introducing "This"
Challenge
Make code more reusable with “this”.
Challenge Description
We are going to look at our sayName method
from the previous example
sayName: function() {return "The name of this duck is " + duck.name + "."}
Notice how we are referencing the variable name of our object, duck
inside the sayName
method in order to access the name
property. While this is a totally valid way to access our object's property
, there is a bit of a pitfall here.
What if the variable name of our object
changes? What if, instead of a duck, it's a mallard now? Well, we would have to change duck.name
to mallard.name
. In this small example, that's fine, but what happens when we have an object
that makes many references to its properties
throughout its methods
? Now the process gets much more complex!
There's a way to avoid that all. Introducing...this
:
var duck = {
name: "Aflac",
numLegs: 2,
sayName: function() {return "The name of this duck is " + this.name + "."}
}
This
is a very robust topic that would be a great idea to research more deeply. For now, understand that this
, in the current context, refers to the object
that our method
is associated with: duck
.
Now, if we changed our object's name to mallard, we don't have to find all our references to duck
in our code. We've made our code more reusable and easy to read.
Instructions
Modify the dog.sayLegs
method to remove any references to dog
. It should follows the same code style as the duck
object.
Challenge Seed
var duck = {
name: "Aflac",
numLegs: 2,
sayName: function() {return "The name of this duck is " + this.name + "."}
};
// Modify code below this line.
var dog = {
name: “Spot”,
numLegs: 4,
sayLegs: function() {return "This dog has " + dog.numLegs + " legs."}
};
dog.sayLegs();
Challenge Tests
Test to see if dog.sayLegs()
still returns the desired string.
assert(dog.sayLegs() === “This dog has 4 legs.”), ‘message: dog.sayLegs()
should return the desired string.’
Challenge Solution
var dog = {
name: “Spot”,
numLegs: 4,
sayLegs: function() {return "This dog has " + this.numLegs + " legs."}
}
dog.sayLegs();
Use a Mixin
to add common behavior between unrelated objects
As you have previously seen you can share behaviour through inheritance
, but there are cases when inheritance is not the best solution. Inheritance does not work well for unrelated objects like Bird
and Airplane
. They can both fly, but a Bird
is not a type of Airplane
and vice versa.
For unrelated objects, it's better to use mixins
. A mixin
allows a collection of functions to be used by other objects.
var flyMixin = function(obj) {
obj.fly = function() {
console.log("Flying, wooosh!")
}
}
The flyMixin
takes any object and gives it the fly
method.
var bird = {
name: “Donald”,
numLegs: 2,
}
var plane = {
model: “777”,
numPassengers: 524
}
flyMixin(bird);
flyMixin(plane);
Here bird
and plane
are passed into flyMixin
, which then assigns the fly
function to each object. Now bird
and plane
can both fly
.
bird.fly(); //results in output on the screen: Flying, wooosh!
plane.fly(); //results in output on the screen: Flying, wooosh!
Note how the mixin
allows for the same fly
method to be reused by completely unrelated objects bird
and plane
.
Create a mixin
named glideMixin’ that defines a method named
glide. Use the
glideMixinto give both
birdand
boatthe ability to
glide`.
var bird = {
name: ‘Donald’,
numLegs: 2,
}
var boat = {
name: 'Warrior',
type: 'race-boat',
}
// Only add code below this line.
assert(/var glideMixin = function(.+)/.test(code), ‘message: Please use the `var glideMixin` and set it equal to an anonymous function’
assert(typeof glideMixin === "function", ‘message: `glideMixin` should follow the proper structure of a function)
assert(/glideMixin\(.+\)/.test(code), ‘message: Don’t forget to pass the object to your mixin, to give it access to `.glide` method’
var glideMixin = function(obj) {
obj.glide = function() {
console.log("Gliding on the water")
}
}
glideMixin(bird);
glideMixin(boat);
Use closure to protect properties
within an object
from being modified externally.
In the previous challenge, bird
had a public
property name
. We say it is public
because it can be changed outside of the context of bird
.
bird.name = "Duffy"
Any part of your code can therefore easily change the name of bird
to any value. Think about things like passwords and bank accounts being easily changeable by any part of your codebase. That could be catastrophic.
The simplest way to make properties private
is by creating a var
within the constructor function
. In this way the private properties
can only be accessed and changed by privileged methods
within the constructor
.
function Bird() {
var hatchedEgg = 10; // private property
this.getHatchedEggCount = function() { //publicly available method that a bird object can use
return hatchedEgg();
}
}
ducky = new Bird();
ducky.getHatchedEggCount() //returns 10
Here getHachedEggCount
is a privileged method
, because it has access to the private
variable hatchedEgg
. This is possible because hatchedEgg
is declared in the same context as getHatchedEggCount
. In javascript a function always has access to the context in which it was created. This is called closure
.
Change the function
in the text editor to make the weight parameter
private.
// rewrite the function below
function Bird() {
this.weight = 15;
}
assert(/var weight/.test(code), 'message: Please use the `var` before the weight to make it private'
assert(/function handleWeight\(\) {/.test(code), 'message: Please make sure to create a named function within your Bird object'
assert(/return weight;/.test(code), 'message: Please remember to have a return statement for your private weight variable'
assert(/this.getWeight = function\(\) {/.test(code), 'message: Make sure to have a public method that an instance of your `Bird` can call on'
function Bird() {
var weight = 15;
function handleWeight() {
return weight;
}
this.getWeight = function() {
return handleWeight();
}
}
https://curiosity-driven.org/private-properties-in-javascript http://javascript.crockford.com/private.html
Understand the immediately invoked function Expression (IIFE)
A common pattern in javascript is to execute a function as soon as it is declared:
(function () {
console.log("Chirp, chirp!")
})(); // this is the anonymous function expression that executes right away
// Outputs "Chirp, chirp" immediately
Note that the function has no name and is not stored in a variable. The two parentheses ()
at the end of the function expression cause it to be immediately executed or invoked
. This pattern is known as an immediately invoked function expression
or IIFE
.
Rewrite the function in the text editor to be an immediately invoked function expression (IIFE).
function makeNest() {
console.log("A cozy nest is ready");
}
makeNest();
assert(/\(function\(\) {/.test(code), 'message: Make sure your new function is anonymous'
assert(/}\)\(\)/.test(code), 'message: Don’t forget to have double parentheses at the end of your function expression'
(function() {
console.log("A cozy nest is ready")
})();
Use an IIFE to create a module
An immediately invoked function expression (IIFE) is often used to group related functionality into a single object or module
. For example, in a previous section, we defined two mixins
:
function glideMixin(obj) {
obj.glide = function() {
console.log("Gliding on the water")
}
}
function flyMixin(obj) {
obj.fly = function() {
console.log("Flying, wooosh!")
}
}
We can group these mixins into a module
as follows:
var motionModule = (function () {
return {
glideMixin: function (obj) {
obj.glide = function() {
console.log("Gliding on the water")
}
},
flyMixin: function(obj) {
obj.fly = function() {
console.log("Flying, wooosh!")
}
}
}
}) (); // The two parentheses cause the function to be immediately invoked
Note that we have an immediately invoked function expression (IIFE) whose sole purpose is to return an object. motionModule
is this returned object. This returned object contains all of the mixin
behaviors as properties
on the object.
The advantage of the module pattern
is that all of our motion behaviors can be packaged into a single object which can then be used by other parts of your code. Here we show a sample invocation:
motionModule.glideMixin(duck);
duck.glide();
Create a module
named funModule
to wrap the two mixins isCuteMixin
and singMixin
.
var isCuteMxin = function(obj) {
obj.isCute = function() {
return true
}
}
var singMixin = function(obj) {
obj.sing = function() {
console.log("Singing to an awesome tune");
}
}
assert(typeof funModule !== undefined, "message: <code>funModule</code> should be defined");
assert(funModule.isCuteMixin !== undefined, "message: <code>funModule.isCuteMixin</code> should be defined");
assert(funModule.singMixin !== undefined, "message: <code>funModule.singMixin</code> should be defined");
var funModule = (function () {
return {
isCuteMixin: function (obj) {
obj.isCute = function() {
return true
}
},
singMixin: function(obj) {
obj.sing = function() {
console.log("Singing to an awesome tune")
}
}
}
}) ();
@venture-vin @silvestrijonathan great work!
Could you please point which of the items in the roadmap these challenges point to? I will update the roadmap
@alayek - no problem did you want that in the beginning of each comment, and did you want general topics or the more specific topics? (Mine are Mixin & Data Encapsulation)
If you are adding challenges that are not mentioned in the roadmap, that's still fine. Just copy the roadmap and check off the item you are taking. Or copy it, add the item where it would fit in, and check it off.
You wanted some help with how to introduce Closures without going into concepts of garbage collector etc. We can discuss if you are available.
@alayek, I'll go back and add those in after we discuss how we'll be adding them to the roadmap. Thanks!
Updated: 08/24/16
Challenge
Challenge: Set the Child's Prototype to an Instance of the Parent
Challenge Description
In the previous challenge we showed step 1 for inheriting behavior from the supertype
(or parent
) Animal
: making a new instance of Animal
.
In this challenge, we cover the next step: set the prototype of the subtype
(or child
) -- in this case, Bird
-- to be an instance
of Animal
.
Bird.prototype = Object.create(Animal.prototype);
What does that do for us? Remember that the prototype is like the "recipe" for creating an object. In a way, we are saying that the recipe for Bird
includes an instance of Animal
as a key ingredient.
var duck = new Bird("Donald");
duck.eat(); // prints "nom nom nom"
duck
inherits
all of Animal
's properties including the eat
method.
Instructions
Modify the code so that instances of Dog
inherit from Animal
.
Challenge Seed
function Animal() {
};
Animal.prototype = {
constructor: Animal,
eat: function() {
console.log("nom nom nom");
}
}
function Dog() {
};
//
// Add code here
//
var beagle = new Dog();
beagle.eat(); //Should print "nom nom nom"
Challenge Tests
assert(Animal.prototype.isPrototypeOf(Dog.prototype), 'message: Dog.prototype
should be an instance of Animal
);
Challenge Solution
function Animal() {};
function Dog() {};
Animal.prototype.eat = function () { console.log("nom nom nom"); };
Bird.prototype = Object.create(Animal.prototype);
var beagle = new Dog();
beagle.eat(); // prints "nom nom nom"
Updated: 08/22/16
Challenge Inherit Behaviors From A Supertype
Challenge Description
In the previous challenge, we created a supertype
called Animal
that defined behaviors shared by all animals:
function Animal() { };
Animal.prototype.eat = function() {
console.log("nom nom nom");
}
In this and the next challenges, we will learn how to reuse Animal
's methods inside Bird
and Dog
without defining them again by using a technique called inheritance
.
This challenge will cover the first step: make an instance
of the supertype
(or parent
).
We already know one way to create an instance of Animal
using the new
operator:
var animal = new Animal();
There are some disadvantages when using this syntax for inheritance, which are too complex for the scope of this challenge. Instead, we show a better approach:
var animal = Object.create(Animal.prototype);
Object.create(obj)
creates a new object, and sets obj
as the new object's prototype. Recall that the prototype is like the "recipe" for creating an object. By setting the prototype of animal
to be Animal
's prototype, we are effectively giving animal
the same "recipe" as any other instance of Animal
.
animal.eat(); // prints "nom nom nom"
animal instanceof Animal; //=> true
Instructions
Use Object.create
to make two instances of Animal
named duck
and beagle
Challenge Seed
function Animal(){
}
Animal.prototype = {
constructor: Animal,
eat: function() {
console.log("nom nom nom");
}
}
// Modify the code below this line
var duck =
var beagle =
duck.eat(); // Should print "nom nom nom"
beagle.eat(); //Should print "nom nom nom"
Challenge Tests
assert(typeof duck !== "undefined", 'message: Did you remember to define the variable duck
?);
assert(typeof beagle !== "undefined", 'message: Did you remember to define the variable beagle
?);
assert(duck instanceof Animal, 'message: Did you remember to assign the Animal prototype to duck?') assert(beagle instanceof Animal, 'message: Did you remember to assign the Animal prototype to beagle?')
Challenge Solution
function Animal(){
}
Animal.prototype = {
constructor: Animal,
eat: function() {
console.log("nom nom nom");
}
}
// Modify the code below this line
var duck = Object.create(Animal.prototype);
var beagle = Object.create(Animal.prototype);
duck.eat(); // Should print "nom nom nom"
beagle.eat(); //Should print "nom nom nom"
Updated: 08/22/16
Challenge Add Own Methods To Inherited Prototype
Challenge Description A constructor function that inherits its prototype object from a super-type constructor function can still have its own methods in addition to inherited methods.
Let's use Bird
for example, a constructor that inherits its prototype from Animal
.
function Animal() {}
Animal.prototype.eat = function () { console.log("Nom nom nom"); };
function Bird() {}
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;
In addition to what is inherited from Animal, we want to add behavior that is unique to birds. Let’s give Bird
a fly()
function. we add a function to Bird
's protoype the same way we do with any constructor function: Bird.prototype.fly = function () { console.log("I'm flying!"); };
Now instances of Bird
will have both eat()
and fly()
methods.
var duck = new Bird;
duck.eat(); // prints "Nom nom nom"
duck.fly(); // prints "I'm flying!"
Instructions
Add the necessary code to make beagle respond to eat()
and bark()
.
Hint
Objects inherit methods from other objects by cloning their prototype. The Object.create
method will come in handy, and don't forget to reset the constructor
property afterward!
Challenge Seed
function Animal(){};
Animal.prototype.eat = function () { console.log("Nom nom nom"); };
function Dog() {};
// Add code below this line
// Add code above this line
var beagle = new Dog;
beagle.eat(); // prints "Nom nom nom"
beagle.bark(); // prints "Woof!"
**Challenge Tests**
assert(typeof Animal.prototype.bark == "undefined", 'message: Only instances of Dog should respond to the .bark() method');
assert(!(typeof Dog.prototype.eat == "undefined"), 'message: Dog should inherit the .eat() method from Animal');
assert(beagle instanceof Animal, 'message: Dog should inherit the .eat() method from Animal');
assert(beagle.constructor === Dog, 'message: Did you remember to the constructor for Dog?');
**Challenge Solution**
```javascript
function Animal(){};
Animal.prototype.eat = function () { console.log("Nom nom nom"); };
function Dog() {};
// Add code below this line
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function () {};
// Add code above this line
var beagle = new Dog;
beagle.eat(); // prints "Nom nom nom"
beagle.bark();
Updated: 08/24/16
Challenge Override Inherited Methods
Challenge Description
In previous lessons, we learned that an object can inherit its behavior (methods) from another object by cloning its prototype object, like this ChildObject.prototype = Object.create(ParentObject.prototype)
. We gave ChildObject
its own methods by chaining them onto its prototype, like this ChildObject.prototype.methodName = function () {...}
.
We can override an inherited method the same way by adding a method to ChildObject.prototype
using the same method name as the one we want to override.
Here's an example of Bird overriding the eat()
method inherited from Animal.
function Animal(){}
Animal.prototype.eat = function() { return "Nom nom nom"; };
function Bird(){}
// inherit methods first, followed by method overrides
Bird.prototype = Object.create(Animal.prototype);
// Bird.eat() overrides Animal.eat()
Bird.prototype.eat = function () { return "Peck peck peck"; };
If we have an instance var duck = new Bird
and we call duck.eat()
, here’s how JavaScript looks for the method on duck
’s prototype chain:
duck
=> Is eat()
defined here? No.Bird
=> Is eat()
defined here? => Yes. Execute it and stop searching.Animal
=> eat()
is also defined here, but JavaScript stopped searching before reaching this level.Object
=> JavaScript stopped searching before reaching this level.Instructions
Override the fly()
method for Penguin so that it prints in the console "Alas, this is a flightless bird."
Challenge Seed
function Bird(){}
Bird.prototype.fly = function () { return "I'm flying!"; };
function Penguin(){}
Penguin.prototype = Object.create(Bird.prototype);
Penguin.prototype.constructor = Penguin;
// Only add code below this line
// Only add code above this line
var penguin = new Penguin;
console.log(penguin.fly());
Challenge Tests
assert(penguin.fly() === "Alas, this is a flightless bird.", 'message: penguin.fly()
should return "Alas, this is a flightless bird."');
assert((new Bird).fly() === "I'm flying!", 'message: bird.fly()
should return "I'm flying!"');
assert(penguin.constructor === Penguin, 'message: Did you remember to the constructor for Penguin?');
Challenge Solution
function Bird(){}
Bird.prototype.fly = function () { return "I'm flying!"; };
function Penguin(){}
Penguin.prototype = Object.create(Bird.prototype);
Penguin.prototype.constructor = Penguin;
// Only add code below this line
Penguin.prototype.fly = function () {
return "Alas, this is a flightless bird.";
}
// Only add code above this line
var penguin = new Penguin;
console.log(penguin.fly());
Updated 08/24/16
Challenge Reset An Inherited Constructor Property
Challenge Description When an object inherits its prototype from another object, it also inherits the super-type’s constructor property.
Example:
function Bird(){};
Bird.prototype = Object.create(Animal.prototype);
var duck = new Bird;
duck.constructor // function Animal(){...}
Uh oh! We want duck
and all instances of Bird
to show that they were constructed by Bird
. We have to manually set Bird's constructor
property to the Bird
object, like this: Bird.prototype.constructor = Bird
. Now, duck.constructor
returns function Bird(){...}
.
Instructions
Fix the code so duck.constructor
and beagle.constructor
return their respective constructors.
Challenge Seed
function Animal(){}
function Bird(){}
function Dog(){}
Bird.prototype = Object.create(Bird.prototype);
Dog.prototype = Object.create(Bird.prototype);
//
// Add code here
//
var duck = new Bird;
var beagle = new Dog;
Challenge Tests
assert(Animal.prototype.isPrototypeOf(Bird.prototype), 'message: Bird.prototype
should be an instance of the Animal
');
assert(duck.constructor === Bird, 'message: duck.constructor should return Bird');
assert(Animal.prototype.isPrototypeOf(Dog.prototype), 'message: Dog.prototype
should be an instance of the Animal
');
assert(beagle.constructor === Bird, 'message: beagle.constructor
should return Dog
');
Challenge Solution
function Animal(){}
function Bird(){}
function Dog(){}
Bird.prototype = Object.create(Animal.prototype);
Dog.prototype = Object.create(Animal.prototype);
Bird.prototype.constructor = Bird;
Dog.prototype.constructor = Dog;
var duck = new Bird;
var beagle = new Dog;
Challenge
Understand "own" properties
Challenge Description
In the following example, the Bird constructor defines two properties: name
and numLegs
function Bird(name) {
this.name = name;
this.numLegs = 2;
}
var duck = new Bird(“Donald”);
var canary = new Bird(“Tweety”);
name
and numLegs
are called own
properties, because they are defined directly on the instance object. That means that duck
and canary
each has its own separate copy of these properties.
In fact every instance of Bird will have its own copy of these properties
The following code adds all of the own
properties of duck
to the array ownProps
:
ownProps = []
for (var property in duck) {
if(duck.hasOwnProperty(property)) {
ownProps.push(property);
}
}
console.log(ownProps); // prints [ ‘name’, 'numLegs' ]
Instructions
Add the own properties of canary
to the array ownProps
Challenge Seed
function Bird(name) {
this.name = name;
this.numLegs = 2;
}
var canary = new Bird(“Tweety”);
ownProps = []
//Add your code below this line
Challenge Solution
for (var property in canary) {
if (canary.hasOwnProperty(property)) {
ownProps.push(property);
}
}
\ Challenge Tests **
assert(ownProps.includes('name') && ownProps.includes('numLegs'), "message: <code>ownProps</code> should include the values <code>'numLegs'</code> and <code>'name'</code>'");
Challenge
Use prototype properties to reduce duplicate code
Challenge Description
One downside of an own
property is that it can lead to duplicate code. In the previous example, we saw that duck
and canary
each have their own separate copy of name
and numLegs
.
Since numLegs
will probably have the same values for all instances of Bird
, we essentially have a duplicated variable numLegs
inside each Bird
instance.
This may not be a big deal when we only have two instances, but imagine if we had millions of instances, that would be a LOT of duplicated variables.
A better way is to use Bird’s prototype
. The prototype
is an object that is shared among ALL instances of Bird
. We’ll add numLegs
to Bird’s prototype
:
Bird.prototype.numLegs = 2
Now all instances of Bird
have the numLegs
property.
console.log(duck.numLegs); // prints 2
console.log(canary.numLegs); // prints 2
Since all instances are automatically endowed with the properties on the prototype, you can think of a prototype as a "recipe" for creating objects.
Note that the prototype for duck
and canary
lives on the Bird constructor as Bird.prototype
. Nearly every object in javascript has a prototype property which lives on the constructor function that created it.
Instructions
Add a numLegs
property to the prototype
of Dog
Challenge Seed
function Dog(name) {
this.name = name;
}
var beagle = new Dog(“Snoopy”);
//Add your code below this line
Challenge Solution
Dog.prototype.numLegs = 4;
\ Challenge Tests **
assert(beagle.numLegs !== undefined, "message: <code>beagle</code> should have the property <code>'numLegs'</code>");
assert(typeof(beagle.numLegs) === "number" , "message: <code>beagle.numLegs</code> should be a number");
assert(beagle.hasOwnProperty('numLegs') === false, "message: <code>numLegs</code> should be a prototype property not an own property");
Challenge Iterate over all properties
Challenge Description
We have now seen two kinds of properties: own
properties and prototype
properties. own
properties are defined directly on the object instance itself. prototype
properties are defined on the prototype.
function Bird(name) {
this.name = name; //own property
}
Bird.prototype.numLegs = 2 //prototype property
var duck = new Bird(“Donald”);
Here is how we add duck
’s own
properties to the array ownProps
and prototype
properties to the array prototypeProps
:
var ownProps = [];
var prototypeProps = [];
for (var property in duck) {
if(duck.hasOwnProperty(property)) {
ownProps.push(property);
} else {
prototypeProps.push(property);
}
}
console.log(ownProps) // prints [‘name’ ]
console.log(prototypeProps) // prints [ 'numLegs’ ]
Instructions
Add all of the own
properties of beagle
to the array ownProps
. Add all of the prototype
properties of to the array prototypeProps
.
Challenge Seed
function Dog(name) {
this.name = name;
}
Dog.prototype.numLegs = 4
var beagle = new Dog(“Snoopy”);
var ownProps = [];
var prototypeProps = [];
//Add your code below this line
Challenge Solution
for (var property in beagle) {
if(beagle.hasOwnProperty(property)) {
ownProps.push(property);
} else {
prototypeProps.push(property);
}
}
console.log(ownProps); // prints "Snoopy"
console.log(prototypeProps); // prints 4
Challenge Tests
assert(ownProps.includes('name'), "message: <code>ownProps</code> should include <code>'name'</code>");
assert(prototypeProps.includes('numLegs'), "message: <code>prototypeProps</code> should include <code>'numLegs'</code>");
Challenge Use the "constructor" property
Challenge Description
There is a special “constructor” property located on the object instances duck
and beagle
we created in the previous challenges:
var duck = new Bird();
var beagle = new Dog();
console.log(duck.constructor); //prints "Bird"
console.log(beagle.constructor); //prints "Dog"
Note that the constructor
property is equal to the constructor function that created the instance.
The advantage of the constructor
property is that your code can inspect this property to find out what kind of object it is. One example illustrating how this could be used:
function joinBirdFraternity(candidate) {
if (candidate.constructor === Bird) {
return true;
} else {
return false;
}
}
Note: Since the constructor
property can be overwritten (as we’ll see in the next two sections) it’s generally better to use the instanceof
method to check the type of an object.
Instructions
Write a joinDogFraternity function that takes a candidate
parameter and returns true
if the candidate is a Dog
and returns false
otherwise.
Challenge Seed
function Dog(name) {
this.name = name;
}
// Type your code below this line
function joinDogFraternity(candidate) {
}
Challenge Solution
function joinDogFraternity(candidate) {
if (candidate.constructor === Dog) {
return true;
} else {
return false;
}
}
Challenge Tests
assert(typeof(joinDogFraternity) === "function", "message: <code>joinDogFraternity</code> should be defined as a function");
assert(joinDogFraternity(new Dog("")) === true, "message: <code>joinDogFraternity</code> should return true if<code>candidate</code> is an instance of <code>Dog</code>");
Challenge
Change the prototype to a new object
Challenge Description
Up until now we have been adding properties to the prototype individually:
Bird.prototype.numLegs = 2
This becomes tedious after more than a few properties.
Bird.prototype.eat = function() {
console.log(“nom nom nom”);
}
Bird.prototype.describe = function() {
console.log(“My name is ” + this.name);
}
A better way is to set the prototype to a new object that already contains the properties. In this way, the properties can be added all at once:
Bird.prototype = {
numLegs: 2,
eat: function() {
console.log(“nom nom nom”);
},
describe = function() {
console.log(“My name is ” + this.name);
}
};
Instructions
Add three properties numLegs
, eat
and describe
to the prototype of Dog
by setting the prototype to a new object.
Challenge Seed
function Dog(name) {
this.name = name;
}
Dog.prototype = {
/* Your code goes here */
}
Challenge Solution
function Dog(name) {
this.name = name;
}
Dog.prototype = {
numLegs: 4,
eat: function() {
console.log("chomp");
},
describe: function() {
console.log("My name is " + this.name);
}
}
Challenge Tests
assert((/Dog\.prototype[ ]*=[ ]*{/).test(code), "message: Dog.prototype should be set to a new object.");
assert(Dog.prototype.numLegs !== undefined, "message: <code>Dog.prototype</code> should have the property <code>'numLegs'</code>");
assert(Dog.prototype.eat !== undefined, "message: <code>Dog.prototype</code> should have the property <code>'eat'</code>");
assert(Dog.prototype.describe !== undefined, "message: <code>Dog.prototype</code> should have the property <code>'describe'</code>");
Challenge Remember to set the "constructor" property when changing the prototype
Challenge Description
There is one crucial side effect of manually setting the prototype to a new object. We erased the constructor
property! Our code in the previous challenge would print the following for duck
console.log(duck.constructor) // prints ‘undefined’. Oops!
To fix this, whenever we manually set a prototype to a new object, we must remember to define the constructor
property.
Bird.prototype = {
constructor: Bird, // define the constructor property
numLegs: 2,
eat: function() {
console.log(“nom nom nom”);
},
describe = function() {
console.log(“My name is ” + this.name);
}
};
Instructions
Define the "constructor" property on the Dog
prototype.
Challenge Seed
function Dog(name) {
this.name = name;
}
//modify this section
Dog.prototype = {
numLegs: 2,
eat: function() {
console.log(“nom nom nom”);
},
describe = function() {
console.log(“My name is ” + this.name);
}
};
Challenge Solution
Dog.prototype = {
constructor: Dog,
numLegs: 4,
eat: function() {
console.log(“peck peck peck”);
},
describe: function() {
console.log(“My name is ” + this.name);
}
};
Challenge Tests
assert(Dog.prototype.constructor === Dog, "message: <code>Dog.prototype</code> should have the property <code>'constructor'</code>");
Define a Constructor Function
Constructors are functions that create new objects. They define properties and behaviors that will belong to the new object. You can think of them as a blueprint for the creation of new objects.
Here is an example of a constructor:
function Bird () {
this.name = 'Albert';
this.color = 'blue';
this.numLegs = 2;
}
This constructor defines a Bird
object with properties name
, color
, and numLegs
set to Albert, blue and 2, respectively.
Constructors follow a few conventions:
Constructors are defined with a capitalized name to distinguish them from other functions that are not constructors.
Constructors use the keyword this
to set properties of the object they will create. Inside the constructor, this
refers to the new object it will create.
Constructors define properties and behaviors instead of returning a value as other functions might.
Instructions:
Now create your own constructor, Dog
, with properties name
, color
, and numLegs
set to a string, a string, and a number, respectively.
// Your code here
function Dog () {
this.name = 'somestring';
this.color = 'somecolor';
this.numLegs = 4;
}
assert(typeof (new Dog()).name === 'string', 'message: <code>Dog</code> should have a <code>name</code> attribute set to a string');
assert(typeof (new Dog()).color === 'string', 'message: <code>Dog</code> should have a <code>color</code> attribute set to a string');
assert(typeof (new Dog()).numLegs === 'number', 'message: <code>Dog</code> should have a <code>numLegs</code> attribute set to a number');
Use a Constructor to Create Objects
Let’s see our Bird
constructor from the previous challenge in action:
function Bird () {
this.name = 'Albert';
this.color = 'blue';
this.numLegs = 2;
// ‘this’ inside the constructor always refers to the object being created
}
var blueBird = new Bird();
Notice that we use the new
operator when calling our constructor. This tells JavaScript that we want to create a new instance of Bird
. Without the new
operator, this
inside the constructor would not point to the newly created object, giving us unexpected results.
Now blueBird
has all the properties defined inside the Bird
constructor:
blueBird.name; // => Albert
blueBird.color; // => blue
blueBird.numLegs; // => 2
Just like any other object, its properties can be accessed and modified:
blueBird.name = 'Elvira';
blueBird.name; // => Elvira
Instructions:
Use the Dog
constructor from the last lesson to create a new instance of Dog
, assigning it to a variable hound
.
function Dog () {
this.name = 'Rupert';
this.color = 'brown';
this.numLegs = 4;
}
//your code here
var hound = new Dog();
assert(hound instanceof Dog, 'message: <code>hound</code> should be created using the <code>Dog</code> constructor');
Extend Constructors to Receive Arguments
The Bird
and Dog
constructors we created were pretty cool. However, notice that all Birds we create with our Bird
constructor are automatically named Albert, are blue in color, and have two legs. What if we want birds with different values for name
and color
? We could change the properties of each bird manually but that would be a lot of work:
var swan = new Bird();
swan.name = 'Carlos';
swan.color = 'white';
Suppose we were writing a program to keep track of hundreds or even thousands of different birds in an aviary. It would be time consuming to create all the birds and go through each of them, setting their properties to different values.
To more easily enable the construction of different Bird
objects, we can design our Bird
constructor to accept parameters:
function Bird(name, color) {
this.name = name;
this.color = color;
this.numLegs = 2;
}
We can then pass in the values that will define our unique bird as arguments to our Bird
constructor:
var cardinal = new Bird('Bruce', 'red');
This gives a new instance of Bird with name
and color
properties set to Bruce and red, respectively. The numLegs
property is still set to 2.
As we can see, cardinal
has these properties:
cardinal.name // => Bruce
cardinal.color // => red
cardinal.numLegs // => 2
The flexibility of our constructor has increased. We can now define the properties we want to assign to each Bird
at the time we create it.
This capability brings us closer to understanding why JavaScript constructors are so useful. Constructors allow us to group objects together based on shared characteristics and behavior and define a blueprint that automates their creation.
Instructions:
Create another Dog
constructor. This time, set it up to take the arguments name
and color
, and have the property numLegs
fixed at 4. Then create a new Dog
, passing it two strings for the name
and color
properties, setting it equal to the variable terrier
.
function Dog() {
// your code here
}
// your code here
function Dog(name, color) {
this.name = name;
this.color = color;
this.numLegs = 4;
}
var terrier = new Dog('Clifford', 'red');
assert((new Dog('Clifford')).name === 'Clifford', 'message: <code>Dog</code> should receive arguments <code>name</code> and <code>color</code> and set them to their respective properties');
assert((new Dog('Clifford', 'yellow')).color === 'yellow', 'message: <code>Dog</code> should receive arguments <code>name</code> and <code>color</code> and set them to their respective properties');
assert((new Dog('Clifford')).numLegs === 4, 'message: <code>Dog</code> should have property <code>numLegs</code> set to 4');
assert(terrier instanceof Dog, 'message: <code>terrier</code> should be created using the <code>Dog</code> constructor');
Verify an Object's Constructor with instanceof
Anytime we create a new object using a constructor function, that object is said to be an instance of its constructor. JavaScript gives us a convenient way to verify this programmatically. Enter the instanceof
operator. instanceof
allows us to compare an object to a constructor, returning true or false based on whether or not that object was created with the constructor.
var Bird = function(name, color) {
this.name = name;
this.color = color;
this.numLegs = 2;
}
var crow = new Bird('Alexis', 'black');
crow instanceof Bird; // => true
If we create an object without using a constructor, instanceof
will verify that it is not an instance of that constructor:
var canary = {
name: 'Mildred',
color: 'Yellow',
numLegs: 2
}
canary instanceof Bird; // => false
Instructions:
Create a new instance of the House constructor, calling it myHouse
and passing a number of bedrooms. Then, use instanceof
to verify that it is an instance of House.
var House = function(numBedrooms) {
this.numBedrooms = numBedrooms;
}
//your code here
var myHouse = ;
instanceof ;
var myHouse = new House(3000);
myHouse instanceof House;
assert(typeof myHouse.numBedrooms === number, 'message: <code>myHouse</code> should have a <code>numBedrooms</code> attribute set to a number');
assert(editor.getValue().match(/(myHouse instanceof House)/), 'message: Be sure to verify that <code>myHouse</code> is an instance of <code>House</code> using the <code>instanceof</code> operator');
Challenge
Understand where an object’s prototype comes from
Challenge Description
Just like you inherited genes from your parents, an object inherits its prototype directly from the constructor function that created it. For example, here we use the Bird
constructor to create the duck
object.
function Bird(name) {
this.name = name;
}
var duck = new Bird(“Donald”);
duck
inherits its prototype from Bird
. We can show this relationship with the isPrototypeOf
method:
Bird.prototype.isPrototypeOf(duck); // => true
Instructions
Fix the code so that it shows the correct prototype of beagle
Challenge Seed
function Dog(name) {
this.name = name;
}
var beagle = new Dog(“Snoopy”);
// Fix the code below so that it evaluates to “true”
???.isPrototypeOf(beagle);
Challenge Solution
function Dog(name) {
this.name = name;
}
var beagle = new Dog(“Snoopy”);
// Fix the code below so that it evaluates to “true”
Dog.prototype.isPrototypeOf(beagle);
Challenge Tests
assert(/Dog\.prototype\.isPrototypeOf/.test(code), "message: Show that <code>Dog.prototype</code> is the prototype of <code>beagle</code>");
Challenge
Understand the prototype chain
Challenge Description
All objects in javascript (with a few exceptions) have a prototype. It turns out that an object’s prototype itself is an object.
function Bird(name) {
this.name = name;
}
typeof Bird.prototype; // => object
Because a prototype is an object, a prototype can have its own prototype! In this case, the prototype of Bird.prototype
is Object.prototype
:
Object.prototype.isPrototypeOf(Bird.prototype); // => true
How is this useful to us? You may recall the hasOwnProperty
method from a previous challenge:
var duck = new Bird(“donald”);
duck.hasOwnProperty(“name”); // => true
The hasOwnProperty
method is defined in Object.prototype, which can be accessed by Bird.prototype
, which can then be accessed by duck
. This illustrates the prototype chain
.
In this prototype chain
, we say that Bird
is the supertype
for duck
, while duck
is the subtype
. Object
is a supertype
for both Bird
and duck
.
Object
is in fact a supertype
for all objects in javascript. Therefore any object will respond to the hasOwnProperty
method.
Instructions
Modify the code to show the correct prototype chain
Challenge Seed
function Dog(name) {
this.name = name;
}
var beagle = new Dog(“Snoopy”);
Dog.prototype.isPrototypeOf(beagle); // => true
// Fix the code below so that it evaluates to “true”
???.isPrototypeOf(Dog.prototype);
Challenge Solution
function Dog(name) {
this.name = name;
}
var beagle = new Dog(“Snoopy”);
Dog.prototype.isPrototypeOf(beagle); // => true
// Fix the code below so that it evaluates to “true”
Object.prototype.isPrototypeOf(Dog.prototype);
Challenge Tests
assert(/Object\.prototype\.isPrototypeOf/.test(code), "message: Show that <code>Dog.prototype</code> is the prototype of <code>beagle</code>");
@user512 Excellent work, Tom! These are coming along great. You do an excellent job of explaining these concepts, and these challenges are really well conceived.
Credit to @utsab. He is great at leading the team and teaching these concepts.
Here is the final list of topics. Note that this is different than what is posted in the first comment. @alayek, can you please update the first comment with this new list? Thank you.
Objects
Constructors
instanceof
Prototypes
Inheritance
Other
Mixin
to add common behavior between unrelated objects
properties
within an object
from being modified externally.The final version of all of these challenges is located in this google doc
@utsab yeah I was just about to update the first comment. Thanks!
@utsab Excellent! Could you check the boxes that correspond to your created challenges so we can see which challenges remain to be created?
Challenge Don't Repeat Yourself
Challenge Description There's a principle in programming called "Don't Repeat Yourself (DRY)". The reason repeated code is a problem is because any change requires fixing code in multiple places, which means more work for programmers and more room for errors.
Notice in the example below that the describe
method is shared by Bird
and Dog
:
Bird.prototype = {
constructor: Bird,
describe: function() {
console.log("My name is " + this.name);
}
};
Dog.prototype = {
constructor: Dog,
describe: function() {
console.log("My name is " + this.name);
}
};
The describe
method is therefore repeated in two places. We can make our code more DRY
by creating a supertype
(or parent
) called Animal
:
function Animal(){
};
Animal.prototype = {
constructor: Animal,
describe: function() {
console.log("My name is " + this.name);
}
};
Since Animal
includes the describe
method, we can then remove it from Bird
and Dog
.
Bird.prototype = {
constructor: Bird
};
Dog.prototype = {
constructor: Dog
};
Instructions
The eat
method is repeated in both Cat
and Bear
. Make the code more DRY by moving the eat
method to the Animal
supertype
.
Challenge Seed
function Cat(name) {
this.name = name;
};
Cat.prototype = {
constructor: Cat,
eat: function() {
console.log("nom nom nom");
}
};
function Bear(name) {
this.name = name;
};
Bear.prototype = {
constructor: Bear,
eat: function() {
console.log("nom nom nom");
}
};
function Animal(){
};
Animal.prototype = {
constructor: Animal
};
Challenge Tests
Test that Animal.prototype has the eat
method.
Test that Bear.prototype does not have the eat
method
Test that Cat.prototype does not have the eat
method
Challenge Solution
function Cat(name) {
this.name = name;
};
Cat.prototype = {
constructor: Cat
};
function Bear(name) {
this.name = name;
};
Bear.prototype = {
constructor: Bear;
};
function Animal(){
};
Animal.prototype = {
constructor: Animal,
eat: function() {
console.log("nom nom nom");
}
};
@QuincyLarson, I updated the list of topics and checked off the completed challenges. Every challenge except one (calling a supertype method) was created.
Since there were a few people creating challenges for this topic, I went through everyone's challenges and updated them for stylistic consistency. The final version of all of these challenges is located in this google doc.
Is this an acceptable way to submit the final versions of the challenges?
@utsab yes - this should work fine. Rather than asking you to manually port these over to GitHub, any time you have available would be better invested in helping design the remaining challenges: https://github.com/FreeCodeCamp/CurriculumExpansion/issues/46
Thanks again!
@utsab is in charge of coordinating the expansion of these challenges, but he needs your help.
For each challenge, please reply to this GitHub issue with:
Here are the challenges we have currently planned (these can be further expanded):
Objects
Constructors
instanceof
Prototypes
Inheritance
Other
Mixin
to add common behavior between unrelatedobjects
properties
within anobject
from being modified externally.Here are the challenges as we originally discussed for archival purposes: