你所不了解的javascript操作DOM的细节知识点(一)


Posted in Javascript onJune 17, 2015

一:Node类型

DOM1级定义了一个Node接口,该接口是由DOM中的所有节点类型实现。每个节点都有一个nodeType属性,用于表明节点的类型,节点类型在Node类型中有以下几种:

Node.ELEMENT_NODE(1); 元素节点
Node.ATTRIBUTE_NODE(2); 属性节点
Node.TEXT_NODE(3); 文本节点
Node.DOCUMENT_NODE(9); 文档节点

其实还有很多种,但是那些都不是很常用,所以就来理解这其中4种就可以了,我们先来看看节点类型,比如如下代码:

HTML代码如下:

<div id="test">
 <p>aaaaaa</p>
 <p>bbbbbb</p>
 <p>cccccc</p>
</div>

JS如下:

var test = document.getElementById("test");
if(test.nodeType === Node.ELEMENT_NODE) {
 alert(1)
}

如上代码在IE8及以下下是不行的,会报错,如下:

你所不了解的javascript操作DOM的细节知识点(一)

因为IE没有公开Node类型的构造函数,因此在IE8-下会有错误,但是我们可以通过数值来比较,比如上面的要比较元素节点的话,我们可以使用1来比较,同理属性节点是2,文本节点是3;如下代码:

var test = document.getElementById("test");
// 下面的所有的浏览器都支持
if(test.nodeType == 1) { 
  alert(1)
}

理解nodeName与nodeValue

nodeName保存的是元素的标签名,而nodeValue一般都是null;我们可以看如下代码,没有特殊的说明,HTML代码都是上面的,因此这边就不贴代码了;如下JS代码测试:

var test = document.getElementById("test");
if(test.nodeType == 1) {
 console.log(test.nodeName); // 打印DIV
 console.log(test.nodeValue); // 打印null
}

理解节点关系

<!doctype html>
<html lang="en">
 <head>
 <meta charset="UTF-8">
 <title>Document</title>
 </head>
 <body>
</body>
</html>

如上代码,我们可以把head和body是html的子元素,同理html是他们的父级元素,那么head和body就是兄弟元素了,那么head及body里面的就是子元素了,我们需要明白的是每个节点都有一个childNodes属性,其保存的是类似数组的元素,其也有length属性,但是他不是数组Array的实例,比如我们可以看看如下测试代码即可:

<div id="test">
 <p>aaaaaa</p>
 <p>bbbbbb</p>
 <p>cccccc</p>
</div>

JS代码如下:

var test = document.getElementById("test");
if(test.nodeType == 1) {
 console.log(test.childNodes);
 console.log(test.childNodes.length);
}

如上代码,在标准浏览器下及IE9+下 第一行打印如下:

[text, p, text, p, text, p, text, item: function]

第二行打印7 长度为7,因为他们把文本节点那个空格也包括进去了,但是在IE8及以下,长度为3,他们不包括文本空格的节点,因此想要统一的话,我们可以编写HTML代码去掉空格,如下HTML代码即可;

<div id="test"><p>aaaaaa</p><p>bbbbbb</p><p>cccccc</p></div>

这个问题稍后在仔细考虑,我们现在来看看如何取得子元素,我们可以使用2种方法,第一种是使用中括号[index]索引,第二种是使用item[index]索引,如下代码:

console.log(test.childNodes[1]); // <p>bbbbbb</p>
console.log(test.childNodes.item(1)); // <p>bbbbbb</p>

但是他们并不是数组,我们可以测试下代码即可,如下代码:

console.log(Object.prototype.toString.call(test.childNodes) === "[object Array]");
// false但是我们使其转换为数组,如下代码:

//在IE8 及之前版本中无效
var arrayOfNodes = Array.prototype.slice.call(test.childNodes,0);
console.log(arrayOfNodes instanceof Array);

// true不过在IE8及之前不生效;由于IE8及更早版本将NodeList实现为一个COM对象,而我们不能像使用JScript对象那样使用对象,要想在IE低版本中转换为Array的形式,我们可以像下面一样封装一个方法即可;

function convertToArray(nodes){
 var array = null;
 try {
  array = Array.prototype.slice.call(nodes, 0); //针对非IE 浏览器
 } catch (ex) {
  array = new Array();
  for (var i=0, len=nodes.length; i < len; i++){
   array.push(nodes[i]);
  }
 }
 return array;
}
var test = document.getElementById("test");
var testArray = convertToArray(test.childNodes);
console.log(testArray instanceof Array); // true

理解parentNode(父节点),previousSibling(上一个兄弟节点),nextSibling(下一个兄弟节点);

每个节点都有一个parentNode属性,该属性指向文档中父节点,previousSibling是指当前节点的上一个同胞节点,nextSibling是指当前节点的下一个同胞节点,比如如下代码:

<div id="test"><p>aaaaaa</p><p>bbbbbb</p><p>cccccc</p></div>
var test = document.getElementById("test");
if(test.nodeType == 1) {
 var secodeChild = test.childNodes[1];
 console.log(secodeChild); // <p>bbbbbb</p>
 console.log(secodeChild.previousSibling); // <p>aaaaaa</p>
 console.log(secodeChild.nextSibling); // <p>cccccc</p>
}

如果该节点列表中只有一个节点的话,那么该节点的previousSibling和nextSibling都为null;父节点的firstChild指向了父节点中第一个节点;如下代码:

<div id="test"><p class="a">aaaaaa</p><p class="b">bbbbbb</p><p class="c">cccccc</p></div>

JS如下:

var test = document.getElementById("test");
if(test.nodeType == 1) { 
 console.log(test.firstChild); // <p class="a">aaaaaa</p>
 console.log(test.lastChild); // <p class="c">cccccc</p>
}

