Closed
Bug 139645
Opened 24 years ago
Closed 23 years ago
|this| is not properly closed
Categories
(Core :: JavaScript Engine, defect)
Tracking
()
VERIFIED
INVALID
People
(Reporter: p_ch, Assigned: khanson)
Details
Attachments
(5 files, 2 obsolete files)
build 2002 04
The following code gives an assertion:
setTimeout(function () {this.my_method()}, delay)
Error: this.my_method is not a function
Shaver has a workaround:
var t = this
setTimeout(function () {t.my_method()}, delay)
Comment 1•24 years ago
|
||
Is there a way to test this that doesn't involve setTimeout()?
I'd like to add a test to the JS testsuite for this -
Or is this an bug of the DOM embedding of JS Engine, and not
a JS Engine bug per se?
Assignee: rogerl → khanson
Comment 3•24 years ago
|
||
Comment 4•24 years ago
|
||
Resolving as WORKSFORME.
The testcase brings up both expected alertboxes with no errors
in the JavaScript Console. No warnings, either, if I turn on
JavaScript strict warnings.
If I have somehow miswritten the testcase, please reopen this bug
and attach the corrected version via the "Create a New Attachment"
link above; thanks -
Status: NEW → RESOLVED
Closed: 24 years ago
Resolution: --- → WORKSFORME
Comment 5•24 years ago
|
||
Argh, reopening to reassign to DOM Level 0
Status: RESOLVED → REOPENED
Component: JavaScript Engine → DOM Level 0
Resolution: WORKSFORME → ---
Comment 6•24 years ago
|
||
Reassigning for real this time -
Assignee: rogerl → jst
Status: REOPENED → NEW
QA Contact: pschwartau → desale
Comment 7•24 years ago
|
||
Re-resolving as WORKSFORME.
I am using Mozilla trunk binary 2002042212 on WinNT.
Status: NEW → RESOLVED
Closed: 24 years ago → 24 years ago
Resolution: --- → WORKSFORME
| Reporter | ||
Comment 8•24 years ago
|
||
Sorry, my report was not clear. Adding test case that shows the problem.
Attachment #80862 -
Attachment is obsolete: true
| Reporter | ||
Comment 9•24 years ago
|
||
I forgot to reopen this bug
Status: RESOLVED → REOPENED
Resolution: WORKSFORME → ---
Comment 10•24 years ago
|
||
To understand this issue better, I wrote this HTML script,
which I will attach below for convenience:
<SCRIPT>
this.name = '[object Window]';
var obj1 = {name:'[object Object] (1)',
t:this.name, T:function() {alert(this.name)} }
alert(obj1.t);
obj1.T();
var obj2 = {name:'[object Object] (2)',
t:this.name, T:setTimeout(function() {alert(this.name)}, 50) }
alert(obj2.t);
obj2.T();
</SCRIPT>
In NN4.7, IE6, and Mozilla, the output of the program is:
[object Window]
[object Object] (1)
[object Window]
[object Window]
Note for NN4.7 you have to modify the names a little bit.
NN4.7 doesn't permit spaces or brackets in name properties.
Note the only difference from |obj1| and |obj2| is that
the latter wraps the former's |T()| method in setTimeout().
By doing that, we get a different meaning for |this| inside
the object method |T()| : it evaluates to the global object.
That is the reason for the failure in the Comment #8 testcase.
Since this happens in all three browsers, I'm wondering if this
is really a bug, or the way setTimeout() is supposed to function?
Comment 11•24 years ago
|
||
Comment 12•24 years ago
|
||
Reassigning back to JS Engine, because we don't need the DOM function
setTimeout(f, delay) to reproduce this issue. In what follows, I will
use this instead:
executeNow = function(f) {f()}
Assignee: jst → khanson
Status: REOPENED → NEW
Component: DOM Level 0 → JavaScript Engine
QA Contact: desale → pschwartau
Comment 13•24 years ago
|
||
These scripts are perhaps the best examples so far of what the issue is:
---------------- HTML testcase #2 (will attach below) ----------------
<SCRIPT>
executeNow = function(f) {f()}
show = function() {alert('window.show() fired')}
var obj1 = {show:function() {alert('obj1.show() fired')},
T:function() {this.show()}}
var obj2 = {show:function() {alert('obj2.show() fired')},
T:function() {executeNow(this.show)}}
var obj3 = {show:function() {alert('obj3.show() fired')},
T:function() {var THIS = this; executeNow(THIS.show)}}
obj1.T();
obj2.T();
obj3.T();
</SCRIPT>
---------------- OUTPUT in NN4.7, IE6, and Mozilla ----------------
obj1.show() fired
obj2.show() fired
obj3.show() fired
In other words, everything is fine here. But now let us wrap
|this.show()| in a lambda expression in each case:
---------------- HTML testcase #3 (will attach below) ----------------
<SCRIPT>
executeNow = function(f) {f()}
show = function() {alert('window.show() fired')}
var obj1 = {show:function() {alert('obj1.show() fired')},
T:function() {function() {this.show()}()}
var obj2 = {show:function() {alert('obj2.show() fired')},
T:function() {executeNow(function() {this.show()})}}
var obj3 = {show:function() {alert('obj3.show() fired')},
T:function() {var THIS = this; executeNow(function() {THIS.show()})}}
obj1.T();
obj2.T();
obj3.T();
</SCRIPT>
---------------- OUTPUT in NN4.7, IE6, and Mozilla ----------------
obj1.show() fired
window.show() fired
obj3.show() fired
Shows the issue: wrapping |this.show()| by a lambda expression
causes method |T()| of obj2 to see |this| as the global object.
The code for obj3 shows the workaround Mike suggested above:
var THIS = this;
and using |THIS| instead of |this| inside the lambda expression.
In that case, the method |T()| will see |THIS| as the local object
and not the global object.
Comment 14•24 years ago
|
||
Comment 15•24 years ago
|
||
Comment 16•24 years ago
|
||
-------------------------------- SUMMARY --------------------------------
IN THE GLOBAL SCOPE, DEFINE:
executeNow = function(f) {f()}
show = function() {alert('window.show() fired')}
and define a similar show() method in each of obj1, obj2, obj3.
Now define a |T()| method in the three objects as follows:
T:function() {this.show()}}
T:function() {executeNow(this.show)}}
T:function() {var THIS = this; executeNow(THIS.show)}}
OUTPUT: obj1.show() fired
obj2.show() fired
obj3.show() fired
BUT NOW WRAP |this.show()| IN LAMBDA EXPRESSIONS IN EACH CASE:
T:function() {function() {this.show()}()}
T:function() {executeNow(function() {this.show()})}}
T:function() {var THIS = this; executeNow(function() {THIS.show()})}}
OUTPUT: obj1.show() fired
window.show() fired <<<<<<<<<<<<<<<<< Q: is this a bug?
obj3.show() fired
Comment 17•24 years ago
|
||
Argh, I made a double error in |obj1| in HTML testcase #3:
ONE: The code in the attachment accidentally left in the method
|T()| from HTML testcase #2. OOPS!!!
TWO: The code I displayed in Comment #13 is missing a set of
parentheses within the method |T()|. It should have been:
var obj1 = {show:function() {alert('obj1.show() fired')},
T:function() {(function() {this.show()})()}}
I will obsolete HTML testcase #3 above and reattach it below -
Comment 18•24 years ago
|
||
Attachment #81231 -
Attachment is obsolete: true
Comment 19•24 years ago
|
||
Corrected HTML testcase #3:
WRAP |this.show()| IN LAMBDA EXPRESSIONS IN EACH CASE:
T:function() {(function() {this.show()})()}
T:function() {executeNow(function() {this.show()})}}
T:function() {var THIS = this; executeNow(function() {THIS.show()})}}
OUTPUT: window.show() fired <<<<<<<<<<<<<<<<< Q: is this a bug?
window.show() fired <<<<<<<<<<<<<<<<< Q: is this a bug?
obj3.show() fired
Comment 20•24 years ago
|
||
Here are parts of the ECMA-262 Edition 3 Final spec at
http://www.mozilla.org/js/language
-----------------------------------------------------------------------------
10.2 Entering An Execution Context
10.2.3 Function Code
• The scope chain is initialised to contain the activation object
followed by the objects in the scope chain stored in the [[Scope]]
property of the Function object.
• Variable instantiation is performed using the activation object
as the variable object and using property attributes { DontDelete }.
• The caller provides the this value. If the this value provided by
the caller is not an object (including the case where it is null),
then the this value is the global object.
13 Function Definition
The production
FunctionExpression : function ( FormalParameterListopt ) { FunctionBody }
is evaluated as follows:
1. Create a new Function object as specified in section 13.2 with parameters
specified by FormalParameterListopt and body specified by FunctionBody.
Pass in the scope chain of the running execution context as the Scope.
2. Return Result(2).
-----------------------------------------------------------------------------
Points to remember:
1. The [[Scope]] property of a function is determined at compile-time
2. The |this| value is looked up at run-time
Comment 21•24 years ago
|
||
-------------------- In HTML testcase #2: --------------------
var obj1 = {show:function() {alert('obj1.show() fired')},
T:function() {this.show()}}
When T is called, |this| is seen as the local object.
The resulting output is 'obj1.show() fired'.
-------------------- In HTML testcase #3: --------------------
WRAP |this.show()| IN A LAMBDA EXPRESSION:
var obj1 = {show:function() {alert('obj1.show() fired')},
T:function() {(function() {this.show()})()}}
When T is called, |this| is seen as the global object.
The resulting output is 'window.show() fired'.
ATTEMPT AT EXPLANATION:
Remember this from above:
• The caller provides the this value. If the this value provided by
the caller is not an object (including the case where it is null),
then the this value is the global object.
In testcase #2, |this| is inside |T|, which is being called from |obj1|.
Therefore, |this| is provided by |obj1| as |obj1|.
In testcase #3, |this| is inside the lambda expression, which is being
called from |T|. Therefore it is up to |T| to provide the |this| value.
This is where I get lost: I would have guessed |T| has a |this| value
provided by its own caller, |obj1|, which would evaluate to |obj1|.
I would expect this to get passed down the chain to the lambda expression.
But this is not what's happening. Apparently |T| has no |this| value (?)
so according to the spec, |this| evaluates to the global object.
Mike's trick in |obj3| is to do, in the definition of |T|,
var THIS = this;
This means that at compile-time, |T| will store in its [[Scope]] property
a reference to the |this| value of |obj3|, namely, |obj3|. So at run-time
when |T| is called, |THIS| will point to |obj3|.
Comment 22•24 years ago
|
||
cc'ing Waldemar, rginda to see if Comment #21 comes close
to explaining this issue -
Comment 23•24 years ago
|
||
Phil, you have the right idea. Every function closes over the 'this' variable
-- it does not inherit it from its outer lexical scope. The caller always
provides the value to put into the 'this' variable. To see in detail where it
comes from, look at ECMA 11.2.3:
When evaluating a call expression 'f(args)', the subexpression 'f' is evaluated
and then GetValue is called on it to get the function. If 'f' is not an lvalue,
'this' gets the value null, which the called function then turns into a global
object. If 'f' is an lvalue, 'this' gets the value obtained by GetBase (ECMA
8.7). If 'f' is a member expression such as 'x.y', then the base is the value
of 'x'. If 'f' is a simple identifier reference such as 'y', then the base is
whatever scope object contains the variable 'y'.
According to this, the behavior on the examples in the prior comments is
correct. executeNow passes its local scope as the 'this' argument to 'f'.
However, I tried different, slightly weirder examples which don't match the ECMA
spec:
js> this.x = "g"
g
js> x
g
js> function show() {return this.x}
js> show()
g
js> function a() {
var x = "h";
return show();
}
js> a()
g
So far so good, but:
js> function b() {
var x = "i";
var y = show;
return y();
}
js> b()
g
Here I'd expect show's this value to be b's activation object and get the answer
"i" instead of "g".
js> function b() {
var x = "i";
var y = function() {return this.x};
return y();
}
js> b()
g
Same thing, following a strict ECMA interpretation.
Before trying to fix this case, let's see what Internet Explorer does. If it
behaves as above (returns "g"), then I'd be more inclined to mark this as a bug
in the standard than to fix it in our implementation.
Comment 24•24 years ago
|
||
According to ECMA, the output of the test should be:
ThisTest:
Show: called globally = g
Show: called from a = g
Show: called from b = *i*
Show: called from c = k
Show: called via with from c = k
Show: called via with and rename from c = g
Show: called via with and local scope from c = k
Show: called via with and local scope rename from c = *k*.
Trying it on both NN6 and Mac IE, I get:
ThisTest:
Show: called globally = g
Show: called from a = g
Show: called from b = g
Show: called from c = k
Show: called via with from c = k
Show: called via with and rename from c = g
Show: called via with and local scope from c = k
Show: called via with and local scope rename from c = g.
(My IE doesn't show the last two lines because it doesn't like nested functions
for some reason.)
Although this is non-ECMA, I think it's a saner behavior than what ECMA
specifies and should be treated as a bug in the spec. Any other data points?
Comment 25•23 years ago
|
||
Time to close this bug as Invalid. As Waldemar pointed out above,
---
Every function closes over the 'this' variable -- it does not inherit it
from its outer lexical scope. The caller always provides the value to put into
the 'this' variable. To see in detail where it comes from, look at ECMA 11.2.3:
11.2.3 Function Calls
When evaluating a call expression 'f(args)', the subexpression 'f' is evaluated
and then GetValue is called on it to get the function. If 'f' is not an lvalue,
'this' gets the value null, which the called function then turns into a global
object. If 'f' is an lvalue, 'this' gets the value obtained by GetBase (ECMA
8.7). If 'f' is a member expression such as 'x.y', then the base is the value
of 'x'. If 'f' is a simple identifier reference such as 'y', then the base is
whatever scope object contains the variable 'y'.
According to this, the behavior on the examples in the prior comments is
correct. |executeNow| passes its local scope as the 'this' argument to 'f'.
---
Status: NEW → RESOLVED
Closed: 24 years ago → 23 years ago
Resolution: --- → INVALID
Comment 26•23 years ago
|
||
Marking Verified -
Pierre, thank you for this report. Note I have filed a new bug on
the issues Waldemar discovered in Comment #23 and Comment #24 above:
bug 167392
"Technical issues on resolution of |this| inside function calls"
Status: RESOLVED → VERIFIED
You need to log in
before you can comment on or make changes to this bug.
Description
•