我想要:
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;
}
}
有好的替代品吗?
大多数答案使用某种鸭子类型,例如检查对象是否具有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
上面和下面的所有解决方案(包括我的解决方案)都有可能是不正确的,特别是在IE上——(重新)定义一些对象/方法/属性来模拟DOM节点,使测试无效是很可能的。
所以我通常使用duck-typing风格的测试:我专门测试我使用的东西。例如,如果我想克隆一个节点,我这样测试它:
if(typeof node == "object" && "nodeType" in node &&
node.nodeType === 1 && node.cloneNode){
// most probably this is a DOM node, we can clone it safely
clonedNode = node.cloneNode(false);
}
基本上,它是一个小的完整性检查+对我计划使用的方法(或属性)的直接测试。
顺便说一句,上面的测试对于所有浏览器上的DOM节点来说都是一个很好的测试。但是如果您想要安全起见,请始终检查方法和属性的存在并验证它们的类型。
编辑:IE使用ActiveX对象来表示节点,因此它们的属性行为不像真正的JavaScript对象,例如:
console.log(typeof node.cloneNode); // object
console.log(node.cloneNode instanceof Function); // false
而它应该分别返回“function”和true。测试方法的唯一方法是查看是否定义了方法。
var isElement = function(e){
try{
// if e is an element attached to the DOM, we trace its lineage and use native functions to confirm its pedigree
var a = [e], t, s, l = 0, h = document.getElementsByTagName('HEAD')[0], ht = document.getElementsByTagName('HTML')[0];
while(l!=document.body&&l!=h&&l.parentNode) l = a[a.push(l.parentNode)-1];
t = a[a.length-1];
s = document.createElement('SCRIPT'); // safe to place anywhere and it won't show up
while(a.length>1){ // assume the top node is an element for now...
var p = a.pop(),n = a[a.length-1];
p.insertBefore(s,n);
}
if(s.parentNode)s.parentNode.removeChild(s);
if(t!=document.body&&t!=h&&t!=ht)
// the top node is not attached to the document, so we don't have to worry about it resetting any dynamic media
// test the top node
document.createElement('DIV').appendChild(t).parentNode.removeChild(t);
return e;
}
catch(e){}
return null;
}
I tested this on Firefox, Safari, Chrome, Opera and IE9. I couldn't find a way to hack it.
In theory, it tests every ancestor of the proposed element, as well as the element itself, by inserting a script tag before it.
If its first ancestor traces back to a known element, such as <html>, <head> or <body>, and it hasn't thrown an error along the way, we have an element.
If the first ancestor is not attached to the document, we create an element and attempt to place the proposed element inside of it, (and then remove it from the new element).
So it either traces back to a known element, successfully attaches to a known element or fails.
It returns the element or null if it is not an element.