父节点的firstChild始终等于父节点的.childNodes[0],父节点的lastChild始终等于父节点的.childNodes[父节点的.childNodes.length - 1]; 如下代码:

console.log(test.firstChild === test.childNodes[0]); // true
console.log(test.lastChild === test.childNodes[test.childNodes.length - 1]); // true

如果没有子节点的话,那么firstChild和lastChild都指向为空null;

hasChildNodes():如果需要判断该父节点有没有子节点的话,可以使用该方法判断,返回的是一个布尔型,有返回true,没有返回false,如下代码:

<div id="test"><p class="a">aaaaaa</p><p class="b">bbbbbb</p><p class="c">cccccc</p></div>

JS代码如下:

var test = document.getElementById("test");
console.log(test.hasChildNodes());

// true如果是如下的 就返回false;如下代码:

<div id="test"></div>
var test = document.getElementById("test");
console.log(test.hasChildNodes()); // false

ownerDocument: 所有节点都有最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点,这种关系表示的任何节点都属于它所在的文档,任何节点都不能同时存在两个或更多文档中,通过这个属性,我们可以不必在节点层次中通过层层回溯到达顶端,而是可以直接访问文档节点;如下测试代码:

<div id="test">
 <p class="a">11</p>
</div>
var test = document.getElementById("test");
console.log(test.ownerDocument); // document
var p = test.ownerDocument.getElementsByTagName("p");
console.log(p); // <p class="a">11</p>

appendChild(): 用于向childNodes列表的末尾添加一个节点;返回的是新增加的节点;如下代码:

<div id="test">
<p class="a">11</p>
</div>

JS代码如下:

var test = document.getElementById("test");
var newNode = document.createElement("p");
var returnNode = test.appendChild(newNode);
console.log(returnNode); // <p></p>
console.log(returnNode === newNode); // true
console.log(test.lastChild === newNode); // true

insertBefore(): 该方法是将新节点插入到指定的节点的前面去,该方法接收2个参数,要插入的节点和作为参照的节点;插入节点后,被插入的节点会变成参照节点的前一个同胞节点,同时被方法返回,如下代码:

<div id="test">
<p class="a">11</p>
</div>

JS代码如下:

var test = document.getElementById("test");
var newNode = document.createElement("div");
var returnNode = test.insertBefore(newNode,test.childNodes[0]);
console.log(returnNode); // <div></div>
console.log(returnNode === newNode); // true

插入节点后,结构变成如下:

<div id="test">

<div></div>

<p class="a">11</p>

</div>

但是如果参照节点为null的话,那么就会把新节点插入到最后面去了,如下代码:

var test = document.getElementById("test");

var newNode = document.createElement("div");

var returnNode = test.insertBefore(newNode,null);

插入后HTML结构如下:
<div id="test">

    <p class="a">11</p>

    <div></div>

</div>

我们还可以更深入的看下如下测试代码:

var test = document.getElementById("test");
var newNode = document.createElement("div");
// 插入后成为最后一个节点
var returnNode = test.insertBefore(newNode,null);
console.log(returnNode === test.lastChild); // true 
// 插入后成为第一个节点
var returnNode = test.insertBefore(newNode,test.firstChild);
console.log(returnNode === newNode); // true
console.log(newNode === test.firstChild); // true
// 插入到最后一个子节点的前面
var returnNode = test.insertBefore(newNode,test.lastChild);
console.log(returnNode === test.childNodes[test.childNodes.length - 2]); // true

replaceChild(); 该方法接收2个参数,要插入的节点和要替换的节点,要替换的节点将由这个方法返回并从文档树中被移除,同时由插入的节点占据其位置,如下代码:

<div id="test"><p class="a">11</p><p class="b">22</p></div>

JS代码如下:

var test = document.getElementById("test");
var newNode = document.createElement("div");
// 替换第一个节点
var returnNode = test.replaceChild(newNode,test.firstChild);
console.log(returnNode); // <p class="a">11</p>

替换后html代码结构变为如下:

<div id="test"><div></div><p class="b">22</p></div>

替换最后一个节点代码如下:

var test = document.getElementById("test");
var newNode = document.createElement("div");
// 替换最后一个节点
var returnNode = test.replaceChild(newNode,test.lastChild);
console.log(returnNode); // <p class="b">22</p>

替换后的代码如下:

<div id="test"><p class="a">11</p><div></div></div>removeChild():移除节点,该方法接收一个参数,即要移除的节点;

被移除的节点将成为返回值,如下代码:

var test = document.getElementById("test");
var newNode = document.createElement("div");
// 移除第一个节点
var returnNode = test.removeChild(test.firstChild);
console.log(returnNode); // <p class="a">11</p>

移除后的代码结构变为如下:

<div id="test"><p class="b">22</p></div>

移除最后一个节点的代码如下:

// 移除最后一个节点
var returnNode = test.removeChild(test.lastChild);
console.log(returnNode); // <p class="b">22</p>
移除后的代码结构变为如下:

<div id="test"><p class="a">11</p></div>cloneNode():

克隆一个相同的副本,该方法接收一个布尔值参数,如果为true的话,说明是深复制,复制该节点及整个子节点书,如果为false的话,只复制该节点本身,比如如下代码:

var test = document.getElementById("test");
var deeplist = test.cloneNode(true);
console.log(deeplist);
// <div id="test"><p class="a">11</p><p class="b">22</p></div>
如果是浅复制的如下代码:
var test = document.getElementById("test");
var deeplist = test.cloneNode(false);
console.log(deeplist);// <div id="test"></div>

Document类型

