9.1 冯娜

学习日志

今天学习了构建DOM树,查找元素、修改元素、添加元素、删除元素。

一、构建DOM树(先在头脑中构建DOM树)

1、查找触发事件的元素

var a=document.body.childNodes[i];

绑定事件处理函数

  • 在事件处理函数内部,查找要操作的元素,对元素进行修改/增加/删除

    a.onclick=function(){
    document.body.innerHTML="html"
    }
    注意:事件处理函数中的this可以自动获得触发事件的当前元素,this ==> 标签a

二、查找元素

1、不需要查找可直接获得的节点

  • document

    根节点

  • document.documentElement

    获取html中包含的所有内容

  • document.head

    获取head中包含的所有内容

  • document.body

    获取body中包含的所有内容

  • document.forms[i]

    获取页面中所有的表单元素

二、按节点间关系查找

按节点间关系查找是已经获得一个树上的节点对象,通过关系,逐渐找到想要的另一个元素的过程

  • 节点树

    • 什么是节点树?

    包含所有网页内容(节点)的树结构

    • 使用(两大类关系,六个属性)

    • 父子

      • elem.parentNode

      获得当前节点的父节点

      • elem.childNodes

      获得当前节点下的直接子节点的集合,返回一个类数组对象包含该父节点下所有直接子节点

      • elem.firstChild

      获得当前节点下的第一个直接子节点

      • elem.lastChild

      获得当前节点下的最后一个直接子节点

    • 兄弟

      • elem.previousSibling

      获得当前节点的前一个平级兄弟节点

      • elem.nextSibling

      获得当前节点的后一个平级兄弟节点

    • 节点树优点

    网页结构完整

    • 节点树问题

    我们在写代码的时候为了代码格式,会添加很多空格,缩进,换行,这些都是字符。在节点树中,字符也是节点对象,称为 " #text " 。这些看不见的文本节点,会极大的妨碍正常的查找

    • 解决节点树问题

    新DOM: 推出了一种新的树,元素树

  • 元素树

    • 什么是元素树?

    仅包含元素节点的树结构,不是一棵新树,仅是节点树的子集(其实内存中只有一棵完整的节点树。而元素树只不过是在节点树的基础上,新增了一些仅指向元素节点的新属性而已。所以,元素树只是节点树中的一个子集)

    • 什么时候使用?

    所有的按关系查找,都用元素树,而不用节点树(只关心元素,不关心文本时)

    • 为什么使用?

    元素树不包含节点,只包含元素,查找时无干扰

    • 使用( 2大类关系,六个属性)

    • 父子

      • elem.parentElement

      获得当前元素的父元素(因为能当父元素的通常都是元素节点,所以也可以用parentNode代替)

      • elem.children

      获得当前元素下的直接子元素的集合,返回一个类数组对象包含该父元素下所有直接子元素(IE8+)

      • elem.firstElementChild

      获得当前元素下的第一个直接子元素

      • elem.lastElementChild

      获得当前元素下的最后一个直接子元素

    • 兄弟

      • elem.previousElementSibling

      获得当前元素的前一个平级兄弟元素

      • elem.nextElementSibling

      获得当前元素的后一个平级兄弟元素

    • 优点

    不包含看不见的文本节点,不干扰查找

    • 缺点

    不包含一切文本节点,如果想获取文本可用 “ elem.innerHTML ”获取文本
    实例:elem.innerHTML
    兼容性问题:IE9+

  • childNodes和children的区别

    childNodes和children都返回动态集合(live collection),不实际存储数据,每次访问集合,都是重新查找DOM树
    优点:首次查找返回速度快
    缺点:反复访问集合,会导致反复查找DOM树
    遍历:for(var i=0,len=children.length;i<len;i++)
    区别:
    1.childNodes是标准属性,返回指定元素的子元素集合,包括HTML节点,所有属性,文本节点。可以通过nodeType判断是哪种类型的节点;
    2.children是非标准属性,返回指定元素的子元素集合。但是它只返回HTML几点,甚至不返回文本节点,虽然不是标准的DOM属性,但是却得到几乎所有浏览器的支持

  • 递归遍历

    • 什么时候使用?

    当需要遍历一个父节点下的所有后代节点时使用递归遍历

    • 使用递归遍历

    var body=document.body;
    function getChild(parent){
       // Step1:遍历parent的直接子节点
    var child=parent.childNodes;
       for(var i=0;i<child.length;i++){
    // 输出查找到的子元素
    console.log(child[i])
         //Step2: 为每个子节点调用和父节点完全相同的函数
    console.log(arguments);
         arguments.callee(child[i]);
       }
    }
    getChild(body)

    • 遍历原理:深度优先遍历

    1.什么是深度优先遍历(DFS算法)?
    深度优先遍历是从某个顶点出发,访问此顶点,然后从未被访问的邻接点出发深度优先遍历,直到所有和邻接点相同的路径都被访问到了。回溯到某个未被访问的分支,继续执行遍历。
    DOM遍历:是当同时有子节点和兄弟节点时,优先遍历子节点。只有所有子节点遍历完后,才能遍历兄弟节点
    2.什么是广度优先遍历(BFS算法)?
    广度优点遍历是从某个顶点出发,访问此顶点,然后访问所有未被访问的邻接点进行广度优点遍历,遍历完成后继续访问未被访问的分支节点执行遍历

    • 递归问题

    递归的效率极低

    • 解决递归问题

    解决:可用循环代替递归

    注意:createNodeIterator遍历的时候包含自身元素

    • 循环问题

    只能遍历所有,如需筛选,得自己写判断

    • 解决循环问题

    使用按条件查询

