kangax said:
Thomas said:
Cody said:
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>