JS通过Document类型表示文档,document继承HTMLDocument(继承自Document)类型的一个实例,表示整个HTML页面。document对象是window的一个属性,所以可以通过全局对象来访问,document对象有如下特点:

nodeType值为9,
nodeName值为”#document”,
nodeValue值为null,
parentNode值为null,
ownerDocument值为null
理解文档document的子节点
documentElement: 该属性始终指向html元素,如下代码:
console.log(document.documentElement); // 指向html的引用
如果需要直接访问html元素的引用可以直接使用该元素更快,更方便。
childNodes: 通过childNodes列表访问文档元素;如下测试代码:

console.log(document.documentElement); // 指向html的引用
console.log(document.childNodes[0]); // <!doctype html>
console.log(document.firstChild); // <!doctype html>
console.log(document.firstChild === document.childNodes[0]); // true
console.log(document.body); // 指向body的引用

所有的浏览器都支持document.documentElement 和 document.body属性;

title:  通过这个属性可以取得网页的标题,如下测试代码:

console.log(document.title);

也可以该方法设置网页的标题;如下代码:

document.title = “我是龙恩”;

URL:  取得页面中完整的url,如下代码测试:

console.log(document.URL); // http://127.0.0.1/dom/dom.html

domain: 该属性包含页面的域名;如下测试代码:

console.log(document.domain); // 127.0.0.1

域名也可以设置的,比如常见的跨子域的情况下,我们需要设置相同的父域即可完成跨域;

namedItem(): 该方法可以通过元素的name特性取得集合中的项,比如现在页面上有很多input的框,我想通过他们的name属性取得自己想要的哪一项,测试代码如下:

HTML代码如下:

<input name="aa"/>
<input name="bb"/>

JS代码如下:

var inputs = document.getElementsByTagName("input");
console.log(inputs.namedItem("aa")); // <input name="aa"/>

我们还可以通过方括号的语法来访问,如下代码:

console.log(inputs["aa"]); // <input name="aa"/>

要想取得文档中所有的元素,可以向getElementsByTagName()中传入”*”; 星号表示全部的意思;

getElementsByName(); 该方法也是HTMLDocument类型才有的方法,这个方法会返回带有给定name特性的所有元素,如下测试代码:

<input name="aa" type="radio"/>
<input name="aa" type="radio"/>
var aa = document.getElementsByName("aa");
console.log(aa); // object
console.log(aa.length); // 2

该方法也会返回一个HTMLCollection.

浏览器支持程度IE,firefox,chrome都支持;

Element类型

Element节点有以下特征:

nodeType的值为1;

nodeName的值为元素的标签名;

nodeValue的值为null;

parentNode可能是Document或Element

要访问元素的标签名,可以使用nodeName属性,也可以使用tagName属性,这两个属性会返回相同的值,如下代码:

<div id="test"></div>
var test = document.getElementById("test");
console.log(test.tagName); // DIV
console.log(test.nodeName); // DIV
console.log(test.nodeValue); // null;
console.log(test.parentNode); // body
console.log(test.nodeType); // 1

理解HTML元素中的获取属性的方法

所有HTML元素都由HTMLElement类型表示,HTMLElement类型直接继承Element;如果想要取得HTML元素的特性的话,有下面几个方法:

getAttribute(); 获取元素的属性的方法;如下测试代码:

<div id="test" class="testName" title="aa"></div>
var test = document.getElementById("test");
console.log(test.getAttribute("id")); // test
console.log(test.getAttribute("class")); // testName
console.log(test.getAttribute("title")); // aa

注意:上面的获取类名在IE8+及标准浏览器下都是使用获取属性class来取得类名,上面的如果在IE7下会返回null, 但是在IE7及以下是使用className这个属性来获取的;如下代码:在IE7下测试:

console.log(test.getAttribute("className")); // testName

当然也可以取得自定义的属性;如下代码:

<div id="test" data-value="test1"></div>

console.log(test.getAttribute("data-value")); // test1

setAttribute() 设置元素的属性;该方法接收2个参数,第一个参数是属性名称,第二个参数是对应的值;如果该值存在,则会以现在的值替换之前的值,如果属性名不存在,则会创建该属性,并指定该属性的值;如下代码:

<div id="test"></div>
// JS代码如下:
var test = document.getElementById("test");
test.setAttribute("id",'test');
test.setAttribute("class","testName");
test.setAttribute("title","aa");
test.setAttribute("data-value","test1");

生成HTML结构如下:

<div id="test" class="testName" title="aa" data-value="test1"></div>

removeAttribute(); 删除元素的特性;如下代码:

<div id="test" class="testName" title="aa" data-value="test1"></div>

JS代码如下:

var test = document.getElementById("test");
test.removeAttribute("id");
test.removeAttribute("class");
test.removeAttribute("title");
test.removeAttribute("data-value");

IE7及以下删除类名需要使用className;

attributes属性

Element类型是使用attributes属性的唯一一个dom节点类型,attributes属性包含一个NamedNodeMap集合;该对象有以下方法:

getNamedItem(name): 返回nodeName属性等于name的节点;

attributes属性中包含一系列节点,每个节点的nodeName就是特性的名称,而节点的nodeValue就是节点值,要取得元素id的特性,如下代码:

<div id="test" class="testName" title="aa" data-value="test1"></div>JS代码如下:

var div = document.getElementById("test");

var id = div.attributes.getNamedItem("id").nodeValue;

console.log(id);  // test

我们也可以通过中括号的语法来获取的,如下代码:

var id = div.attributes['id'].nodeValue;

console.log(id); // test

同样我们也可以通过上面介绍的getAttribute(“id”)方法来获取元素,如下代码:

console.log(div.getAttribute("id")); // test

也可以通过以下方法给元素设置id,如下方法:

div.attributes["id"].nodeValue = "aa";

