How do Javascript objects work?
Preface
- I know the right code for these examples.
- What I want to know is why the following examples won't work as expecte开发者_如何学God.
Code
With parentheses when calling
sayIt
function.function Fruit(type){ this.type = type; this.taste = "Awful"; this.thought = sayIt(); } function sayIt(){ return this.taste+" "+ this.type; } window.onload = function (){ var lemon= new Fruit("Lemon"); alert(lemon.thought); };
This will alert "undefined undefined", why?
sayIt
function without parentheses.function Fruit (type){ this.type = type; this.taste = "Awful"; this.thought = sayIt; } function sayIt(){ return this.taste +" "+ this.type; } window.onload = function (){ var lemon= new Fruit("Lemon"); alert(lemon.thought); };
This will literally write down the function on the alert box, why?
Thank you in advance.
Notes inline, discussion below, references and further reading at the end:
With parentheses when calling
sayIt
function.function Fruit(type){ this.type = type; this.taste = "Awful"; // Here, you're *calling* the `sayIt` function and assigning its // return value to `this.thought`. During the call, `this` will // refer to the global object (not to the `Fruit` instance). this.thought = sayIt(); } function sayIt(){ // If this is called as it is above, `this` is the global object, // which is `window` in browsers. Since `window` doesn't have // `taste` or `type` properties, this returns "undefined undefined". // That's what `this.thought` above receives. return this.taste+" "+ this.type; } window.onload = function (){ var lemon= new Fruit("Lemon"); // You've said this alerts "undefined undefined", but I think you'll // find it just alerts "undefined" (singular). There is no `sayIt` // property on the `lemon` instance at all. If you alerted // `lemon.thought` instead, you'd see the "undefined undefined" we // stored there above. alert(lemon.sayIt); };
sayIt
function without parentheses.function Fruit (type){ this.type = type; this.taste = "Awful"; // Here you're assigning the `sayIt` function to `this.thought`. // Perfectly normal stuff. this.thought = sayIt; } function sayIt(){ return this.taste +" "+ this.type; } window.onload = function (){ var lemon= new Fruit("Lemon"); // Here you're also *referring* to the function object, not calling // it. (To call a function, you use `()` after it.) So since functions // are objects, you're passing an object reference into `alert`. // Alert will try to convert that to a string, and the // implementation of `toString` on `Function` objects in most // environments is to dump out a version of the source code of // the function (although this behavior is *not* standardized and // some browsers, esp. mobile browsers, don't do it). alert(lemon.thought); };
Key concepts from the above:
Functions are objects. You call a function by using a reference to it followed by
()
, e.g.:x = foo();
means "call
foo
and assign its return value tox
".You refer to the function object by just using its name, sans
()
, e.g.:x = foo;
means "assign the function object
foo
tox
. (You could then call it:x()
.)Unlike some other languages, in JavaScript,
this
is defined entirely by how a function is called, not where it's defined. When you call a function via a free variable (e.g.,foo()
), you're doing nothing to explicitly set thethis
for the function call, and sothis
will be the default value, which is the global object (window
on browsers).You can set what
this
is in two different ways:A. Put the function reference on an object property and call the function via the property's reference to it, e.g.:
// To put it on whatever `this` is at the moment: this.thought = sayIt; // Or to put it on an object we have in the variable `x`: x.thought = sayIt;
You'd then call it via the property:
this.thought(); x.thought();
Within the function call,
this
will refer to the object from which you retrieved the property.B. Using the function object's intrinsic
call
orapply
functions:sayIt.call(lemon);
means "call the
sayIt
function, makingthis
=lemon
within the function call." If you pass further arguments tocall
, they'll be passed to the function, so:sayIt.call(lemon, 1, 2, 3);
means "call
sayIt
withthis
=lemon
and pass in1
,2
, and3
.There's also the
apply
function, which is just the same thing except you pass the arguments as an array rather than individually:// note ------------v-------v---- the square brackets create an array sayIt.applyl(lemon, [1, 2, 3]); // More explicitly: var a = [1, 2, 3]; sayIt.apply(lemon, a);
means "call
sayIt
withthis
=lemon
and pass in1
,2
, and3
.
I've blogged a bit on these subjects, FWIW:
- Mythical methods
- You must remember
this
- Anonymouses anonymous (talks more about function references and assigning them)
More to explore:
- The ECMAScript specification (yes, really)
- MDC's JavaScript pages
- Crockford's JavaScript pages (advanced, read and understand all of the above first)
I'm assuming there is a typo in the first example and you meant to write alert(lemon.thought())
. The reason you're seeing undefined undefined
is because this.thought
is set to the return value of the sayIt
function. In the sayIt
function, this
refers to the window
object and not the Fruit
object. Since window
doesn't have a taste
or type
property, you will see undefined undefined
.
In the second example (I'll again assume you have a typo and you meant to do alert(lemon.thought())
), you this.thought
is set to be a reference to the sayIt
function, so you're not actually calling it. When you alert a reference to a function, it will print out the source of that function.
BONUS
You can get it to work the way you want if you do this:
this.thought = sayIt.call(this);
This will set the this
to point to the Fruit
object and now sayIt
will return what you want.
In the second example, you will get what you want if you do this:
alert(lemon.thought());
lemon.thought
refers to sayIt
and the this
will be set properly because you are calling a member function of lemon
.
The first argument to call
(or its friend apply
) is for the value of this
in the context of that function.
UPDATE
Dan, in the second example even without the change I made, that is if you still have lemon.thought = sayIt;
and you say alert(lemon.thought);
. You will still get the source of the function because you're not calling the function and passing its result to alert
. You're passing the function reference itself to alert
, and so it will print the source.
First code:
EDIT NB: edited to reflect edits in the question
function Fruit(type){
this.type = type;
this.taste = "Awful";
this.thought = sayIt(); // this line invokes sayIt, with global context,
// so sets thought to 'undefined undefined'
}
function sayIt() {
return this.taste+" "+ this.type; // as called, this == window, not the Fruit object
}
window.onload = function() {
var lemon= new Fruit("Lemon");
alert(lemon.thought); // see above
};
Second code:
function Fruit (type){
this.type = type;
this.taste = "Awful";
this.thought = sayIt;
}
function sayIt(){
return this.taste +" "+ this.type;
}
window.onload = function (){
var lemon= new Fruit("Lemon");
alert(lemon.thought); // doesn't -call- the function, results in .toString() on
// the function object
};
I think you have a mistake in the first example. You wrote alert(lemon.sayIt);
where it should be alert(lemon.thought);
. Anyway...
With parentheses when calling
sayIt
function. This will alert "undefined undefined", why?
Because when you execute this.thought = sayIt();
, you are assigning the return value of sayIt
to this.thought
. When you call sayIt()
, then this
inside the function will refer to the global object which is window
is browser. And window.taste
and window.type
are not defined. Hence this.thought
will have string "undefined undefined"
assigned to it.
sayIt
function without parentheses. This will literally write down the function on the alert box, why?
In this case you are assigning a reference to the function itself to this.tought
. The string representation of a function is the code itself. Now you can call the function via lemon.tought()
. If you do so, this
will refer to the lemon
object and the output will be as expected.
So, call the function: alert(lemon.tought())
.
I suggest you read about
- Functions
- Working with Objects
Based on your code, the "lemon" object has the properties "type", "taste", and "thought".
alert(lemon.sayIt);
This line alerts the value of the "sayIt" property on "lemon", converted to a String. Since the "lemon" object doesn't have a "sayIt" property, it converts the value undefined to a string and displays it.
alert(lemon.thought);
This line alerts the value of the "thought" property on "lemon", converted to a String. Since the "thought" property is a function, the string conversion displays the text of the function.
What you probably want to do is call the function, and display its return value: alert(lemon.thought());
The function sayIt is defined after it is called. Moving the definition of sayIt above the definition of fruit would fix.
You're alerting the definition of a function and not the return value from calling that function.
sayIt is not a function of the Fruit object - it's a function of the window object.
Fruit.thought is a function, or a "function pointer" as it is assigned to the window.sayIt function.
精彩评论