我想要:
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;
}
}
有好的替代品吗?
根据mdn
Element是Document中所有对象都继承的最通用基类。它只有对所有类型的元素通用的方法和属性。
我们可以通过原型实现isElement。以下是我的建议:
/**
* @description detect if obj is an element
* @param {*} obj
* @returns {Boolean}
* @example
* see below
*/
function isElement(obj) {
if (typeof obj !== 'object') {
return false
}
let prototypeStr, prototype
do {
prototype = Object.getPrototypeOf(obj)
// to work in iframe
prototypeStr = Object.prototype.toString.call(prototype)
// '[object Document]' is used to detect document
if (
prototypeStr === '[object Element]' ||
prototypeStr === '[object Document]'
) {
return true
}
obj = prototype
// null is the terminal of object
} while (prototype !== null)
return false
}
console.log(isElement(document)) // true
console.log(isElement(document.documentElement)) // true
console.log(isElement(document.body)) // true
console.log(isElement(document.getElementsByTagName('svg')[0])) // true or false, decided by whether there is svg element
console.log(isElement(document.getElementsByTagName('svg'))) // false
console.log(isElement(document.createDocumentFragment())) // false
这是我想出来的:
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。当然,这不是很可靠,但绝大多数用户甚至不应该倒退到这么远。
这是我想到的最可靠的方法,同时还能保持尽可能高的性能。
我有一个特殊的方法来做到这一点,在答案中还没有提到。
我的解决方案基于四个测试。如果对象通过了这四个,那么它就是一个元素:
该对象不是空的。
对象有一个叫做“appendChild”的方法。
方法“appendChild”继承自Node类,而不仅仅是一个冒名方法(具有相同名称的用户创建的属性)。
对象类型为Node Type 1 (Element)。从Node类继承方法的对象总是Node,但不一定是element。
问:如何检查给定的属性是否是继承的,而不是冒名顶替的?
答:要判断一个方法是否真正继承自Node,一个简单的测试是首先验证该属性是否具有“对象”或“函数”类型。接下来,将属性转换为字符串,并检查结果是否包含文本“[Native Code]”。如果结果看起来像这样:
function appendChild(){
[Native Code]
}
然后从Node对象继承了该方法。参见https://davidwalsh.name/detect-native-function
最后,把所有的测试放在一起,解决方案是:
function ObjectIsElement(obj) {
var IsElem = true;
if (obj == null) {
IsElem = false;
} else if (typeof(obj.appendChild) != "object" && typeof(obj.appendChild) != "function") {
//IE8 and below returns "object" when getting the type of a function, IE9+ returns "function"
IsElem = false;
} else if ((obj.appendChild + '').replace(/[\r\n\t\b\f\v\xC2\xA0\x00-\x1F\x7F-\x9F ]/ig, '').search(/\{\[NativeCode]}$/i) == -1) {
IsElem = false;
} else if (obj.nodeType != 1) {
IsElem = false;
}
return IsElem;
}
上面和下面的所有解决方案(包括我的解决方案)都有可能是不正确的,特别是在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。测试方法的唯一方法是查看是否定义了方法。