removeNamedItem(name): 从列表中移除nodeName属性等于name的节点;

调用removeNamedItem()方法与在元素上调用removeAttribute()方法的效果相同,是直接删除具有给定名称的特性。他们之间的区别是:removeNamedItem()方法返回表示被删除特性的节点;

如下代码:

<div id="test" class="testName" title="aa" data-value="test1"></div>

JS代码如下:

var div = document.getElementById("test");

var oldAttr = div.attributes.removeNamedItem("id");

console.log(oldAttr); // 返回是被删除的节点  id="test"

现在的html结构变成如下:

<div class="testName" title="aa" data-value="test1"></div>

如上方法:目前IE,firefox,chrome浏览器都支持;

但是上面我们有getAttribute,setAttribute(),removeAttribute()方法,我们可以使用这些方法足够代替上面介绍的几个方法,但是如果想遍历一个元素上有多少个属性时,attributes这个就可以派上用上了,如下代码:

HTML代码如下:

<div id="test" class="testName" title="aa" data-value="test1"></div>

JS代码如下:

var div = document.getElementById("test");
function outputAttributes(element) {
 var arrs = new Array();
 var attrName,
   attrValue,
  i,
  len;
 for(i = 0, len = element.attributes.length; i < len; i++) {
  attrName = element.attributes[i].nodeName;
  attrValue = element.attributes[i].nodeValue;
  arrs.push(attrName + "='" + attrValue + "'");
 }
 return arrs.join(" ");
}
console.log(outputAttributes(div));

结果打印如下:

id='test' class='testName' title='aa' data-value='test1'

上面的函数使用一个数组来保存名值对,最后再以空格为分隔符将它们拼接起来,但是上面的代码我们需要注意两点:

1.针对attributes对象中的特性,不同的浏览器返回的顺序不同。
2.IE7及更早的版本会返回HTML元素中所有可能的特性,包括没有指定的特性;
针对IE7的问题我们可以进行改进,我们都知道,每个特性节点都有一个名为specified的属性,这个属性值如果为true,则意味着要么在HTML中指定了相应的特性,要么通过setAttribute方法设置了该特性,在IE中,所有为设置过的特性该属性的之都为false;如下代码:

var div = document.getElementById("test");
function outputAttributes(element) {
 var arrs = new Array();
 var attrName,
  attrValue,
 i,
 len;
 for(i = 0, len = element.attributes.length; i < len; i++) {
  attrName = element.attributes[i].nodeName;
  attrValue = element.attributes[i].nodeValue;
  if(element.attributes[i].specified) {
    arrs.push(attrName + "='" + attrValue + "'");
  }
 }
 return arrs.join(" ");
}
console.log(outputAttributes(div));

理解元素的子节点

元素可以有任意数目的子节点和后代节点,元素的childNodes属性中它包含了它的所有子节点,这些子节点可能是元素,文本节点,注释或处理指令;不同的浏览器子节点的个数不一样,下面我们来看看demo如下:

<ul id="list">
 <li>11</li>
 <li>22</li>
 <li>33</li>
</ul>

JS代码如下:

var list = document.getElementById("list");

console.log(list.childNodes);

console.log(list.childNodes.length);

我们之前在上面讲过,在标准浏览器下(IE9+,firefox,chrome)下,打印的长度是7个,因为他们把空格也包含上去,在IE8-及以下的浏览器返回的是3个子元素,这是表现的正常情况下,但是如果我现在把html元素的空格都去掉的话,那么所有的浏览器都会返回3个子元素,如下代码:

<ul id="list"><li>11</li><li>22</li><li>33</li></ul>

JS代码如下:

var list = document.getElementById("list");

console.log(list.childNodes);

console.log(list.childNodes.length);

那么平时我们不可能都这样小心小意的编写html代码的,因为一步小心就有一个空格产生,因此我们可以如下判断下是不是元素节点;如下代码演示:

<ul id="list">
 <li>11</li>
 <li>22</li>
 <li>33</li>
</ul>

JS代码如下:

var list = document.getElementById("list");
var arrs = [];
for(var i = 0, ilen = list.childNodes.length; i < ilen; i++) {
 var curElement = list.childNodes[i];
 if(curElement.nodeType === 1){
   arrs.push(curElement);
 }
}
console.log(arrs); 
console.log(arrs.length); // 3

如上代码在所有的浏览器都支持,通过判断该元素是不是元素节点,然后在执行后续操作;不是元素节点都会过滤掉,因此返回的长度都为3;

理解Text类型

Text节点具有以下特征:

nodeType的值为3;
nodeName的值为”#text”;
nodeValue的值为节点所包含的文本;
parentNode是一个Element;

没有子节点;

1.创建文本节点;
可以使用document.createTextNode()创建新文本节点,这个方法需要一个参数,即要插入的文本,如下代码:

var element = document.createElement("div");
var text = document.createTextNode("aa");
element.appendChild(text); 
document.body.appendChild(element);

2. 分割文本节点splitText(offset); 从offset指定的位置将当前的文本节点分成2个文本节点;如下代码:

var element = document.createElement("div");
var text = document.createTextNode("aabbbccc");
element.appendChild(text);
document.body.appendChild(element);

var newNode = element.firstChild.splitText(5);
console.log(element.firstChild.nodeValue); // aabbb
console.log(newNode.nodeValue); // ccc 
console.log(element.childNodes.length); // 2

浏览器支持的程度有IE,firefox,chrome都支持;

理解DOM操作—动态插入脚本

我们动态插入JS脚本,常见的写法有如下代码即可插入:

var script = document.createElement("script");
script.type ="text/javascript";
script.src = 'a.js';
document.body.appendChild(script);