1、按HTML查找(4个特征)
  • 什么时候使用?

    当我们一个元素都没有获得,首次查找元素时使用HTML查找

  • 按ID查找

    var elem=document.getElementById("id")
    提示:
    1.只能用document调用,不能换成其他元素,使用document意为在整个网页范围内查找;
    2.只能找到一个元素对象,如果找不到返回null,如果不小心两个HTML元素id名相同,则getElementById只能返回第一个找到的元素
    问题:
    1.按id查找一次只能找一个元素对象,无法同时找多个元素对象;
    2.网页中标有id的元素并不多,因为没有那么多英文单词名字可用

  • 按标签名查找

    var elems=parent.getElementsByTagName("标签名")
    提示:
    1.可在任意父元素下查找。主语是哪个父元素,就只在哪个父元素内查找符合条件的元素;
    2.返回多个符合条件的元素组成的类数组对象,也是动态集合,如果找不到,返回空类数组对象:{ length:0 };
    3.getElementsByTagName()不仅查找直接子元素,而是在所有后代中查找符合条件的元素

  • 按class

    var elems=parent.getElementsByClassName("class")
    提示:
    1.可以在任意父元素上调用,控制查找的范围;
    2.返回类数组对象,也是动态集合,如果没找到,返回空类数组对象{ length:0 };
    3.不只是在直接子元素中查找,而是在所有后代子元素中查找符合条件的元素;
    4.如果一个元素有多个class修饰,而查找时,只要用其中一个class,就能找到该元素

    兼容性问题:IE9+

  • 按name

    var elems=document.getElementsByName("name")
    提示:
    1.只能在document上调用;
    2.返回类数组对象,也是动态集合,如果没找到,返回空类数组对象{ length:0 };
    3.因为只有表单元素才会有name属性,所以,getElementsByName几乎专门用于查找指定name属性的表单元素
    注意:ByTagName、ByClassName、 ByName三个函数注定返回类数组对象,即使只找到一个元素,也会放在类数组对象中返回,无法直接返回一个元素对象,所以,如果只找到一个元素对象时,应该再加[0],才能取出类数组对象中保存的惟一的这个元素对象

  • 按HTML查找问题

    一次只能按一个条件查找。如果查找条件复杂,元素藏得很深,还需要筛选时,按HTML特征查找,就会需要很多次查找,才能找到想要的元素,这样代码就过于繁琐

  • 解决按HTML查找问题

    按选择器查找

2、按选择器查找(Selector API)

1.可在任意父元素上调用;
2.返回类数组对象,但是是非动态集合;
3.受制于浏览器对选择器的兼容性(IE8+);
4.“ () ”中的选择器,是相对于“ . ” 前的父元素下的相对选择器,而不是完整的选择器

  • 什么时候使用?

    当查找条件非常复杂时,使用选择器查找,无论藏的多深的元素,css的选择器永远只需要一句很简洁的话就可找到元素

  • 查找一个元素

    var elem=parent.querySelector("selector")

  • 查找多个元素

    var elems=parent.querySelectorAll("selector")

返回值总结

1.如果该函数规定只返回一个元素对象,则如果找不到,都返回null(getElementById、querySelector)
2.如果该函数规定会返回一个类数组对象,则如果找不到,都返回空类数组对象:{ length: 0 }

