Closed Bug 139645 Opened 24 years ago Closed 23 years ago

|this| is not properly closed

Categories

(Core :: JavaScript Engine, defect)

x86
Linux
defect
Not set
normal

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)
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
Reassigning to DOM Level 0 -
Assignee: khanson → rogerl
Attached file HTML testcase (obsolete) —
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
Argh, reopening to reassign to DOM Level 0
Status: RESOLVED → REOPENED
Component: JavaScript Engine → DOM Level 0
Resolution: WORKSFORME → ---
Reassigning for real this time -
Assignee: rogerl → jst
Status: REOPENED → NEW
QA Contact: pschwartau → desale
Re-resolving as WORKSFORME. I am using Mozilla trunk binary 2002042212 on WinNT.
Status: NEW → RESOLVED
Closed: 24 years ago24 years ago
Resolution: --- → WORKSFORME
Attached file XUL test case
Sorry, my report was not clear. Adding test case that shows the problem.
Attachment #80862 - Attachment is obsolete: true
I forgot to reopen this bug
Status: RESOLVED → REOPENED
Resolution: WORKSFORME → ---
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?
Attached file HTML testcase
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
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.
Attached file HTML testcase #2
Attached file HTML testcase #3 (obsolete) —
-------------------------------- 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
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 -
Attachment #81231 - Attachment is obsolete: true
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
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
-------------------- 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|.
cc'ing Waldemar, rginda to see if Comment #21 comes close to explaining this issue -
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.
Attached file Sample test page
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?
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 ago23 years ago
Resolution: --- → INVALID
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.

Attachment

General

Creator:
Created:
Updated:
Size: