html dom 高级,js中的DOM操作(JavaScript高级程序设计笔记)

DOM

DOM(文档对象模型)是针对HTML 和XML 文档的一个API(应用程序编程接口)。

DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。

节点层次

DOM可以将任何HTML文档描绘成多层节点构成的对象模型

Sample Page

Hello World!

上面的html内容转换成DOM结构就是下面这张图的样子

70b983ae7eb5

Document节点是每个文档的根节点, 它只有一个子节点, 即元素, 我们叫它文档元素。其他所有元素都包含在文档元素中。

对于每一段html标记都可以通过树中的一个节点表示: HTMl元素通过元素节点表示, 特性通过属性节点表示, 文档通过文档节点表示, 除了这些还有诸如注释节点等总共12中节点类型, 这些类型都继承于一个基类型

Node类型

DOM1级定义了一个Node类型, 所有的节点类型都继承自Node类型, 所有节点类型都共享着相同的基本属性和方法。

每个节点都有一个nodeType属性, 用于表示节点类型, 通常这个属性会返回一个常数, 每个常数对应一种类型, 任何节点都是这12种的其中一个:

Node.ELEMENT_NODE(1)

Node.ATTRIBUTE_NODE(2)

Node.TEXT_NODE(3)

Node.CDATA_SECTION_NODE(4)

Node.ENTITY_REFERENCE_NODE(5)

Node.ENTITY_NODE(6)

Node.PROCESSING_INSTRUCTION_NODE(7)

Node.COMMENT_NODE(8)

Node.DOCUMENT_NODE(9)

Node.DOCUMENT_TYPE_NODE(10)

Node.DOCUMENT_FRAGMENT_NODE(11)

Node.NOTATION_NODE(12)

通过上面的常量, 可以很容易的确定节点类型, 如:

if (someNode.nodeType == 1){ //适用于所有浏览器

alert("Node is an element.");

}

我们可以通过节点的nodeName属性得到元素的标签名, 对于元素节点, nodeValue的值始终是null

节点关系

文档中所有元素之前都存在着父子关系, 如

元素师

的父亲元素

每个节点都有一个childNodes属性, 其中保存着一个NodeList伪数组, NodeList中有序的保存着该节点的所有子节点, 可以通过伪数组位置来访问某个子节点, NodeList最显著地特点是他是基于DOM结构动态执行查询结果的, 会实时反映DOM结构的变化。下面例子是childNodes的典型应用:

var firstChild = someNode.childNodes[0]

var secondChild = someNode.childNodes.item(1)

var count = someNode.childNodes.length

这里需要注意的一点, 使用childNodes时, 同胞元素之间的空格和回车会被识别为文本节点

除了childNodes外, 每个节点都有一个parentNode属性, 该属性用来获取该节点的父节点, 此外还有一个previousSibling用来获取前一个同胞节点, nextSibling用来获取后一个同胞节点

这里同样需要注意, 由于元素间的空格和回车会被识别为文本节点, 所有通过previousSibling和 nextSibling获取同胞节点时, 当遇到这些文本节点时, 一定要向前或向后获取两次同胞节点来跳过这些文本节点

父节点还可以通过firstChild和lastChild来获得第一个子节点和最后一个子节点

下面这张图就是上面这些属性的总结

70b983ae7eb5

除了上面这些, 我很还可以通过hasChildNodes()方法来判断节点是否有子节点

所有节点都有的最后一个属性是ownerDocument,该属性指向表示整个文档的文档节点。这种关系表示的是任何节点都属于它所在的文档,任何节点都不能同时存在于两个或更多个文档中。

操作节点

appendChild(newNode)

DOM提供了很多操作节点的方法, 其中最常用的方法就是appendChild(), 用于向childNodes列表末尾添加一个节点, 如下面的例子就是向body的末尾添加一个button子节点

let el = document.querySelector('body')

let btn = document.createElement('button')

el.appendChild(btn)

如果传入appendChild()中的节点已经是文档的一部分了, 那么该节点会从原来的位置转移到新位置, 原因是DOM中的任何元素不能同时出现在文档中的多个位置上, 如下面的例子

//someNode 有多个子节点

var returnedNode = someNode.appendChild(someNode.firstChild);

alert(returnedNode == someNode.firstChild); //false

alert(returnedNode == someNode.lastChild); //true

insertBefore(insertNode, positionNode)

该方法是把某个放到childNodes列表的某个特定位置上, 第一个参数是要插入的节点, 第二个参数是作为参照的节点。执行后要插入的节点就会被放在参照节点的前一个位置

replaceChild(newNode, replaceNode)

该方法接受两个参数, 第一个是要插入的节点, 第二个是要被替换的节点, 返回值为被替换的节点

removeChild(Node)

该方法接受一个参数, 就是要移除的节点, 返回值是被移除的节点, removeChild和replaceChild一样, 被删除的节点并没有在文档中被删除, 只是断开了所有链接

cloneNode(boolean)

该方法接受两个参数为true时对元素进行深拷贝, 拷贝对象包括其子节点, 为false时为浅拷贝, 只拷贝元素本身

// 假设我们有一个

  • 元素, 里面包含三个
  • , 我们用myList保存获取的

    var deepList = myList.cloneNode(true);

    alert(deepList.childNodes.length); //3(IE < 9)或7(其他浏览器)

    var shallowList = myList.cloneNode(false);

    alert(shallowList.childNodes.length);

    cloneNode()方法不会复制添加到DOM 节点中的JavaScript 属性,例如事件处理程序等。这个方法只复制特性、(在明确指定的情况下也复制)子节点,其他一切都不会复制。IE 在此存在一个bug,即它会复制事件处理程序,所以建议在复制之前最好先移除事件处理序。

    最后一个方法是normalize(),这个方法唯一的作用就是处理文档树中的文本节点。由于解析器的实现或DOM操作等原因,可能会出现文本节点不包含文本,或者接连出现两个文本节点的情况。当在某个节点上调用这个方法时,就会在该节点的后代节点中查找上述两种情况。如果找到了空文本节点,则删除它;如果找到相邻的文本节点,则将它们合并为一个文本节点

    Document类型节点相关操作

    在浏览器中, document对象是HTMLDocument(继承自Document类型)的一个实例, 表示整个HTML页面, document对象是window对象的一个属性, 可以通过全局访问

    我们可以通过其documentElement属性或者childNodes来访问元素

    document对象还有一个body属性指向

    元素。document.body在js代码中出现的频率非常高, 请大家熟练掌握

    document我们用的最多的是查找元素相关操作

    我们可以通过document.getElementById()和document.getElementsByTagName()这两个方法获取相关节点

    现在更多的用document.querySelector()和document.querySelectorAll()这两个方法来获取元素

    Element类型

    除了document类型外, Element类型就要算是web编程中最常用的类型了。

    nodeName和tagName

    这两个属性都返回标签名, 返回的是大写, 例如:

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

    alert(div.tagName); //"DIV"

    alert(div.tagName == div.nodeName); //true

    所以判断时不要忘记用toLowerCase()转换成小写, 不然很容易出错

    if (element.tagName == "div"){ //不能这样比较,很容易出错!

    //在此执行某些操作

    }

    if (element.tagName.toLowerCase() == "div"){ //这样最好(适用于任何文档)

    //在此执行某些操作

    }

    HTML元素

    html一般有以下表示特性的属性, 我们用例子来表示:

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

    alert(div.id); //"myDiv"

    alert(div.className); //"bd"

    alert(div.title); //"Body text"

    alert(div.lang); //"en" 元素内的语言代码, 很少使用

    alert(div.dir); //"ltr" 语言方向, ,值为"ltr"(left-to-right,从左至右)或"rtl"(right-to-left,从右至左, 很少使用。

    当然,像下面这样通过为每个属性赋予新的值,也可以修改对应的每个特性:

    div.id = "someOtherId";

    div.className = "ft";

    div.title = "Some other text";

    div.lang = "fr";

    div.dir ="rtl";

    取得特性

    元素有很多的特性, 我们主要通过getAttribute(), setAttribute()和removeAttribute()来操作特性

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

    alert(div.getAttribute("id")); //"myDiv"

    alert(div.getAttribute("class")); //"bd"

    alert(div.getAttribute("title")); //"Body text"

    alert(div.getAttribute("lang")); //"en"

    alert(div.getAttribute("dir")); //"ltr"

    getAttribute也可以获取自定义属性

    var value = div.getAttribute("my_special_attribute"); //hello!

    特性的名称是不区分大小写的,即"ID"和"id"代表的都是同一个特性。另外也要注意,根据HTML5 规范,自定义特性应该加上data-前缀以便验证。

    自定义的属性只能通过getAttribute获取, 不能直接用属性名获取, 下面的代码就获取自定义属性得到的是undefined

    alert(div.id); //"myDiv"

    alert(div.my_special_attribute); //undefined(IE 除外)

    alert(div.align); //"left"

    style和事件绑定getAttribute和直接调用属性名返回的内容是不同的

    在通过getAttribute()访问时,返回的style 特性值中包含的是CSS 文本,而通过属性来访问它则会返回一个对象。

    通过getAttribute()访问事件时,会返回相应代码的字符串。而在访问s事件属性(如onclick)时,则会返回一个JavaScript 函数(如果未在元素中指定相应特性,则返回null)

    设置特性

    与getAttribute()对应的方法是setAttribute()这个方法接受两个参数:要设置的特性名和值。如果特性已经存在,setAttribute()会以指定的值替换现有的值;如果特性不存在,setAttribute()则创建该属性并设置相应的值。来看下面的例子:

    div.setAttribute("id", "someOtherId");

    div.setAttribute("class", "ft");

    div.setAttribute("title", "Some other text");

    div.setAttribute("lang","fr");

    div.setAttribute("dir", "rtl");

    也可以通过直接给属性赋值设置元素的特性

    div.id = "someOtherId";

    div.align = "left";

    直接给属性赋值对于自定义属性依然不适用

    removeAttribute(),这个方法用于彻底删除元素的特性。

    Element类型的attributes属性

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

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

    setNamedItem(node):向列表中添加节点,以节点的nodeName 属性为索引;

    item(pos):返回位于数字pos 位置处的节点。

    创建元素

    使用document.createElement()方法可以创建新元素。这个方法只接受一个参数,即要创建元素的标签名

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

    元素的子节点

    正如我们之前说的, 元素的childNodes 属性中包含了它的所有子节点,这些子节点有可能是元素、文本节点、注释或处理指令。不同浏览器在看待这些节点方面存在显著的不同,以下面的代码为例。

    • Item 1
    • Item 2
    • Item 3

    在浏览器中,

    • 元素都会有7 个元素,包括3 个
    • 元素和4 个文本节点(表示
    • 元素之间的空

      白符)。如果像下面这样将元素间的空白符删除,那么所有浏览器都会返回相同数目的子节点。

      • Item 1
      • Item 2
      • Item 3

      如果需要通过childNodes 属性遍历子节点,那么一定不要忘记有些浏览器会识别标签间的空格。这意味着在执行某项操作以前,通常都要先检查一下nodeTpye 属性,如下面的例子所示。

      for (var i=0, len=element.childNodes.length; i < len; i++){

      if (element.childNodes[i].nodeType == 1){

      //执行某些操作

      }

      }

      DOM操作技术

      很多时候,DOM 操作都比较简明,因此用JavaScript 生成那些通常原本是用HTML代码生成的内容并不麻烦。不过,也有一些时候,操作DOM 并不像表面上看起来那么简单。由于浏览器中充斥着隐藏的陷阱和不兼容问题,用JavaScript 代码处理DOM 的某些部分要比处理其他部分更复杂一些。

      动态脚本

      我们都知道使用

      我们可以通过以下方式引入外部JavaScript文件

      通过js代码创建这个节点的代码如下:

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

      script.type = "text/javascript";

      script.src = "client.js";

      document.body.appendChild(script);

      我们可以用一个函数讲代码封装

      function loadScript(url) {

      let script = document.createElement('script')

      script.type = 'text/javascript'

      script.src = url

      document.body.appendChild(script)

      }

      然后调用loadScript('client.js')就能加载外部js文件了

      我们还可以直接在

      function sayHi(){

      alert("hi");

      }

      同样的我们也可以通过js代码创建这个标签

      let script = document.createElement("script")

      script.type = "text/javascript"

      script.text = "(function() {alert('hi')})()"

      document.body.appendChild(script);

      同样我们可以封装

      function loadScriptString(code){

      let script = document.createElement("script")

      script.type = "text/javascript"

      script.text = code

      document.body.appendChild(script)

      }

      之后调用就行了loadScriptString('(function() {console.log("a")})()')

      以这种方式加载的代码会在全局作用域中执行,而且当脚本执行后将立即可用。实际上,这样执行 代码与在全局作用域中把相同的字符串传递给 eval()是一样的。

      动态样式

      动态样式的套路跟上面以上, 也是用js代码动态插入标签, 我们知道引给页面添加css样式有两种方式, 一种引入外部css文件, 另一种

      如果我们想动态引入外部css文件, 我们可以通过一个函数封装来实现

      function loadStyles(url){

      let link = document.createElement("link")

      link.rel = "stylesheet"

      link.type = "text/css"

      link.href = url

      let head = document.getElementsByTagName("head")[0]

      head.appendChild(link)

      }

      如果想直接嵌入css则使用下面的封装来实现

      function loadStyleString(css){

      let style = document.createElement("style")

      style.type = "text/css"

      style.styleSheet.cssText = css

      let head = document.getElementsByTagName("head")[0]

      head.appendChild(style)

      }

      // 想下面这样调用函数即可

      loadStyleString("body{background-color:red}")

      NodeList

      NodeList是"动态的", 每当文档结构发生变化时, 它都会更新。本质上来说, NodeList对象是在访问DOM文档时实时运行的查询, 例如, 下面的代码就会无限循环:

      let divs = document.getElementsByTagName("div"), i, div;

      for(i=0; i < divs.length; i++) {

      div = document.createElement("div")

      document.body.appendChild(div)

      }

      第一行代码会取得文档中所有

      元素的 HTMLCollection。由于这个集合是“动态的”,因此, 只要有新
      元素被添加到页面中,这个元素也会被添加到该集合中。浏览器不会将创建的所有集合都保存在一个列表中,而是在下一次访问集合时再更新集合。结果,在遇到上例中所示的循环代码时,就会导致一个有趣的问题。每次循环都要对条件 i < divs.length 求值,意味着会运行取得所有
      元素的询。 考虑到循环体每次都会创建一个新
      元素并将其添加到文档中, 因此 divs.length 的值在每次循环后都会递增。既然 i 和 divs.length 每次都会同时递增,结果它们的 值永远也不会相等。

      var divs = document.getElementsByTagName("div"), i, len, div;

      for(i=0, len=divs.length; i < len; i++) {

      div = document.createElement("div");

      document.body.appendChild(div);

      }

      这个例子中初始化了第二个变量 len。由于 len 中保存着对 divs.length 最初的值,因此就会避免上一个例子中出现的无限循环问题。

      一般来说,应该尽量减少访问 NodeList 的次数。因为每次访问 NodeList,都会运行一次基于文档的查询。所以,可以考虑将从 NodeList 中取得的值缓存起来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值