3、按HTML vs 按选择器
  • 返回值

    1.按HTML,返回动态集合,不实际存储所有内容,只返回本次所需的内容,如果重复访问集合,就需要重新查找DOM树,优点是首次查找速度快,缺点是反复访问集合,会导致反复查找DOM树(会根据页面元素的变化而变化);
    2.selector API,返回非动态集合,可以直接存储所有数据,即使反复访问集合,也不需要反复查找DOM树

  • 首次查询效率

    1.按HTML效率更高,仅返回需要的内容,不需要准备完整数据;
    2.selector API低,第一次要返回完整数据

  • 易用性

    1.按HTML繁琐;
    2.selector API简单

  • 什么时候使用

    1.如果只凭一个条件即可获得想要的元素时,首选按HTML查找;
    2.如果需要多级复杂条件查找才能获得想要的元素时,用selector API

三、修改元素

1、内容

  • elem.innerHTML

    获取或设置开始标签到结束标签之间的原始的html代码片段,获取元素的HTML内容时,返回原始的HTML代码内容,修改元素的HTML内容时,先将新的HTML代码内容提交给浏览器解析后,再显示在元素内部
    实例:
    p.innerHTML="hello world"

  • elem.textContent

    获取或设置开始标签到结束标签之间的纯文本内容,获取元素的纯文本内容时,textContent会去掉内嵌标签,还会将特殊符号翻译为正文,修改元素的纯文本内容时,不会将新内容交给浏览器解析,而是直接原样显示在元素内部
    实例:
    p.textContent=“hello world

    • 对比innerHTML

    innerHTML设置文本的时候是去掉所有标签,只显示文本,同时翻译转义字符为正文,而textContent是我们写的是什么就显示什么

  • elem.innerText

    innerText用法同textContent一样,只不过具有兼容性要求(IE8+)

  • elem.value

    获取或修改表单元素的值
    注意:获取表单元素的值不能用 “.innerHTML",因为表单元素多数是单标记,没有innerHTML,表单元素的内容都是用“.value”属性来获取或修改的

2、属性

  • HTML标准属性

    HTML标准中规定的属性,比如:id, title, class, href, src, ...,属性值为字符串类型

    • 核心DOM(4个)

    操作一切结构化文档的通用API,即可操作HTML,又可操作XML
    优点:万能
    缺点:繁琐,单词太长

    • 获取属性

      var value=elem.getAttribute("属性名")
      实例:获得a元素的href属性
      a.getAttribute("href")
      了解:
      var attrNode=elem.attributes[ i / 属性名 ];
      // var attrNode=elem.getAttributeNode("属性名");
      var value=attrNode.value;

    • 修改属性

      elem.setAttribute("属性名",属性值)
      注意:如果属性不存在,也可以使用setAttribute,此时会自动添加该属性
      实例:修改a元素的title属性
      a.setAttribute("title","hello world")

    • 移除属性

      elem.removeAttribute("属性名")
      提示:只移除开始标签中的attribute,不删除内存中对象的property
      实例:移除a元素的title属性
      a.removeAttribute("title")

    • 判断是否含有属性

      var bool=elem.hasAttribute("属性名")
      实例:判断a元素是否有id属性,如果含有返回true,否则返回false
      a.hasAttribute("id")

    • HTML DOM

    什么是HTML DOM?
    HTML DOM是专门操作HTML文档的简化版API,只对部分常用API进行简化。所有HTML标准属性都被封装在HTML DOM对象中,可直接用 . 访问,用法普通对象的属性完全一样
    原理:HTML DOM提前将所有的HTML标准属性都定义在了元素对象上。只不过,属性值暂时是 " ",当手动在<开始标签>中加入了属性值或用程序在内存中添加了属性值,则该属性的值变为指定的新值
    优点:简单
    缺点:不是万能的,需要核心DOM的补充

    • 获取属性

      elem.属性名

    • 修改属性

      elem.属性名=值

    • 判断是否包含属性

      elem.属性名!==""

    • 移除属性

      elem.属性名=""

    • 特例:class属性

      class属性,不能直接用 “ . ” 访问,因为class属性是ES中的关键词,所以DOM不能再使用class作为属性。如果操作页面上的class属性,只能改名为“ .className”。使用"元素“.className"属性,等效于使用<元素 class="xxx">属性,直接修改class
      实例:
      元素.className="xxx"
      提示:元素.classList
      1.元素.classList.add("class名")
      2.元素.classList.remove("class名")

  • 状态属性

    HTML标准中规定的,只要放在元素上就起作用,不在元素上就没作用。且不需要指定属性值,包含:disabled, checked, selected(bool类型)

    • HTML

    在开始标签中加上,就有用,不加,就无效,不需要给属性值

    • CSS

    css选择器,查找指定状态的元素,状态伪类,例如:
    :disabled 专门匹配禁用的元素
    :checked 专门匹配选中的checkbox或radio
    [selected]  专门匹配选中的option
    实例:
    document.querySelector(“input:not(:checked)”)

    • JS

      在JS中disabled、checked、selected 其实都是bool类型

      • 问题

      核心DOM的四个函数,只支持字符串类型的属性,因为三大状态属性都是布尔型,所以不支持三大状态属性

      • 解决

      使用HTML DOM,只要使用三大状态属性,只能用“ . ”来访问,且值必须是bool类型
      提示:elem.状态
      实例:
      var option=document.querySelector("select>option");
      console.log(option.selected)

    • 扩展(自定义)属性

    • 什么是自定义属性?

      HTML标准中没有规定的,程序员自发添加到元素上的自定义属性

    • 什么时候使用?

      1.代替id、元素、class选择器,给多个元素添加行为,代替其他选择器,作为选择触发事件的元素添加行为的标志。可让行为和样式彻底分离(id选择器一次只能选一个元素,元素选择器,实现一种效果,不一定非要用一种标签,比如:实现按钮样子,可用 a input  button 都行。class选择器,专门用于修饰样式,修改会很频繁);
      2.在客户端网页中临时缓存之后操作频繁使用的业务数据,为了减少ajax请求的次数,提高加载速度和用户体验(比如: 放大镜效果时,虽然第一次不需要所有的小图片,中图片和大图片,但是会在首次请求时,就把所有图片的路径都下载到客户端,缓存在每个小图片上,当用户在小图片之间切换时,无需重复ajax请求,就可直接获得对应的中图片和大图片路径)

    • 自定义属性的好处

      自定义属性和标签名无关,和样式无关,自成体系,不受别人修改的影响

    • 普通自定义属性

      • 定义自定义扩展属性

      • 访问自定义扩展属性

      不能用HTML DOM打 . 的方式访问自定义扩展属性,因为自定义扩展属性是后天添加的,HTML DOM 无法提前预知自定义属性的名称,自然就不能提前在元素对象中准备好属性,所以不能用 "元素.自定义属性"方式访问。
      只能用核心DOM any.getAttribute("属性名") 和 any.setAttribute("属性名","值")
      因为getAttribute和setAttribute()和内存中的元素对象无关,而是每次都临时去页面上的HTML代码中开始标签中查找属性,而不是在对象中直接读取现成的属性

      • 问题

      HTML DOM无法访问扩展属性

      • 解决

      核心DOM

    • HTML5自定义属性

      • 定义自定义扩展属性

      所有自定义扩展属性名前必须加data-前缀:

      • 访问自定义扩展属性

      如果添加自定义属性时,添加了data-前缀,则可用dataset自动收集所有data-开头的属性,然后用 . 访问
      提示:elem.dataset.属性名
      实例:

      //如果想获得或修改md和lg属性的值
      img.dataset.md
      img.dataset.lg
      注意:dataset会自动收集所有data-开头的属性,不带data-前缀的属性,dataset是收集不到的。dataset. 后只需要写data-后的属性名即可,不用再写data-前缀

    • 查找带有自定义扩展属性的元素

      用自定义扩展属性作为条件,查找触发事件的元素,可用自定义扩展属性

      • CSS属性选择器

      [data-属性名=值]
      实例:

      <ul>
      

         

    • click me!
    •    

    • click me!
    •      ...

    ​ // 找到所有 i
    ​ var is=document.querySelectorAll(
          "ul>li>[data-toggle=tab]"
    ​ )

3、样式

  • 内联样式

