什么是 DOM 编程
我们使用HTML写的网页其实是一棵树,是由 html 标签不断向下细分很多个子节点,最终形成的一棵树
而 JS 需要操作这棵树,于是浏览器在 window 上添加了一个 document,而 DOM(Document Object Model)的意思就是 JS 用 document 来操作这棵网页树,我们使用window.document就可以获取整个网页的所有元素
怎么使用 DOM (获取元素的 API)
获取方法
-
当一个元素拥有ID,那么就可以直接用window.XXX(这个ID)获取到这个元素
- window是可以省略的
- 有时ID会和全局属性冲突,这种情况下第一种方法就不能用了
- 这种情况下document.getElementById('XXX')(ID名)可以获取到这个元素并且不冲突(平常时候这个方法没啥用还繁琐)
-
document.getElementsByTagName('div')[0](找到页面中所有div元素,方括号表示对其中第几个进行操作。)
-
document.getElementsByClassName('red')[0](找到页面中所有Class为red的元素,方括号意义同上)
- 2、3方法只有需要兼容IE的时候才会使用,平常他们又长又难用,没人会用
-
document.querySelector('#IDxxx')(找到ID为XXX的元素)
-
document.querySelectorAll('#IDxxx')[0](找到所有满足这个条件的元素,后面的方括号表示要用里面的第几个元素)
获取特定元素
- 获取HTML标签:document.documentElement
- 获取head元素:document.head
- 获取body元素:document.body
- 获取窗口(窗口不是元素):window
- 获取所有元素:document.all(这句话只在IE浏览器中运行为真,其他浏览器运行都为假。可以利用这一特点用if....else语句来写一些只对IE生效的代码)(但是如果不进行判断直接用这句话则一点问题都没有)
- 这也是JS的第六个falsy值
获取到的元素是个啥?(元素的6层原型链)
首先我们抓来一只野生 div 对象来看看(console.dir(div)来查看原型链)
- 第一层原型 HTMLDivElement.protopyte
- 这里面是所有 div 共有的属性,不用细看
- 第二层原型 HTMLElement.protopyte
- 这里面是所有 HTML 标签的属性,不用细看
- 第三层原型 Element.protopyte
- 这里面是所有 XML 、 HTML 标签的共有属性。你不会以为浏览器只会展示 HTML 吧
- 第四层原型 Node.protopyte
- 这里面是所有节点共有的属性,节点包括 XML 标签文本注释、HTML 标签文本注释等等
- 第五层原型 EventTarget.protopyte
- 里面最重要的函数属性是addEventListener
- 最后一层原型就是 Object.protopyte了
怎么创建元素的 API
节点的增
创建一个标签节点
let div1 = document.createElement('div')(创建一个div标签)- 同理,style、script、li标签都是这样创建
创建一个文本节点
text1 = document.createTextNode('你好')(创建一个“你好”的文本节点)- 为什么不直接用你好来创建呢?因为直接创建的话你好就是一个字符串,而用节点的方式创建出来你好则为节点
如何在标签里面插入文本
- 第一种方法:
div1.appendChild(text1)(要注意的是标签节点和文本节点都已经创建完成) - 第二种方法:
div1.innerText='你好'或者div1.textContent='你好'(同理,标签节点和文本节点都要已经创建完成)- 两种方法是不同接口提供的不同方法,第一种方法是由Element提供的,第二种方法是由Node提供的
创建的标签、文本节点如何插入到页面中
- 创建的标签默认处于JS线程中,必须把它插入到head或者body中才能生效
- 插入方法:
document.body.appentChild(div1)或者已在页面中的其他元素.appent.Child(div1)
- 插入方法:
appendChild需要注意的地方
- 假如页面中有div#text1和div#text2
let div1 = document.createElement('div')
text1.appendChild(div)
text2.appendChild(div)
最终div出现在text2里面,因为text2是后写入的,而一个元素不能同时出现在两个地方,除非复制一份。所以最终出现在text2中
克隆节点
let div2 = div1.cloneNode(true)(深度克隆,该元素内所有子元素都会被克隆)let div2 = div1.cloneNode(false)(浅克隆,只克隆该节点本身)
节点的删除
两种方法
- 第一种:
div1.parentNode.removeChild(div1)(告诉它父亲删除它,这种方法只是把节点从树里面删除掉,实际上还在内存中,随时可以加回来) - 第二种:
div1.remove()(这种方法很简便,但是不兼容IE。这种方法也只是把节点从页面中移除,但还在内存中) - 如何彻底删掉节点?(首先要确定该节点已经从页面中移除)
div1 = null
改属性
写标准属性
- 改ID:
div2.id = 'div2' - 改class:
div.className = 'red'(这种方法会覆盖之前的属性)。div.className += ' red'(这样写不会覆盖,会在原有基础上后加新的属性)- 新语法:
div.classList会查看当前div当前所有class。例如div.classList.add('red')(不覆盖原有属性的基础上加入新属性)
- 新语法:
- 改style:
div.style = 'color:bule'(这样会覆盖之前内容)- 可以这样写:
div.style.width = '200px'(只修改style中的width) - JS不支持‘-’符号,有这样符号的时候只需要删除这个符号,然后把符号后面的字母写成大写就可以了
- 可以这样写:
读属性
div.id(大部分属性都可以这样读取,直接写出来就行)div.classList(class读取和别的不太一样)
改内容
改文本内容
div.innerText = 'hi'(修改div里面的文本内容)div.textContent = 'hi'(同上,二者几乎没有区别)
改HTML内容(标签里面加或改标签)
div.innerHTML = '<strong>你好</strong>(在div里加一个strong标签,要注意HTML全是大写)
改标签(删掉这个标签换成别的标签)
div.innerHTML = ''(先清空内容)div.appendChild(div2)(加入新的标签)
改爸爸(让自己去另一个标签里面)
newParent.appendChild(div)
改事件处理函数
div.onclick可以给div添加一个函数,点击div可以调用这个函数,默认值为null,点击不会发生任何事情text.onclick = function( ) {}(text是这个div的id)(onclick赋予函数可以让这个div点击后运行该函数)- 调用原理是fn.call(div,event)(div是该元素,event是事件的所有详细信息)
查看元素的API
查爸爸
div2.parentNodediv2.parentElement
查爷爷
div2.parentNode.parentNode(就是查爸爸调用两次)div2.parentElement.parentElement
查儿子
div2.childNodes(要注意nodes有一个s,因为儿子可能有很多)- 要注意获取子元素的时候有时会获取到你不想要的东西,比如空格和回车也会算作子元素
div2.children(这种方法不会获取到空格回车之类的无聊的玩意,推荐使用)
查兄弟姐妹
div2.parentElement.children(先获取父元素,再获取父元素的子元素,这样自己也会包含在里面,还需要遍历一遍所有元素,把自己删出去)- 排除自己的方法:
let siblings = [] let c2 = div2.parentElement.children for(let i = 0;i<c2.length;i++){ ifc2[i]!==div2{ siblings.push(c2[i]) } }
查看第一个儿子
document.body.firstChild(查看body中的第一个儿子)
查看最后一个儿子
document.body.lastChild(查看body中的最后一个儿子)
查看离我最近的哥哥姐姐
div2.previousSibling(查看div2前面最近的一个元素)要注意这个方法可以查找到文本节点,也就是说如果前面有个回车的话也会被查找出来div2.previousElementSibling(避开文本节点,只寻找元素节点)
查看离我最近的弟弟妹妹
div2.nextSibling(查看div2后面最近的一个元素)要注意这个方法可以查找到文本节点,也就是说如果后面有个回车的话也会被查找出来div2.nextElementSibling(避开文本节点,只寻找元素节点)
DOM操作跨线程
- 浏览器分为渲染引擎和JS引擎
- 各线程各司其职,JS引擎只能操作JS,渲染引擎只能渲染
跨线程通信
- 当浏览器发现JS在body里面加了一个div1对象
- 浏览器会通知渲染引擎在页面上也新增一个div元素
- 浏览器通知引擎所消耗的时间会较多,所以跨线程通信会比较慢
- 跨线程通信图示:
插入新标签的完整过程
- 在div1放入页面之前
- 你对它的所有操作都属于JS线程内的操作
- 把div1放入页面之时
- 浏览器会发现JS的意图
- 浏览器会通知渲染线程在页面中渲染div1对应的元素
- 把div1放入页面之后
- 你对div1的操作都有可能触发重新渲染
- 如果你连续对div1进行多次操作,浏览器可能会合并成一次操作,也可能不会,不同浏览器可能有不同逻辑
- 如果不想合并,就需要中间插入一个强制执行渲染,再继续后面操作的一个操作
属性同步
标准属性
- 对div1的标准属性的修改,会被浏览器同步到页面中
data-***
- 同上
非标准属性
- 对非标准属性的修改,只会停留在JS线程中,不会同步到页面里
启示
- 如果你要写自定义属性,又想同步到页面中,请使用data- 作为前缀
JS线程中的属性我们一般称为properties,渲染线程中的属性我们一般称为attributes
- JS线程中div1的所有属性,叫做div1的properties
- 渲染线程中div1对应标签的属性,叫做attributes
区别
- 大部分时候,同名的 properties和attributes值相等
- 但如果不是标准属性,则只会在最开始时相等,后面修改不会同步
- 要注意attributes只支持字符串,其他一切东西都会自动转化成字符串
- 而properties支持的就很多了,比如数字、布尔等等