js节点的兼容性代码
1. 十二行基本代码
这十二行代码,都是获取节点或者元素的,但是后四组有兼容性的问题
// 获取父节点
console.log(ulObj.parentNode);
// 获取父元素
console.log(ulObj.parentElement);
// 获取子节点
console.log(ulObj.childNodes);
// 获取子元素
console.log(ulObj.children);
console.log("----------------");
// 获取第一个子节点
console.log(ulObj.firstChild); //IE8中是获取第一个子元素
// 获取第一个子元素
console.log(ulObj.firstElementChild); //IE8中不支持,undefined
// 获取最后一个子节点
console.log(ulObj.lastChild); //IE8中是获取最后一个子元素
// 获取最后一个子元素
console.log(ulObj.lastElementChild); //IE8中不支持,undefined
// 获取前一个兄弟节点
console.log(liObj.previousSibling); //IE8中是获取前一个兄弟元素
// 获取前一个兄弟元素
console.log(liObj.previousElementSibling); //IE8中不支持,undefined
// 获取后一个兄弟节点
console.log(liObj.nextSibling); //IE8中是获取后一个兄弟元素
// 获取后一个兄弟元素
console.log(liObj.nextElementSibling); //IE8中不支持,undefined
2. 别人的思路
为解决获取元素的兼容性问题,需要编写兼容性代码
在网上有看到这样的代码
http://bbs.miaov.com/forum.php?mod=viewthread&tid=6789&highlight=
function first(obj){
return obj.firstChild.nodeType == 1 ? obj.firstChild : next(obj.firstChild)
}
function last(obj){
return obj.lastChild.nodeType == 1 ? obj.lastChild : pre(obj.lastChild);
}
function next(obj){
return obj.nextSibling.nodeType == 1 ? obj.nextSibling : next(obj.nextSibling);
}
function pre(obj){
return obj.previousSibling.nodeType == 1 ? obj.previousSibling : pre(obj.previousSibling);
}
这个代码确实很简洁
思路也非常值得借鉴
不过在调试过程中,发现了一个BUG
先不说BUG,单讨论其中的思想
总的来讲,第一个孩子 最后一个孩子 前一个兄弟 后一个兄弟,会有兼容性问题
以获取第一个孩子元素为例
firstChild 得到的要么是第一个节点,要么是第一个标签
就这两种情况
如果获取到的是第一个标签
那么就返回
否则,就找第一个节点的下一个兄弟元素(就是第一个元素)
如此,便可以设计出以上的代码
3. 改进和优化
问题所在
在寻找兄弟元素的部分,使用了函数的嵌套调用
function next(obj){
return obj.nextSibling.nodeType == 1 ? obj.nextSibling : next(obj.nextSibling);
}
function pre(obj){
return obj.previousSibling.nodeType == 1 ? obj.previousSibling : pre(obj.previousSibling);
}
这里没有考虑函数嵌套调用的跳出条件
当obj.nextSibling.nodeType == 1始终不满足的时候
或者说没有下一个兄弟节点的时候
最终就会是(null).nodeType==1?
对null使用.nodeType,这样浏览器会报错
以下是个人改进版的代码:
/**
* 获取第一个孩子元素
* @param element 父节点,DOM对象
* @returns {ChildNode | (() => (Node | null)) | ActiveX.IXMLDOMNode} 返回的是获取到的孩子元素,没有找到,返回null
*/
function getFirstElementChild(element) {
return element.firstChild.nodeType == 1 ? element.firstChild : getNextElementSibling(element.firstChild);
}
/**
* 获取最后一个孩子元素
* @param element 父节点,DOM对象
* @returns {ActiveX.IXMLDOMNode | ChildNode | (() => (Node | null))} 返回的是获取到的孩子元素,没有找到,返回null
*/
function getLastElementChild(element) {
return element.lastChild.nodeType == 1 ? element.lastChild : getPreviousElementSibling(element.lastChild);
}
/**
* 获取后一个兄弟元素
* @param element element 当前节点,DOM对象
* @returns {*} 返回的是获取到的后一个兄弟元素,没有后一个兄弟节点,返回null
*/
function getNextElementSibling(element) {
// 如果没有下一个兄弟节点,那么直接返回null,否则对null进行nodeType,会报错
if (element.nextSibling == null)
return null;
return element.nextSibling.nodeType == 1 ? element.nextSibling : getNextElementSibling(element.nextSibling)
}
/**
* 获取前一个兄弟元素
* @param element element 当前节点,DOM对象
* @returns {*} 返回的是获取到的前一个兄弟元素,没有前一个兄弟节点,返回null
*/
function getPreviousElementSibling(element) {
// 如果没有前一个兄弟节点,那么直接返回null,否则对null进行nodeType,会报错
if (element.previousSibling == null)
return null;
return element.previousSibling.nodeType == 1 ? element.previousSibling : getPreviousElementSibling(element.previousSibling)
}
/**
* 获取元素所有的兄弟元素(包括它本身)
* @param element 待使用元素
* @returns {Array} 返回一个数组,包含所有兄弟元素
*/
function getAllElementsSibling(element) {
var parNode = element.parentNode;
// 用最后一个孩子,然后向上遍历也可以
var currentNode = getFirstElementChild(parNode);
var allSiblings = [];
for (var i = 0; currentNode; i++) {
allSiblings[i] = currentNode;
currentNode = getNextElementSibling(currentNode);
}
return allSiblings;
}
4. 小结
碰到函数的嵌套调用,要考虑函数的跳出条件,否则很危险