Isto é o que eu descobri:
var isHTMLElement = (function () {
if ("HTMLElement" in window) {
// Voilà. Quick and easy. And reliable.
return function (el) {return el instanceof HTMLElement;};
} else if ((document.createElement("a")).constructor) {
// We can access an element's constructor. So, this is not IE7
var ElementConstructors = {}, nodeName;
return function (el) {
return el && typeof el.nodeName === "string" &&
(el instanceof ((nodeName = el.nodeName.toLowerCase()) in ElementConstructors
? ElementConstructors[nodeName]
: (ElementConstructors[nodeName] = (document.createElement(nodeName)).constructor)))
}
} else {
// Not that reliable, but we don't seem to have another choice. Probably IE7
return function (el) {
return typeof el === "object" && el.nodeType === 1 && typeof el.nodeName === "string";
}
}
})();
Para melhorar o desempenho, criei uma função auto-invocadora que testa os recursos do navegador apenas uma vez e atribui a função apropriada de acordo.
O primeiro teste deve funcionar nos navegadores mais modernos e já foi discutido aqui. Apenas testa se o elemento é uma instância de HTMLElement. Muito simples.
O segundo é o mais interessante. Esta é sua principal funcionalidade:
return el instanceof (document.createElement(el.nodeName)).constructor
Ele testa se el é uma instância do construtor que finge ser. Para fazer isso, precisamos acessar o contratador de um elemento. É por isso que estamos testando isso na instrução if. O IE7, por exemplo, falha com isso, porque (document.createElement("a")).constructorestá undefinedno IE7.
O problema com essa abordagem é que ela document.createElementrealmente não é a função mais rápida e pode desacelerar facilmente seu aplicativo se você estiver testando vários elementos com ele. Para resolver isso, decidi armazenar em cache os construtores. O objeto ElementConstructorspossui nodeNames como chaves e seus construtores correspondentes como valores. Se um construtor já estiver armazenado em cache, ele o usará do cache; caso contrário, ele criará o Elemento, armazenará em cache seu construtor para acesso futuro e, em seguida, testará contra ele.
O terceiro teste é o fallback desagradável. Ele testa se el é um object, tem uma nodeTypepropriedade definida como 1e uma sequência como nodeName. Isso não é muito confiável, é claro, mas a grande maioria dos usuários nem deveria voltar tão longe.
Essa é a abordagem mais confiável que eu criei, mantendo o desempenho o mais alto possível.