Thomas said:
So there is a false positive, but it is a false positive that is caused by a
wrong premise of the caller. `document.images' is not supposed to be
subject to this feature test as it does not need to be called, and so its
callability is not an issue.
I'm not quite sure what the "right premise" is, but in this case, it
seems like it's "the property needs to be called".
In contrast, the `in' operation makes exactly *no* statement about the
callability of the subject, in the best case it can only state its existence
(that is, either the object has this property or it is inherited through the
prototype chain). And it does that in a more error-prone and less
compatible way (cf. ES3F, sections 11.4.3 and 11.8.7, and the ECMAScript
Support Matrix).
You said the 'in' operation makes exactly *no* statement about the
callability of the subject. Right you are.
Neither |in| nor isHostMethod method determine callability. isHostMethod
rules out the possibility that the property value is primitive but that
is irrelevant in most contexts.
TheisHostMethod logic is as presumputous as an inference made after
checking presence with |in|; kist because the browser reports "unknown"
or "object" (as I showed in my document.images example) the property is
assumed to be callable. And the name "isHostMethod" is presumptuously
misleading.
The typeof a host object property can be *anything*. Method isHostMethod
is based on the result of typeof, and therefore is based on nothing more
than informed assumptions.
Both require presupposition.
Both |in| and |typeof| check the property in the prototype chain, so
that cancels out, as far as comparisons go.
Regarding the comment on |in| being more error-prone: |in| throws a
TypeError if the right hand operand is not an object[ex1], while typeof
throws when the property is null or undefined[ex2]. That makes typeof
less error prone when the operand is number, string, or boolean but that
is irrelevant in most cases. That is some of thinking that went into it.
Reducing the number of things that will pass isHostMethod does not
remove the need to make a reasonable inference about the property.
Making a reasonable inference is what is important. Code that uses |in|
clearly shows that the code makes an inference, isHostMethod is
presumptuous.
Most situations in context of dealing with host objects could get away
with: - if(!obj) - and be confident that the obj would never be a
number, string, or boolean[ex3]. So, in context, a strategy using |in|
would not be more error-prone.
A reasonable strategy for determining if a property is a method, while
ensuring that the would-be object is not a primitive is to first do
that: Make sure the base object is not a primitive. Inferences and
simple checks can be made in cases when dealing with host objects[ex3].
The strategy for determining callability is to make reasonable
assumptions about an object property. I do this *all the time*. For
example, code such as:-
window.alert("test").
- I make the assumptions that |window| is A) a browser window object
with B) an alert property that is C) callable.
Three assumptions.
==
Examples and Commentary:
ex1, TypeError on |in| and |typeof|:
// produces an error.
var aPrimitive = 2;
"toString" in aPrimitive
// no error with typeof
typeof aPrimitive.toString
I see no reason to use |in| on a number. The only plausible situation of
using prop in aPrimitive *by mistake* would be where aPrimitive ===
undefined or aPrimitive === null . IN that case, TypeError occurs with
typeof:-
ex2:
var aPrimitive;
typeof aPrimitive.toString; // => Error.
aPrimitive = null;
typeof aPrimitive.toString; // => Error.
// Error is avoided by a simple check of aPrimitive
if(aPrimitive && "toString" in aPrimitive) {
}
[ex3]
// throws error if el is null/undefined.
function removeNode(el) {
var p = el.parentNode;
// p might be null or undefined, or an object,
// but should never be number string, or boolean.
if(p != null) {
if("removeChild" in p) {
p.removeChild(el);
}
}
}
Downsides to in:
|in| is a more recent addition to ECMAScript. It is less compatible. It
doesn't work in Mac IE or Netscape 4. For compatibility with these
browsers, use typeof and test accordingly.
Downsides to isHostMethod:
The typeof operator used on a host object returns an
implementation-dependent string. To account for the various results in
implementations, the user-defined wrapper isHostMethod was created.
A reasonable expectation of a method with the name "isHostMethod" would
be that if it returns true, the property is a method of a Host object.
That is not what isHostMethod does. The name is misleading.
isPossibleHostMethod is more apt.
Calling a user-defined method contributes to the size of a codebase.
Calling said method is slower than a native |in| operation. Should the
program require debugging, that is one more method to step into. Less is
more.
Neither approach determines callability. Both require presupposition
that can and that should be based on reasonable inferences. For example,
a reasonable inference would be knowing that if an "addEventListener" is
present on the object, that it is callable, as no known implementations
have an addEventListener property that behaves otherwise.
Using direct type conversion is error prone, as some host objects throw
errors upon type conversion.
Both the |in| operator and the typeof operator provides the programmer
with a way to check for the existence of a property while avoiding type
conversion.
The typeof operator mostly works.
The |in| operator mostly works, too.
Both have edge cases that do not work.
In most cases, |in| is the simplest thing that could possibly work.
To determine callability, ES5 provides new behavior for typeof:
| ES5 (11.4.3) requires that both native (ie, pure actual ECMAScript
| objects) and host objects that implement [[Call]] produce "function"
| when typeof is applied to them.
- Allen Wirfs-Brock.
Until ES5 rolls out, the best known approach is to make reasonable
inferences about an object's interface, specifically, what the presence
of property |p| on object |o| in a given context.
Garrett