即可在body最后面动态插如a.js,此动态插入不会堵塞浏览器加载;我们为了更加扩张性,我们也可以封装一个函数,如下代码:

function loadScript(url) {
 var script = document.createElement("script");
 script.type = "text/javascript";
 script.src = url;
 document.body.appendChild(script);
}
loadScript("a.js");

当页面有多个js需要动态插入的话,可以依次调用loadScript函数即可;
但是假如我们现在有这么一个需求,动态加载JS完后,我想在这后面做点事情,我们都知道,动态加载也可以理解为异步加载,不会堵塞浏览器,但是我们需要如何知道动态JS已经加载完了呢?

我们之前讲过一篇文章是 “Javascript事件总结”中有判断JS动态加载完后使用load事件来监听js是否动态加载完,现在我们再来复习下load事件;
Javascript中最常用的一个事件是load,当页面加载完后(包括所有的图片,javascript文件,css文件等外部资源)。就会触发window上面的load事件;目前可以有2种触发load事件,一种是直接放在body上面触发;比如如下代码:

<body onload=”a()”></body>

第二种是通过JS来触发,如下代码:

EventUtil.addHandler(window,'load',function(event){

      alert(1);

});

上面的EventUtil.addHandler 是我们之前在javascript事件总结中封装的JS函数,现在我们直接拿过来使用,如下代码:

var EventUtil = {
 addHandler: function(element,type,handler) {
  if(element.addEventListener) {
    element.addEventListener(type,handler,false);
  }else if(element.attachEvent) {
   element.attachEvent("on"+type,handler);
  }else {
   element["on" +type] = handler;
  }
 },
 removeHandler: function(element,type,handler){
  if(element.removeEventListener) {
    element.removeEventListener(type,handler,false);
  }else if(element.detachEvent) {
   element.detachEvent("on"+type,handler);
  }else {
   element["on" +type] = null;
  }
 },
 getEvent: function(event) {
  return event ? event : window.event;
 },
 getTarget: function(event) {
  return event.target || event.srcElement;
 },
 preventDefault: function(event){
  if(event.preventDefault) {
   event.preventDefault();
  }else {
   event.returnValue = false;
  }
 },
 stopPropagation: function(event) {
  if(event.stopPropagation) {
   event.stopPropagation();
  }else {
   event.cancelBubble = true;
  }
 }
};

下面我们来看看如何使用JS事件来判断JS是否加载完成的代码,我们可以使用load事件来监听,如下代码:

EventUtil.addHandler(window,'load',function(){
 var script = document.createElement("script");
 EventUtil.addHandler(script,'load',function(event){
  console.log(script.src);
 });
 script.src = 'a.js';
 document.body.appendChild(script);
});

如上代码,script元素也会触发load事件,以便可以确定动态加载jS是否加载完成,上面的代码指定src属性和指定的事件处理程序的事件可以调换位置,先后顺序不重要,如上代码,我们也可以这样写,如下代码:

EventUtil.addHandler(window,'load',function(){
 var script = document.createElement("script");
 script.src = 'a.js';
 document.body.appendChild(script);
 EventUtil.addHandler(script,'load',function(event){
  console.log(script.src);
 });  
});

目前只有IE9+,Firefox,opera,chrome和safari3+支持,IE8及以下不支持该load事件,因此我们需要针对IE8及以下做处理;

理解readystatechange事件

IE为DOM文档中的某些部分提供了readystatechange事件,这个事件的目的提供与文档或元素的加载的状态有关信息,此事件提供了下面5个值中的一个;

uninitialized(未初始化):对象存在但尚未初始化;
loading(正在加载):对象正在加载数据;
loaded(加载完毕): 对象加载数据完成;
interactive(交互): 可以操作对象了,但没有完全加载;
complete(完成):对象已经加载完成;

<script>(在IE和opera)中和<link>(仅IE中)元素也会触发readystatechange事件,可以用来确定外部的javascript和css文件是否已经加载完成,基于元素触发的readystatechange事件,即readyState属性无论等于”loaded”还是”complete”都可以表示资源已经可用;如下针对IE的判断javascript是否已经加载完成的方案:

EventUtil.addHandler(window,'load',function(){
 var script = document.createElement("script");
 EventUtil.addHandler(script,'readystatechange',function(event){
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  if (target.readyState == "loaded" || target.readyState == "complete"){
   EventUtil.removeHandler(target, "readystatechange", arguments. callee);
   alert(script.src);
  }
 });
 script.src = 'a.js';
 document.body.appendChild(script); 
});

下面我们可以使用javascript客户端检测技术来判断是否是IE和其他标准浏览器做一个简单的封装;如下代码:

var ua = navigator.userAgent.toLowerCase();
EventUtil.addHandler(window,'load',function(){
 var script = document.createElement("script");
 if(/msie ([^;]+)/.test(ua) || "ActiveXObject" in window) {
  // IE 浏览器
  EventUtil.addHandler(script,'readystatechange',function(event){
   event = EventUtil.getEvent(event);
   var target = EventUtil.getTarget(event);
   if (target.readyState == "loaded" || target.readyState == "complete"){
    EventUtil.removeHandler(target, "readystatechange", arguments. callee);
    console.log("javascript已经加载完成");
   }
  });
 }else {
  // 除IE之外的标准浏览器
  EventUtil.addHandler(script,'load',function(event){
   console.log("javascript已经加载完成");
  });
 }
 script.src = 'a.js';
 document.body.appendChild(script);
});

上面的是针对所有的主要的浏览器进行封装来判断动态加载的JS是否已经加载完成!

理解动态加载行内JS方式

另一种加载javascript代码的方式是行内方式,如下代码:

<script src=”text/javascript”>

       function sayHi(){alert(1);}