elem.style.css属性名=“属性值”
注意:
1.有些css属性名中带 -,而程序中不能随意出现 -,会出现减号冲突,所以所有带 - 的css属性名必须去横线变驼峰(首字母小写,之后每个单词首字母大写);
2.如果属性值是长度或大小属性值,必需拼接 "px" 单位作为结尾,不能只写数值

  • 什么时候使用?

    专门用于修改内联样式,不影响其他元素的样式,优先级最高

  • 读取问题

    style如果用于获取样式属性值时,只能获得内联样式中的属性值。无法获得从样式表或浏览器默认样式中继承或重叠来的样式。所以,将来读取一个元素的完整css样式时,不能用 " 元素.style.css属性 "

  • 解决读取问题

    获取样式时,要用专门的方法getComputedStyle(元素),获得计算后的完整样式

    • 什么是计算样式?

    计算后的样式,最终应用到这个元素上的完整样式的集合,包括所有内联,内部,外部样式,将相对单位的值,计算为绝对单位(F12->Elements->Computed)

    • 什么时候使用?

    只要想获取css属性值,又不用担心丢失样式时,都用getComputedStyle(元素)

    • 先获得完整的style对象

    var style=getComputedStyle(elem对象)
    注意:getComputedStyle()所有浏览器自带,不用自己定义

    • 从style对象中获取想要的css属性值

    var value=style.样式属性名
    注意:
    1.计算后的样式,不仅收集这个元素的所有样式到一个对象中集中保存,而且还会将相对单位(em)以及不是数值的属性值,换算回绝对单位和数值方式;
    2.所有通过计算后的样式获得css属性都是只读的。不能修改。因为计算后的样式来源不确定,一旦修改,很可能牵一发而动全身

  • 修改问题

    使用元素.style.css属性一句话只能修改一个css属性,如果在一次效果变化中,需要同时修改这个元素的多个css属性,代码会很繁琐,且效率低

  • 解决修改问题

    如果想批量应用css属性,应该使用class,代替 “ 元素.style.css属性 ”

  • 内部/外部样式表(不建议)

提示:获取的是style标签中和link标签中的样式
实例:
// 获取页面中的所有样式
var sheet=document.styleSheets[i];
// 获取页面中的具体样式表,如果获得的是keyframes,就需要继续找子rule
var rule=sheet.cssRules[i];
// 通过rule下的style属性修改属性值
rule.style.样式属性=值

  • 最好的修改样式的方法

如果批量修改多个css属性时,首选用class,修改class属性,批量应用样式
实例:
var d1=ducment.getElementById("d1");
d1.className="disBlock";
d1.className="disNone";

  • 优化样式修改

因为每修改一次样式,都可能导致重排重绘网页,所以,应该尽量减少修改样式的次数,使用 “ 元素.style.cssText属性 ” 代替,单独设置的 “ 元素.style.css属性 ”
实例:
元素.style.cssText="width:100px; height:100px"
注意:此时浏览器只需要一次重排重绘网页,对比单独设置:
元素.style.width="100px"
元素.style.height="100px"
单独设置导致网页在短时间内进行两次重排重绘

四、添加和删除元素

1、添加

  • 创建新元素对象

var elem=document.createElement("标签名")
实例:创建一个页面元素对象
var a=document.createElement("a")

  • 设置关键属性

实例:为创建好的对象添加属性
a.href="url";
a.innerHTML="文本"
问题:此时新创建的元素都没有在DOM树上。自然也就无法显示在网页中

  • 将元素添加到DOM树

1.将新元素放在父元素下最后一个直接子元素后,末尾追加
实例:
parent.appendChild(child)
2.将新元素放在父元素下一个现有旧元素之前
实例:
parent.insertBefore(child,oldChild)
3.将新元素替换父元素下一个现有旧元素
实例:
parent.replaceChild(child,oldChild)

  • 添加问题

每操作一次DOM树,都会导致重排重绘。如果数据量大,又频繁操作DOM树,会导致重排重绘不过来

  • 解决添加问题

优化,尽量少的操作DOM树,如果同时添加父元素和子元素,应该先暂存内存中,将所有子元素添加到父元素,最后将父元素一次性添加到DOM树上,只需要重排重绘一次。如果父元素已经在DOM树上了,而要添加多个平级子元素,需要借助文档片段对象

  • 文档片段

    • 什么是文档片段对象?

    内存中临时存储多个子元素的虚拟父元素

    • 什么时候使用?

    当父元素已经挂在DOM树上了,而需要添加多个平级子元素的时候,就使用文档片段

    • 创建文档片段对象

    var frag=document.createDocumentFragment();

    • 将多个平级子元素临时添加到文档片段对象中

    frag.appendChild(child)

    • 将文档片段整体一次性添加到DOM树上指定父元素下

    parent.appendChild(frag)
    注意:frag不会成为页面元素,添加子元素后,frag自动释放

2、删除

parent.removeChild(child)

感觉今天学的没听懂。。。。

评论