From: Thomas 'PointedEars' Lahn on 17 Feb 2010 05:52 kangax wrote: > Thomas 'PointedEars' Lahn wrote: >> Cody Haines wrote: >>> Jorge wrote: >>>> Do you think -as I do- that the Math object is an ugly artifact ? >>>> Well, here's a nice way of getting rid of it : >>>> >>>> Number.prototype.sin= function () { return Math.sin(+this); }; >>>> [...] >>>> (1.234).sin().asin() >>>> [...] >>> >>> Just out of curiosity, does IE support this? I don't see why it >>> wouldn't, just making sure >> >> [...] >> In fact, given that, according to Editions 3 and 5 of the ECMAScript >> Language Specification, when evaluating the CallExpression's >> /MemberExpression/ any primitive value would be converted into an >> appropriate value of type Object (ES3/5, 11.2.3, 11.2.1, and 9.9), this >> SHOULD work everywhere where `Number.prototype' is supported (which >> AFAIK excludes only JavaScript 1.0 as of Netscape 2.x, and JScript 1.0 >> as of IE 3.0 and IIS 3.0 ).¹ [...] >> ___________ >> ¹ To be sure of that, I have debugged the algorithms of ECMAScript Ed. >> 5, >> and compared with Ed. 3; I can post more detailed results of my >> research if anyone is interested. > > I'm interested. In the following, I will use `Number.prototype.sin' as defined above and `(1.234).sin()' for the example. The basic productions that apply to the expression (1.234).sin() in both Editions are: CallExpression : MemberExpression Arguments MemberExpression : PrimaryExpression MemberExpression . Identifier PrimaryExpression : Literal ( Expression ) Expression : AssignmentExpression ... LeftHandSideExpression : NewExpression NewExpression: MemberExpression Literal :: NumericLiteral NumericLiteral :: DecimalLiteral DecimalLiteral :: DecimalIntegerLiteral . DecimalDigits_opt ExponentPart_opt Arguments : () As a result, the following unifications take place: In the algorithm for the production CallExpression : MemberExpression Arguments (section 11.2.3): CallExpression := (1.234).sin() MemberExpression := (1.234).sin Arguments := () And the following steps: 1. Evaluate MemberExpression. In the algorithm for MemberExpression : MemberExpression [ Expression ] (section 11.2.1): MemberExpression := (1.234) Expression := "sin" Steps per ES3F: 1. Evaluate MemberExpression. --> Result(1) := 1.234 2. Call GetValue(Result(1)). --> Result(2) := 1.234 3. Evaluate Expression. --> Result(3) := "sin" 4. Call GetValue(Result(1)). --> Result(4) := "sin" 5. Call ToObject(Result(2)). ^^^^^^^^^^^^^^^^^^^^^^^^^ (9.9) ToObject The operator ToObject converts its argument to a value of type Object according to the following table: [...] Number Create a new Number object whose [[value]] property is set to the value of the number. See section 15.7 for a description of Number objects. [...] --> Result(5) := new Number(1.234) 6. Call ToString(Result(4)). --> Result(6) := "sin" 7. Return Reference(base=Result(5), name=Result(6)): --> Return Reference(base=new Number(1.234), name="sin"). It is thus quite obvious already that the primitive Number value is being converted into an equivalent Number object that has the current value of `Number.prototype' next in its prototype chain, and we can skip analyzing the subsequent steps for ES3F in detail. JFTR, they are: 2. Evaluate /Arguments/, producing an internal list of argument values (section 11.2.4). 3. Call GetValue(Result(1)). 4. If Type(Result(3)) is not Object, throw a *TypeError* exception. 5. If Result(3) does not implement the internal [[Call]] method, throw a TypeError exception. 6. If Type(Result(1)) is Reference, Result(6) is GetBase(Result(1)). Otherwise, Result(6) is *null*. 7. If Result(6) is an activation object, Result(7) is *null*. Otherwise, Result(7) is the same as Result(6). 8. Call the [[Call]] method on Result(3), providing Result(7) as the *this* value and providing the list Result(2) as the argument values. 9. Return Result(8). However, the conversion of the primitive value to an object for the purpose of property lookup is a lot harder to see with the ES5 algorithms, which is why I am going deeply into detail there: (11.2.3) Function Calls The production /CallExpression/ : /MemberExpression/ /Arguments/ is evaluated as follows: 1. Let /ref/ be the result of evaluating /MemberExpression/. --> ref := Evaluate( (1.234).sin ) (11.2.1) Property Accessors [...] The production /MemberExpression/ : /MemberExpression/ [ /Expression/ ]" (equivalence of dot and bracket property accessor notation for Identifiers; the ed.) is evaluated as follows: MemberExpression := (1.234) Expression := "sin" 1. Let /baseReference/ be the result of evaluating /MemberExpression/. --> baseReference := 1.234 2. Let /baseValue/ be GetValue(/baseReference/). --> baseValue := GetValue(baseReference) --> baseValue := 1.234 3. Let /propertyNameReference/ be the result of evaluating /Expression/. --> propertyNameReference := "sin" 4. Let /propertyNameValue/ be GetValue(/propertyNameReference/). --> propertyNameValue := "sin" 5. Call CheckObjectCoercible(/baseValue/). --> CheckObjectCoercible(1.234) exits normally 6. Let /propertyNameString/ be ToString(/propertyNameValue/). --> propertyNameString := "sin" 7. If the syntactic production that is being evaluated is contained in strict mode code, let /strict/ be *true*, else let /strict/ be *false*. We assume no strict mode here (it does not really matter for this algorithm anyway) --> strict := false 8. Return a value of type Reference whose base value is /baseValue/ and whose referenced name is /propertyNameString/, and whose strict mode flag is /strict/. --> return Reference(base=1.234, name="sin", strict=false) --> ref := Reference(base=1.234, name="sin", strict=false) (11.2.3) 2. Let /func/ be GetValue(/ref/). --> func := GetValue(ref) (8.7.1) GetValue(V) V := Reference(base=1.234, name="sin", strict=false) 1. If Type(/V/) is not Reference, return /V/. --> Type(V) is Reference, continue. 2. Let /base/ be the result of calling GetBase(/V/). --> base := GetBase(V) --> base := 1.234 (section 8.7) 3. If IsUnresolvableReference(/V/), throw a *ReferenceError* exception. V := Reference(base=1.234, name="sin", strict=false) (8.7) IsUnresolvableReference(V) := (GetBase(V) == undefined) --> IsUnresolvableReference(V) == false --> Continue. 4. If IsPropertyReference(/V/), then V := Reference(base=1.234, name="sin", strict=false) (8.7) IsPropertyReference(V) := Type(V) == Object || HasPrimitiveBase(V) --> Type(V) == Number HasPrimitiveBase(V) := Type(GetBase(V)) == Boolean || Type(GetBase(V)) == String || Type(GetBase(V)) == Number --> HasPrimitiveBase(V) == true --> IsPropertyReference(V) == true a. If HasPrimitiveBase(V) is false, then let /get/ be the [[Get]] internal method of /base/, otherwise let /get/ be the special [[Get]] internal method defined below. --> HasPrimitiveBase(V) == true --> get := special [[Get]] b. Return the result of calling the /get/ internal method using /base/ as its *this* value, and passing GetReferencedName(/V/) for the argument. --> return get(this=1.234, "sin") --> return special [[Get]](this=1.234, "sin") 1. Let O be ToObject(base). ^^^^^^^^^^^^^^^^^^^^^^^^ --> O := ToObject(1.234) --> O := new Number(1.234) 2. Let /desc/ be the result of calling the [[GetProperty]] internal method of /O/ with property name /P/. desc := (new Number(1.234)).[[GetProperty]]("sin") (8.12.2) [[GetProperty]] (P) When the [[GetProperty]] internal method of /O/ is called with property name /P/, the following steps are taken: O := new Number(1.234) P := "sin" 1. Let /prop/ be the result of calling the [[GetOwnProperty]] internal method of /O/ with property name /P/. prop := (new Number(1.234))[[GetOwnProperty]]("sin") (8.12.1) [[GetOwnProperty]] (P) When the [[GetOwnProperty]] internal method of /O/ is called with property name /P/, the following steps are taken: 1. If /O/ doesn't have an own property with name /P/, return *undefined*. HasOwnProperty(new Number(1.234), "sin") == false --> return undefined --> prop := undefined 2. If /prop/ is not *undefined*, return /prop/. --> prop == undefined --> Continue. 3. Let /proto/ be the value of the [[Prototype]] internal property of /O/. --> proto := (new Number(1.234))[[Prototype]] --> proto := Number.prototype 4. If /proto/ is *null*, return *undefined*. --> proto != null --> Continue. 5. Return the result of calling the [[GetProperty]] internal method of /proto/ with argument /P/. --> return ( Number.prototype.[[GetProperty]]("sin")) 1. prop := Number.prototype[[GetOwnProperty]]("sin") 1. HasOwnProperty(Number.prototype, "sin") == true, continue. 2. D := PropertyDescriptor() 3. X := Property(Number.prototype, "sin") 4. X is a data property (section 8.6), so a. D.[[Value]] := X.[[Value]] b. D.[[Writeable]] := X.[[Writeable]] 5. Else ... (skipped accordingly) 6. D.[[Enumerable]] := X.[[Enumerable]] 7. D.[[Configurable]] := X.[[Configurable]] 8. Return D. --> return PropertyDescriptor( Number.prototype, "sin", attribs) --> desc := PropertyDescriptor( Number.prototype, "sin", attribs) 3. If /desc/ is undefined, return *undefined*. --> PropertyDescriptor(...) != undefined, continue. 4. If IsDataDescriptor(/desc/) is *true*, return desc.[[Value]]. --> IsDataDescriptor(PropertyDescriptor(...)) == true: (8.10.2) 1. PropertyDescriptor(Number.prototype, "sin", attribs) != undefined, continue. 2. PropertyDescriptor(Number.prototype, "sin", attribs).[[Value]] present, continue. 3. Return true. --> Return PropertyDescriptor(...).[[Value]], --> Return Number.prototype.sin. --> return Number.prototype.sin --> func := Number.prototype.sin (11.2.3) 3. Let /argList/ be the result of evaluating /Arguments/, producing an internal list of argument values (see 11.2.4). --> argList := List() 4. If Type(/func/) is not Object, throw a *TypeError* exception. --> Type(Number.prototype.sin) == Object, continue. 5. If IsCallable(/func/) is *false*, throw a *TypeError* exception. --> IsCallable(Number.prototype.sin) == true, continue. 6. If Type(/ref/) is Reference, then --> Type(Reference(base=1.234, name="sin", strict=false)) == Reference, so a. If IsPropertyReference(/ref/) is true, then --> IsPropertyReference( Reference(base=1.234, name="sin", strict=false)) == true, so i. Let thisValue be GetBase(/ref/). --> thisValue := GetBase(ref) --> thisValue := 1.234 b. Else ... (skipped accordingly) 7. Else ... (skipped accordingly) 8. Return the result of calling the [[Call]] internal method on /func/, providing /thisValue/ as the *this* value and providing the list /argList/ as the argument values. --> return (Number.prototype.sin.[[Call]]( this=1.234, arguments=List()) (13.2.1) [[Call]] When the [[Call]] internal method for a Function object /F/ is called with a *this* value and a list of arguments, the following steps are taken: 1. Let /funcCtx/ be the result of establishing a new execution context for function code using the value of /F/'s [[FormalParameters]] internal property, the passed arguments List /args/, and the *this* value as described in 10.4.3. --> funcCtx := ExecutionContext( formalParameters= Number.prototype.sin.[[FormalParameters]], arguments=List(), this=1.234) 2. Let result be the result of evaluating the /FunctionBody/ that is the value of /F/'s [[Code]] internal property. If /F/ does not have a [[Code]] internal property or if its value is an empty FunctionBody, then result is (normal, undefined, empty). --> result := Evaluate(Number.prototype.sin.[[Code]]) 3. Exit the execution context /funcCtx/, restoring the previous execution context. --> Exit(funcCtx), ... 4. If /result/.type is |throw| then throw /result/.value. --> result.type == normal, continue. 5. If /result/.type is |return| then return /result/.value. --> result.type == return --> return /result/.value --> return the equivalent of Number.prototype.sin.apply(1.234, argList) --> return Math.sin(+1.234) --> return Math.sin(1.234) What I found most interesting here is that the primitive value is converted to an object solely for the purpose of property lookup; and that the primitive value, _not_ a reference to that object becomes the `this' value of the called method. (IOW, if the `this' value of such a method is used repeatedly with property accessors, it could be more efficient to convert it to an object once explicitly, and then use the object reference instead.) I have also learned in the process that the more complex algorithms of ES5 do have a purpose beyond simply much a greater abstraction level; for example, they support the implementation of mutable property attributes like [[Enumerable]] -- which finally would allow such nice features like var o = {foo: "bar"}; /* shows "foo" */ for (var p in o) console.log(p); Object.defineProperty(o, "bar", {enumerable: false}); /* shows nothing */ for (p in o) console.log(p); (if they were implemented; not so in JavaScript 1.8.1 as of Firefox/Iceweasel 3.5.6 or any other widely distributed implementation¹) -- and facilitate making the difference between data properties and accessor properties, i.e. normal properties on the one hand, and properties with getters/setters on the other hand (already implemented there and elsewhere¹): var o = { _bar: "", get foo() { return +this._bar; }, set foo(x) { this._bar = String(+x + 19); } }; o.foo = "23"; /* shows 42 as a number */ console.log(o.foo); PointedEars ___________ ¹ <http://www.robertnyman.com/javascript/javascript-getters-setters.html> -- Anyone who slaps a 'this page is best viewed with Browser X' label on a Web page appears to be yearning for the bad old days, before the Web, when you had very little chance of reading a document written on another computer, another word processor, or another network. -- Tim Berners-Lee
From: Thomas 'PointedEars' Lahn on 17 Feb 2010 05:54 Richard Cornford wrote: > In ES3 the - this - keyword always refers to an object, so you are > guaranteed that it is never a primitive value. [...] ISTM that I have just proved this to be a common misconception. PointedEars -- var bugRiddenCrashPronePieceOfJunk = ( navigator.userAgent.indexOf('MSIE 5') != -1 && navigator.userAgent.indexOf('Mac') != -1 ) // Plone, register_function.js:16
From: Jorge on 17 Feb 2010 06:00 On Feb 17, 11:54 am, Thomas 'PointedEars' Lahn <PointedE...(a)web.de> wrote: > Richard Cornford wrote: > > In ES3 the - this - keyword always refers to an object, so you are > > guaranteed that it is never a primitive value. [...] > > ISTM that I have just proved this to be a common misconception. He said *In*ES3*: Number.prototype.test= function () { return typeof this; }; (1).test() --> "object" -- Jorge.
From: Thomas 'PointedEars' Lahn on 17 Feb 2010 06:12 Thomas 'PointedEars' Lahn wrote: > Richard Cornford wrote: >> In ES3 the - this - keyword always refers to an object, so you are >> guaranteed that it is never a primitive value. [...] > > ISTM that I have just proved this to be a common misconception. Please ignore this. In fact, my research¹ proves that you are correct for ES3(F), where the Reference value is computed using the conversion result as base; not so in ES5 (I wonder what the rationale was). PointedEars ___________ ¹ <news:1586094.GhzyNJHpIh(a)PointedEars.de> -- Use any version of Microsoft Frontpage to create your site. (This won't prevent people from viewing your source, but no one will want to steal it.) -- from <http://www.vortex-webdesign.com/help/hidesource.htm> (404-comp.)
From: Stefan Weiss on 17 Feb 2010 06:19
On 17/02/10 11:52, Thomas 'PointedEars' Lahn wrote: [snip impressive human-script-engine-mode text] > I have also learned in the process that the more complex algorithms of ES5 > do have a purpose beyond simply much a greater abstraction level; for > example, they support the implementation of mutable property attributes > like [[Enumerable]] -- which finally would allow such nice features like > > var o = {foo: "bar"}; > > /* shows "foo" */ > for (var p in o) console.log(p); > > Object.defineProperty(o, "bar", {enumerable: false}); > > /* shows nothing */ > for (p in o) console.log(p); Shouldn't this still show "foo"? -- stefan |