</script>

从理论来讲,下面的代码应该是有效的;

var script = document.createElement('script');

script.type = 'text/javascript';

script.appendChild(document.createTextNode("function sayHi(){alert(1);} sayHi()"));

document.body.appendChild(script);

如上代码在firefox,IE9+,chrome和opera中,都是正常的,可以运行的,但是在IE8及以下会报错,如下:

你所不了解的javascript操作DOM的细节知识点(一)

IE8及以下将script视为一个特殊的元素,不允许dom访问其子节点;但是我们可以使用script元素的text属性来指定javascript代码,如下代码:

var script = document.createElement('script');
script.type = 'text/javascript';
script.text = "function sayHi(){alert(1);} sayHi()";
document.body.appendChild(script);

现在在所有的主流的浏览器都是正常的,在IE8及以下也是正常的;但是在safari3.0之前的版本不能正确的支持text属性,但可以允许使用文本节点来指定代码;如下代码:

var script = document.createElement('script');
script.type = 'text/javascript';
var code = "function sayHi(){alert(1);} sayHi()";
try{
 script.appendChild(document.createTextNode(code));
}catch(e){
 script.text = code;
}
document.body.appendChild(script);

下面我们可以使用一个函数来封装下;如下代码:

function loadScriptString(code) {
 var script = document.createElement('script');
 script.type = 'text/javascript';
 try{
  script.appendChild(document.createTextNode(code));
 }catch(e){
  script.text = code;
 }
 document.body.appendChild(script);
}
loadScriptString("function sayHi(){alert(1);} sayHi()");

理解动态加载css样式

与动态脚本类似,所谓动态样式是指在页面刚载入时不存在的样式,动态样式是在页面加载完成后动态添加到页面中的;

动态加载样式如下代码:

