jdw
jdw

Reputation: 4115

Why does setting a variable (outside of the object scope) equal to an object method return an undefined result?

The code below was copied and pasted from the MDN page on OOP with JavaScript. I have researched general questions on OOP with JavaScript. However, I'm a beginner and have the following questions about this code snippet:

  1. What is the purpose of the line Person.prototype.gender = '';? If I take it out and run the code, I get the same results.

  2. Why exactly does calling genderTeller() cause an 'undefined' alert? The MDN explanation seems a bit thin from a beginner's perspective. Is this a scope issue?

function Person(gender) {
    this.gender = gender;
}

Person.prototype.gender = '';

Person.prototype.sayGender = function () {
    alert(this.gender);
};

var person1 = new Person('Male');
var genderTeller = person1.sayGender;

person1.sayGender(); // alerts 'Male'
genderTeller(); // alerts undefined
alert(genderTeller === person1.sayGender); // alerts true
alert(genderTeller === Person.prototype.sayGender); // alerts true

Upvotes: 2

Views: 115

Answers (4)

jfriend00
jfriend00

Reputation: 707696

For question 1, the line

Person.prototype.gender = '';

gives the gender property a default value so that even if it is not assigned some other value when a new Person object is created, then it has a default value that is not undefined. There is a difference between '' and undefined that is important sometimes and not important other times, but there is a difference.


For question 2, when you do this:

var person1 = new Person('Male');
var genderTeller = person1.sayGender;
genderTeller(); // alerts undefined

Your variable genderTeller contains a function pointer and only a function pointer. It has NO association with your person1 object. When you call it as just a function, the this pointer inside the function will be set to either the global object (window in a browser) or undefined (if running in strict mode) and thus because this is not a Person object, this.gender will not contain an appropriate value and will likely be undefined.


There is a very important lesson in this error. In javascript, the this pointer is set according to how a method is called. When you call genderTeller(), you're just calling a function and thus there is no association whatsoever with any particular Person object. The this pointer will NOT point to a Person object and thus any references to this.xxx that assume it's a Person object will not work.

When you call person1.sayGender(), this is a very different way of calling the same function. Because you're calling it via an object reference, javascript will set the this pointer to the person1 object and it will work.


The difference between these two scenarios is subtle, but very important in javascript so I'll try to explain a bit more.

After you create your person1 object, it's an object that contains a property called sayGender. That property contains a value that points to the function for the sayGender operation. sayGender is just a function that's contained in a property (technically in the prototype chain on the person1 object, but you can just think of it as a property on that object.

When you do this:

var genderTeller = person1.sayGender;

You're creating a new variable called genderTeller that now ALSO holds a pointer to that same function. But, just like when it's in the sayGender property, it is just a function. It has no innate binding to any object. It only gets a binding to an object if you call it via an object reference (or if you use .call() or .apply() to force an object reference but that's beyond what you're doing here). When you just call

genderTeller()

you are just calling a function and it will have no object reference associated with it and thus this will not point to a Person object when the function runs.


As I mentioned above, it is possible to force an object reference. For example, you could do all of these:

var genderTeller = person1.sayGender;
genderTeller.call(person1);

// .apply() only differs from .call() in how 
// arguments are passed to the function
// since you have no arguments to sayGender() it looks the same as .call()
var genderTeller = person1.sayGender;
genderTeller.apply(person1);

var genderTeller = person1.sayGender.bind(person1);
genderTeller();

And, it would again work because you were forcing an association with the person1 object when the function is called.

See MDN's reference for .call() and .apply() and .bind() if you want more info on this, but you should generally not need to use those just to call methods on an object because just calling it with the form obj.method() creates the object association and causes the this pointer to be set appropriately for you.

Upvotes: 2

Chi Row
Chi Row

Reputation: 1106

Here is the correct answer.

1) The prototype definition is not for a default value for Person. In fact, if you create another new Person with no gender, you will see it remains undefined.

var person2 = new Person();
person2.sayGender;  // alerts undefined

The point here is to show that the constructor definition overrides a prototype definition.

2) Calling genderTeller() causes undefined because genderTeller is a global function which happens to have same function definition that it copied from person1.sayGender method. genderTeller is same as window.genderTeller.

Therefore when you execute genderTeller(), 'this' = window. Since window does not have a 'gender' property, you get an undefined. You can see it by this code

genderTeller();  // returns undefined
gender = 'hi there';
genderTeller();  // returns 'hi there'

Hope that helps. Here is a Plunker. http://plnkr.co/edit/wwc2vYIvH9QdFYStesVW

Upvotes: 0

Jonathan Lonowski
Jonathan Lonowski

Reputation: 123513

What is the purpose of the line Person.prototype.gender = '';? If I take it out and run the code, I get the same results.

This seems to establish a default value for the gender property. With it, the property is still set even when an instance is created without calling the constructor:

var person2 = Object.create(Person.prototype);
console.log(person2.gender); // ""

Which could be useful when creating a child type:

function Employee() {}
Employee.prototype = Object.create(Person.prototype);

console.log(new Employee().gender); // ""

Why exactly does calling genderTeller() cause an 'undefined' alert?

MDN's document on this should explain it further, especially the section on "Function context." But, the value of this is determined when a function is called rather than by when or where it's defined.

By assigning person1.sayGender to genderTeller, it's being disassociated from person1. So, it's no longer a "method" of a particular object.

Instead, it's called as a regular function with the value of this being defaulted to the global object, which is window in browsers.

window.gender = 'n/a';

var genderTeller = person1.sayGender;
genderTeller(); // logs: 'n/a'

Upvotes: 3

kzahel
kzahel

Reputation: 2785

I looked over the article and was struck by how confusing this would indeed be to somebody not already intimately familiar with javascript.

Regarding #1, I believe this is a hint to the JIT compiler that "gender" is a string. Javascript JIT compilers like it when object properties stay the same type (in this case, String). It's silly that the article does not describe this, or that this line is there at all. Maybe it's there to demonstrate that you can override prototype properties in a "new Person" instance

Regarding #2, when you call object.method() in automatically calls method where the stack fills in "object" as "this". but if you do something like var detachedmethod = curobject.method and then call detachedmethod(), it does not call with "this" bound as curobject (instead, in the method body, this === undefined. Or maybe "window" I'm not sure :-))

All in all, it's a bunch of nitpicking, and not very important for day to day javascript usage, and can be picked up as you go along.

Upvotes: 0

Related Questions