?
function Person(name){
this.name = name
}
new statement to create a Personvar bob = new Person('Bob')
// {name: 'Bob'}
bob is indeed a Person, we can ask bob instanceof Person
// true
Person as a function - without the new,Person('Bob')
// undefined
undefined. Also, you really don’t want to do this, because you’ve
just unintentionally created a name global variablename
// 'Bob'
name, it would have been
overwritten. The reason this happens is because when you call a function as a function(without new), this is set to the global object - in the browser, this is the window object, seewindow.name
// 'Bob'
this === window
// true
Someone pointed out though, that you can prevent this polluting of the namespace(those are just big words for creating global variables) by using this trick
function Person(name){
if (!(this instanceof Person))
return new Person(name)
this.name = name
}
- Check whether
thisis really aPerson- which it would be if called usingnew. - If it indeed is a
Person, go on your merry way. - If it is not a
Person, use anewto create aPerson- the correct way, and return it.
Person, and it doesn’t pollute the namespace.Person('Bob')
// {name: 'Bob'}
name
// undefined
new still works toonew Person('Bob')
// {name: 'Bob'}
new. But, you might be thinking, can I return a non-Person? That would be kind of like lying.function Cat(name){
this.name = name
}
function Person(name){
return new Cat(name)
}
var bob = new Person('Bob')
bob instanceof Person
// false
bob instanceof Cat
// true
Person and I get a Cat? Well, in Javascript it can happen. You can even return an Array.function Person(name){
return [name]
}
new Person('Bob')
// ['Bob']
function Person(name){
this.name = name
return 5
}
new Person('Bob')
// {name: 'Bob'}
Number, String, Boolean, Date are all primitive types. If you return one of these types of values
from a constructor, it would be ignored and the constructor would go back to its normal behavior of returning
the this object.Methods
In the beginning, I said that functions double as constructors, well, actually, they more like triple. Functions also act as methods.If you know OOP, you know that methods are the behaviors of the object - what the object can do. In Javascript, methods are just functions attached to an object - you can create methods simply by creating functions and assigning them to the object
function Person(name){
this.name = name
this.sayHi = function(){
return 'Hi, I am ' + this.name
}
}
var bob = new Person('Bob')
bob.sayHi()
// 'Hi, I am Bob'
var bob = {name: 'Bob'} // this is a Javascript object!
bob.sayHi = function(){
return 'Hi, I am ' + this.name
}
var bob = {
name: 'Bob',
sayHi: function(){
return 'Hi, I am ' + this.name
}
}
Inheritance and the Prototype
Right, so inheritance. You know inheritance, right? You know how in Java, for example, you can have one class inherit another and automatically get all the methods and variables of the parent class?public class Mammal{
public void breathe(){
// do some breathing
}
}
public class Cat extends Mammal{
// now cat too can breathe!
}
function Mammal(){
}
Mammal.prototype.breathe = function(){
// do some breathing
}
function Cat(){
}
Cat.prototype = new Mammal()
Cat.prototype.constructor = Cat
// now cat too can breathe!
What’s this prototype? That’s just a bunch of gibberish!
Javascript is different from traditional object-oriented languages in that it
uses prototype inheritance. In a nutshell, prototype inheritance in Javascript works like this:- An object has a number of properties. This includes any attributes or functions(methods).
- An object has a special parent property, this is also called the prototype of the object(
__proto__). An object inherits all the properties of its parent. - An object can override a property of its parent by setting the property on itself.
- A constructor creates objects. Each constructor has an associated
prototypeobject, which is simply another object. - When an object is created, it’s parent is set to the prototype object associated with the constructor that created it.
First, we create a constructor for
Mammalfunction Mammal(){
}
Mammal already has an associated prototypeMammal.prototype
// {}
var mammal = new Mammal()
mammal.__proto__ === Mammal.prototype
// true
breathe function to the prototype of MammalMammal.prototype.breathe = function(){
// do some breathing
}
mammal the instance can breathemammal.breathe()
Mammal.prototype. Next,function Cat(){
}
Cat.prototype = new Mammal()
Cat constructor is created and we set Cat.prototype to a new instance of Mammal. Why do we do this?var garfield = new Cat()
garfield.breathe()
Mammal and will therefore be able to breathe as well. Next,Cat.prototype.constructor = Cat
garfield.__proto__ === Cat.prototype
// true
Cat.prototype.constructor === Cat
// true
garfield instanceof Cat
// true
Cat, you create a 2-level chain, in that garfield is now parented by Cat.prototype which, since it is
an instance of Mammal, is in turn parented by Mammal.prototype.Now, guess who’s the parent of
Mammal.prototype? Yeah, you guessed it, Object.prototype. So actually, it’s a 3-level chainYou can add properties to any ofgarfield->Cat.prototype->Mammal.prototype->Object.prototype
garfield’s parents, and garfield would
magically gain those properties too, even after garfield has already been
created!Cat.prototype.isCat = true
Mammal.prototype.isMammal = true
Object.prototype.isObject = true
garfield.isCat // true
garfield.isMammal // true
garfield.isObject // true
'isMammal' in garfield
// true
garfield.name = 'Garfield'
garfield.hasOwnProperty('name')
// true
garfield.hasOwnProperty('breathe')
// false
Setting Methods on the Prototype
Now that you really understand prototypes, let’s go back to the very first example of defining methods on objectsfunction Person(name){
this.name = name
this.sayHi = function(){
return 'Hi, I am ' + this.name
}
}
Person.prototypefunction Person(name){
this.name = name
}
Person.prototype.sayHi = function(){
return 'Hi, I am ' + this.name
}
In the first version, each time you create a person, a new
sayHi function will be created for him, where as in the second version, only one sayHi function is ever created, and is shared amongst all persons that are created - because Person.prototype is their parent. Thus, declaring methods on the prototype is more memory efficient.Apply and Call
As you can see, functions become methods just by virtue of being attached to objects, at which point thethis within that function refers to the object which it is attached to, right? Well… not exactly. Look at our previous examplefunction Person(name){
this.name = name
}
Person.prototype.sayHi = function(){
return 'Hi, I am ' + this.name
}
jack and jillvar jack = new Person('Jack')
var jill = new Person('Jill')
jack.sayHi()
// 'Hi, I am Jack'
jill.sayHi()
// 'Hi, I am Jill'
sayHi is not attached to jack or jill, rather, it’s attached to their prototype: Person.prototype. How does the function sayHi know jack and jill’s names?
Answer: this is not bound to any particular object until you call the function.
When you call jack.sayHi(), sayHi’s this will be bound to jack; when you call jill.sayHi(), it will be bound to jill instead, but binding does not change anything about the function itself - it’s still the same function!It turns out that you can explicitly bind a function to an object yourself.
function sing(){
return this.name + ' sings!'
}
sing.apply(jack)
// 'Jack sings!'
apply method belongs to Function.prototype(yeah, that’s right, functions are objects and have prototypes too and can also have properties!). So, you can use apply
with any function to call it while binding it to the object of your
choosing, even if the function is not attached to it. In fact, you can even apply the method to an object of a different typefunction Flower(name){
this.name = name
}
var tulip = new Flower('Tulip')
jack.sayHi.apply(tulip)
// 'Hi, I am Tulip'
Wait a minute! A Tulip is not supposed to say hi!To that, I would say
Everything is everybody. Everybody is everything. We all cool! Just…chill, man!As long as the object has a
name property, sayHi is happy to print it out. This is the principle of duck typingIf it quacks like a duck and it walks like a duck - it’s a duck to me.I am sure I misquoted that, but whatever.
Now back to the
apply function: if you want to include parameters you can pass them as an array as the second parameter to apply.function singTo(other){
return this.name + ' sings for ' + other.name
}
singTo.apply(jack, [jill])
// 'Jack sings for Jill'
Function.prototype also has a call function, which works very much like apply.
The only difference is in that rather than passing the parameters as an
array in the second parameter, you would just add them to the end:sing.call(jack, jill)
// 'Jack sings for Jill'
The new method
Now, for something fun…apply is really handy for certain situations when you want to call a function with a variable list of arguments. For example, the Math.max function takes a variable number of argumentsMath.max(4, 1, 8, 9, 2)
// 9
apply you can get the max for an arbitrary array,Math.max.apply(Math, myarray)
Now, given that
apply is so useful, there may come times when you want to use it, but rather than call-as-function,Math.max.apply(Math, args)
new Person.apply(Person, args)
Person.apply as a constructor. How about this(new Person).apply(Person, args)
apply method on that person.What to do? Thanks to an idea presented on this answer on StackOverflow, there is a way!
We can create a
new method for Function.prototype.Function.prototype.new = function(){
var args = arguments
var constructor = this
function Fake(){
constructor.apply(this, args)
}
Fake.prototype = constructor.prototype
return new Fake
}
new method rather than the new statementvar bob = Person.new('Bob')
new method works.First,
var args = arguments
var constructor = this
function Fake(){
constructor.apply(this, args)
}
Fake constructor which will apply our real constructor as a method when created. In the context of the new method, this is the real constructor - we save it to be used in the Fake constructor. We also save the arguments with which new was called to reuse in the Fake constructor. Next,Fake.prototype = constructor.prototype
Fake.prototype to the original constructor. Since the prototype’s constructor property is still set to the original constructor, any object created by Fake will still be an instanceof the original constructor. Finally,return new Fake
Fake constructor and return it.Did you get all that? It’s okay if you don’t get it the first time; just look it over and poke at it a few more times.
Anyways, the point of all of that was that now you can do things like
var children = [new Person('Ben'), new Person('Dan')]
var args = ['Bob'].concat(children)
var bob = Person.new.apply(Person, args)
Person twice? We can probably write a helper methodFunction.prototype.applyNew = function(){
return this.new.apply(this, arguments)
}
var bob = Person.applyNew(args)
What’s the point of this exercise anyway?Well, it shows that Javascript is a flexible little language. Even if it doesn’t do the things you want, you can probably mold it into doing them.
Summary
This is the end of this lesson/article/blog post. It was nice having you! Today we learned about:- Constructors
- Methods and Prototypes
applyandcall- Implementing the
newmethod
No comments:
Post a Comment