var link = document.createElement("link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = "style.css";
var head = document.getElementsByTagName("head")[0];
head.appendChild(link);

如上代码即可动态生成css样式;

我们也可以像JS一样先封装一个函数,为了调用更加方便;如下代码:

function loadStyles(url) {
 var link = document.createElement("link");
 link.rel = "stylesheet";
 link.type = "text/css";
 link.href = url;
 var head = document.getElementsByTagName("head")[0];
 head.appendChild(link);
}
loadStyles("style.css");

我们也可以使用事件的方式来判断css动态加载是否已经加载完成~

EventUtil.addHandler(window,'load',function(){
 var link = document.createElement("link");
 link.rel = "stylesheet";
 link.type = "text/css";
 EventUtil.addHandler(link, "load", function(event){
  console.log("css loaded");
 });
 link.href = "style.css";
 document.getElementsByTagName("head")[0].appendChild(link);
});

注意:如上代码:不仅在标准浏览器下chrome,firefox,opera下支持,而且IE7-10都支持这种加载;但是safari不支持这种加载;

或者和javascript一样,我们也可以针对IE下做一种这样的处理;代码如下,也可以支持所有的IE下:

EventUtil.addHandler(window, "load", function(){
 var link = document.createElement("link");
 link.type = "text/css";
 link.rel= "stylesheet";
 EventUtil.addHandler(link, "readystatechange", function(event){
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  if (target.readyState == "loaded" || target.readyState == "complete"){
   EventUtil.removeHandler(target, "readystatechange", arguments. callee);
   alert("CSS Loaded");
  }
 });
 link.href = "style.css";
 document.getElementsByTagName("head")[0].appendChild(link);
});

我们也可以像Javascript一样,为了判断所有的浏览器下支持可以封装一个所有都支持的代码;当然除safari浏览器除外,好像safari浏览器没有生效;

如下代码:

var ua = navigator.userAgent.toLowerCase();
EventUtil.addHandler(window,'load',function(){
 var link = document.createElement("link");
 link.type = "text/css";
 link.rel= "stylesheet";
 if(/msie ([^;]+)/.test(ua) || "ActiveXObject" in window) {
  // IE 浏览器
  EventUtil.addHandler(link,'readystatechange',function(event){
   event = EventUtil.getEvent(event);
   var target = EventUtil.getTarget(event);
   if (target.readyState == "loaded" || target.readyState == "complete"){
   EventUtil.removeHandler(target, "readystatechange", arguments. callee);
    alert("css已经加载完成");
   }
  });
 }else {
  // 除IE之外的标准浏览器
  EventUtil.addHandler(link,'load',function(event){
   alert("css已经加载完成");
  });
 }
 link.href = "style.css";
 document.getElementsByTagName("head")[0].appendChild(link);
});

另一种定义样式的方式是使用<style>元素来包含嵌入式的css

如下所示代码:

<style>

body {background-color:red;}

</style>

按照相同的原理,下面的代码除了IE8及以下不支持,其他浏览器下都支持;如下代码:

var style = document.createElement("style");
style.type = "text/css";
style.appendChild(document.createTextNode("body{background-color:red}"));
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

因此我们也可以像JS一样封装一个全兼容的css样式;如下代码所示:

var style = document.createElement("style");
style.type = "text/css";
try{
 style.appendChild(document.createTextNode("body{background-color:red}"));
} catch (ex){
 style.styleSheet.cssText = "body{background-color:red}";
}
var head = document.getElementsByTagName("head")[0];
head.appendChild(style);

为了更方便调用,因此我们也可以封装一个函数来;如下代码:

function loadStyleString(css) {
 var style = document.createElement("style");
 style.type = "text/css";
 try{
  style.appendChild(document.createTextNode(css));
 } catch (ex){
  style.styleSheet.cssText = css;
 }
 var head = document.getElementsByTagName("head")[0];
 head.appendChild(style);
}
// 调用方式如下:
loadStyleString("body{background-color:red}");

DOM的扩展

1. querySelector()方法

querySelector()方法接收一个css选择符,返回与该模式匹配的第一个元素,如果没有找到匹配元素,则返回null;如下代码:

<div id="test">test</div>

<div class="cls">1111</div>

<div class="cls">2222</div>

<div class="cls">2222</div>

JS代码如下:

// 取得body
var body = document.querySelector('body');
console.log(body); // body的引用
// 取得id为test的元素
var test = document.querySelector("#test");
console.log(test); // <div id="test">test</div>
// 取得类为cls的第一个元素
var cls = document.querySelector(".cls");
console.log(cls); // <div class="cls">1111</div>

同理我们也可以通过Element类型调用querySelector()方法时,只会在该元素后代范围内查找匹配的元素,如下代码:

<div class="testParent">

<p>1111</p>

<p>2222</p>

</div>

我想通过先取得div中的testParent,然后再取得p元素(通过querySelector方法);如下代码:

var testParent = document.querySelector('.testParent');
var pfirst = testParent.querySelector("p");
console.log(pfirst); // <p>1111</p>

浏览器支持程度:IE8+,firefox,chrome,safari3.1+,opera10+

2. querySelectorAll()方法

querySelectorAll()方法接收的参数与querySelector()方法一样,都是一个css选择符,但是返回的是所有匹配的元素,这个方法返回的是一个NodeList的实例。

如下代码演示:

<div id="test">
 <em class="selected">11111</em>
 <em class="selected">22222</em>
 <em>33333</em>
 <em>44444</em>
</div>

JS代码如下:

// 取得div中所有em元素
var em = document.getElementById("test").querySelectorAll('em');
console.log(em); // NodeList
console.log(em.length); // 4

// 第二种方式 也可以先使用querySelector
var test = document.querySelector("#test");
var em2 = test.querySelectorAll('em');
console.log(em2); // NodeList
console.log(em2.length); // 4

// 取得类为selected的所有元素
var selected = document.querySelectorAll('.selected');
console.log(selected); // <em class="selected">11111</em>
    // <em class="selected">22222</em>
// 也可以通过以下的方式获得所有的em元素
var em = document.querySelectorAll('div em');
console.log(em); // NodeList
console.log(em.length); // 4

要取得返回的NodeList中的每一个元素,可以使用item()方法,也可以使用方括号的语法;

var ems = document.querySelectorAll('#test em');
for(var i = 0, ilen = ems.length; i < ilen; i++) {
 console.log(ems[i]); // 取得每一项em
 console.log(ems.item(i)); // 取得每一项em
}

浏览器支持程度:IE8+,firefox,chrome,safari3.1+,opera10+

3. matchesSelector()方法

这个方法接收一个参数,即css选择符,如果调用元素与该选择符匹配,返回true,否则,返回false;

如下代码:

console.log(document.body.webkitMatchesSelector("body")); // true

IE9+通过msMatchesSelector()支持该方法,Firefox3.6+通过mozMatchesSelector()支持该方法;safari5+和chrome通过webkitMatchesSelector()支持该方法;因此我们可以包装一个函数,如下代码:

function matchesSelector(element,selector) {
if(element.MatchesSelector) {
 return element.MatchesSelector(selector);
}else if(element.msMatchesSelector) {
 return element.msMatchesSelector(selector);
}else if(element.webkitMatchesSelector) {
 return element.webkitMatchesSelector(selector);
}else if(element.mozMatchesSelector) {
 return element.mozMatchesSelector(selector);
}else {
  throw new Error("Not supported");
 }
}
console.log(matchesSelector(document.body,'body')); // true
var test = document.querySelector('#test');
console.log(matchesSelector(test,'#test')); // true

4. getElementsByClassName() 方法

该方法接收一个参数,即包含一个或者多个类名的字符串,返回带有指定类的所有元素的NodeList;传入多个类名时,类名的先后顺序不重要。

<p class="p1">111</p>

<p class="p1">222</p>

<p class="p1">333</p>

console.log(document.getElementsByClassName("p1")); // 返回NodeList

支持getElementsByClassName()方法的浏览器有IE9+,firefox3+,safari3.1+,chrome和opera9.5+;

5. classList属性

如下代码:

<div class="aa bb cc" id="div">aaa</div>

我想删除aa这个类名的时候,以前需要如下写代码:

var div = document.getElementById("div");
// 首先,取得类名字符串并拆分成数组
var classNames = div.className.split(/\s+/);
var pos = -1,
  i, 
 len;
for(i = 0,len = classNames.length; i < len; i+=1) {
 if(classNames[i] == 'aa') {
  pos = i;
  break;
 }
}
// 删除类名
classNames.splice(i,1);
// 把剩下的类名拼成字符串并重新设置
div.className = classNames.join(" "); 
// <div class="bb cc" id="div">aaa</div>

但是现在HTML5新增了一种操作类名的方式,可以让操作更简单也更方便,那就是所有元素添加classList属性。这个classList属性是新集合类型DOMTokenList的实例;DOMTokenList有一个表示自己包含多少元素的length属性,而要取得每个元素可以使用item()方法,也可以使用中括号;

还有以下方法:

add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。
contains(value):表示列表中是否存在给定的值,如果存在则返回true,否则返回false。
remove(value):从列表中删除给定的字符串。
toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。
现在我们可以来看看使用上面的方法的优点如下,我们可以来做个demo,代码如下:

<div class="aa bb cc" id="div">aaa</div>

JS如下:

var div = document.getElementById("div");
// 删除类名aa,如下方法即可
div.classList.remove("aa");
// html结构如下: <div class="bb cc" id="div">aaa</div>
// 添加类名current,如下方法即可
div.classList.add("current");
// html结构变为如下 <div class="bb cc current" id="div">aaa</div>

// 切换aa类名
div.classList.toggle("aa");
// html结构变为如下 <div class="aa bb cc current" id="div">aaa</div>
// 确定元素是否包含类名;
console.log(div.classList.contains("aa")); // true

支持classList属性的浏览器有IE10+,firefox3.6+和chrome

HTML5新增自定义数据属性

HTML5中规定可以为元素添加非标准的属性,但要添加前缀data-,比如如下代码:

<div id="myDiv" data-appId="12345" data-myname="Nicholas"></div>

添加了自定义属性之后,可以通过元素的dataset属性来访问自定义属性的值,dataset属性的值是DOMStringMap的一个实例,也就是一个名值对的映射;比如如下代码:

//本例中使用的方法仅用于演示
var div = document.getElementById("myDiv");
console.log(div.dataset); // DOMStringMap {value: "12345", name: "Nicholas"}
//取得自定义属性的值
var value1 = div.dataset.value;
console.log(value1); // 12345
var name1 = div.dataset.name;
console.log(name1); // Nicholas
//设置值
div.dataset.value = 23456;
div.dataset.name = "Michael";
//有没有"myname"值呢?
if (div.dataset.name){
 console.log("Hello, " + div.dataset.name); // Hello, Michael
}

你所不了解的javascript操作DOM的细节知识点(一)

浏览器支持的程度有;firefox6+和chrome浏览器;

Javascript 相关文章推荐
表单类各种类型(文本框)失去焦点效果jquery代码
Apr 26 Javascript
jQuery之按钮组件的深入解析
Jun 19 Javascript
解决html按钮切换绑定不同函数后点击时执行多次函数问题
May 14 Javascript
jquery幻灯片插件bxslider样式改进实例
Oct 15 Javascript
javascript下使用Promise封装FileReader
Feb 19 Javascript
js实现分割上传大文件
Mar 09 Javascript
JQuery form表单提交前验证单选框是否选中、删除记录时验证经验总结(整理)
Jun 09 jQuery
使用淘宝镜像cnpm安装Vue.js的图文教程
May 17 Javascript
Vue监听滚动实现锚点定位(双向)示例
Nov 13 Javascript
JS判断浏览器类型与操作系统的方法分析
Apr 30 Javascript
js中!和!!的区别与用法
May 09 Javascript
nuxt 每个页面head标签内容设置方式
Nov 05 Javascript
jQuery实现复选框批量选择与反选的方法
Jun 17 #Javascript
在Python中使用glob模块查找文件路径的方法
Jun 17 #Javascript
举例简介AngularJS的内部语言环境
Jun 17 #Javascript
详解AngularJS中自定义指令的使用
Jun 17 #Javascript
详解AngularJS中的依赖注入机制
Jun 17 #Javascript
详解AngularJS中的作用域
Jun 17 #Javascript
简介AngularJS中使用factory和service的方法
Jun 17 #Javascript
You might like
php数组函数序列之each() - 获取数组当前内部指针所指向元素的键名和键值,并将指针移到下一位
2011/10/31 PHP
强烈声明: 不要使用(include/require)_once
2013/06/06 PHP
PHP的Yii框架中YiiBase入口类的扩展写法示例
2016/03/17 PHP
php实现的mongoDB单例模式操作类
2018/01/20 PHP
Gambit vs CL BO3 第一场 2.13
2021/03/10 DOTA
如何取得中文输入的真实长度?
2006/06/24 Javascript
jquery autocomplete自动完成插件的的使用方法
2010/08/07 Javascript
事件模型在各浏览器中存在差异
2010/10/20 Javascript
javascript与jquery中跳出循环的区别总结
2013/11/04 Javascript
js/jquery解析json和数组格式的方法详解
2014/01/09 Javascript
jquery显示隐藏input对象
2014/07/21 Javascript
js实现类似MSN提示的页面效果代码分享
2015/08/24 Javascript
页面间固定参数,通过cookie传值的实现方法
2017/05/31 Javascript
使用JS判断移动端手机横竖屏状态
2018/07/30 Javascript
详解关于vue2.0工程发布上线操作步骤
2018/09/27 Javascript
vue router 源码概览案例分析
2018/10/09 Javascript
新版小程序登录授权的方法
2018/12/12 Javascript
vue实现的下拉框功能示例
2019/01/29 Javascript
VueJS实现用户管理系统
2020/05/29 Javascript
Vue3+elementui plus创建项目的方法
2020/12/01 Vue.js
Python学生信息管理系统修改版
2018/03/13 Python
Python3中正则模块re.compile、re.match及re.search函数用法详解
2018/06/11 Python
pycharm无法导入本地模块的解决方式
2020/02/12 Python
python正则表达式实例代码
2020/03/03 Python
tensorflow 20:搭网络,导出模型,运行模型的实例
2020/05/26 Python
使用Python快速打开一个百万行级别的超大Excel文件的方法
2021/03/02 Python
REISS美国官网:伦敦最受欢迎的时尚品牌
2019/08/16 全球购物
葡萄牙航空官方网站:TAP Air Portugal
2019/10/31 全球购物
Michael Kors英国官网:美国奢侈品品牌
2019/11/13 全球购物
关于是否需要写商业计划书
2014/02/07 职场文书
小学趣味运动会加油稿
2014/09/25 职场文书
教师实习自我鉴定总结
2019/08/20 职场文书
MySQL 四种连接和多表查询详解
2021/07/16 MySQL
css中z-index: 0和z-index: auto的区别
2021/08/23 HTML / CSS
详解Flutter和Dart取消Future的三种方法
2022/04/07 Java/Android
SQL Server中搜索特定的对象
2022/05/25 SQL Server