我想要:
document.createElement('div') //=> true
{tagName: 'foobar something'} //=> false
在我自己的脚本中,我曾经只使用这个,因为我从来不需要tagName作为属性:
if (!object.tagName) throw ...;
所以对于第二个目标,我想出了下面的快速解决方案——这基本上是有效的。;)
问题是,它依赖于浏览器强制执行只读属性,而并非所有浏览器都这样做。
function isDOM(obj) {
var tag = obj.tagName;
try {
obj.tagName = ''; // Read-only for DOM, should throw exception
obj.tagName = tag; // Restore for normal objects
return false;
} catch (e) {
return true;
}
}
有好的替代品吗?
这是我想出来的:
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";
}
}
})();
为了提高性能,我创建了一个自调用函数,它只测试浏览器的功能一次,并相应地分配适当的函数。
第一个测试应该可以在大多数现代浏览器中工作,这里已经讨论过了。它只是测试元素是否是HTMLElement的实例。非常简单。
第二个是最有趣的一个。这是它的核心功能:
return el instanceof (document.createElement(el.nodeName)).constructor
它测试el是构造函数的实例还是它假装是实例。为此,我们需要访问元素的构造函数。这就是为什么我们在if-Statement中测试这个。例如,IE7就失败了,因为(document.createElement("a"))。构造函数在IE7中未定义。
The problem with this approach is that document.createElement is really not the fastest function and could easily slow down your application if you're testing a lot of elements with it. To solve this, I decided to cache the constructors. The object ElementConstructors has nodeNames as keys with its corresponding constructors as values. If a constructor is already cached, it uses it from the cache, otherwise it creates the Element, caches its constructor for future access and then tests against it.
第三个考验是令人不快的退路。它测试el是否是一个对象,是否有一个nodeType属性设置为1,是否有一个字符串作为nodeName。当然,这不是很可靠,但绝大多数用户甚至不应该倒退到这么远。
这是我想到的最可靠的方法,同时还能保持尽可能高的性能。
大多数答案使用某种鸭子类型,例如检查对象是否具有nodeType属性。但这还不够,因为非节点也可以具有类节点属性。
另一种常见的方法是instanceof,它会产生误报,例如Object.create(Node),尽管继承了节点属性,但它不是节点。
此外,上述两种方法都调用内部基本方法,这可能会有问题,例如,如果测试的值是一个代理。
相反,我建议借用一个节点方法并在我们的对象上调用它。浏览器可能会通过查看代理中不可自定义的内部槽来检查该值是否是一个节点,因此即使它们也无法干扰我们的检查。
function isNode(value) {
try {
Node.prototype.cloneNode.call(value, false);
return true;
} catch(err) {
return false;
}
}
如果您愿意,还可以使用属性getter。
函数isNode(value) {
尝试{
Object.getOwnPropertyDescriptor (Node.prototype nodeType) .get.call(价值);
返回true;
} catch(err) {
返回错误;
}
}
类似地,如果您想测试一个值是否是一个元素,您可以使用
function isElement(value) {
try {
Element.prototype.getAttribute.call(value, '');
return true;
} catch(err) {
return false;
}
}
function isHTMLElement(value) {
try {
HTMLElement.prototype.click.call(value);
return true;
} catch(err) {
return false;
}
}
也许这是另一种选择?在Opera 11, FireFox 6, Internet Explorer 8, Safari 5和谷歌Chrome 16中测试。
function isDOMNode(v) {
if ( v===null ) return false;
if ( typeof v!=='object' ) return false;
if ( !('nodeName' in v) ) return false;
var nn = v.nodeName;
try {
// DOM node property nodeName is readonly.
// Most browsers throws an error...
v.nodeName = 'is readonly?';
} catch (e) {
// ... indicating v is a DOM node ...
return true;
}
// ...but others silently ignore the attempt to set the nodeName.
if ( v.nodeName===nn ) return true;
// Property nodeName set (and reset) - v is not a DOM node.
v.nodeName = nn;
return false;
}
函数不会被例如这个所欺骗
isDOMNode( {'nodeName':'fake'} ); // returns false