31. DOM编程

网页其实是一棵树。

image-20220515013517389

JS如何操作这棵树?

浏览器往window上加一个document即可。

或者也可以通过id来获取元素,比如网页上(baidu.com)某个元素的id是readIcon,可以通过下面两种方式获取元素。

JS用document操作网页,这就是Document Object Model文档对象模型。

记住一个事实,DOM很难用。

如果你觉得DOM很傻,不要怀疑自己,你觉得的是对的。

DOM自带的功能非常反人类,所以一开始用jQuery来操作DOM,再后来用Vue和React来操控DOM。

获取元素

除了window.idxxx或者直接idxxx之外,还有其他方法来获取元素。比如:

那么问题来了,当你知道可以通过window.idxxx和idxxx就可以直接获取元素,还会使用上面繁琐冗长的写法吗?

会,某些情况下会,当你的id和全局属性冲突的时候下还是需要用到上面获取元素的方法的,比如window.parent

工作中用querySectorquerySectorAll

例子:

要兼容IE的可怜虫才用getElementsByXXX。

做demo直接用idxxx,千万别让人发现。

获取特定元素

  • 获取html元素

    • document.documentElement

  • 获取head元素

    • document.head

  • 获取body元素

    • document.body

  • 获取窗口(窗口不是元素)

    • window

      有什么用呢,可以用来监听事件

      此时点击页面,就能看到console出hi

  • 获取所有元素

    • document.all

    • 这个document.all是个奇葩,第6个falsy值

      上面这段代码打印出来的是4,为什么呢?

      因为document.all是IE发明出来的,早期的程序员通过document.all存不存在来区分是否是IE浏览器。

元素的6层原型链

获取到的元素是个啥?

显然是一个对象,我们需要搞清它的原型。

console.dir可以打印出元素的结构/看原型链

告诉你一个秘密,Chrome 显示错了

自身属性:className、id、style 等等

  • 第一层原型 HTMLDivElement.prototype

这里面是所有 div 共有的属性,不用细看

  • 第二层原型 HTMLElement.prototype

这里面是所有 HTML标签共有的属性,不用细看

  • 第三层原型 Element.prototype

这里面是所有 XML、HTML 标签的共有属性,你不会以为浏览器

只能展示 HTML吧

  • 第四层原型 Node.prototype

这里面是所有节点共有的属性,节点包括 XML 标签文本注释、

HTML 标签文本注释等等

  • 第五层原型 EventTarget.prototype

里面最重要的函数属性是 addEventListener

  • 最后一层原型就是 Object.prototype了

div 原型链

例子:在Element.prototype里有个requestFullscreen()可以用来请求全屏

Node和Element的区别

节点Node包括以下几种:

MDN 有完整描述,x.nodeType 得到一个数字

  • 1表示元素 Element, 也叫标签 Tag

  • 3表示文本 Text

  • 8表示注释 Comment

  • 9表示文档 Document

  • 11 表示文档片段 DocumentFragment

记住1和2即可

例子:

节点的增删改查

程序员的宿命就是增删改查

  • 创建一个标签节点

  • 创建一个文本节点

  • 标签里面插入文本,你好是一个字符串,text1是一个对象

但是不能用 div1.appendChild(你好)

  • 插入页面中

你创建的标签默认处于JS线程中

你必须把它插到head或者body里面,它才会生效

document.body.appendChild(div)

或者已在页面中的元素.appendChild(div)

appendChild

  • 代码

页面中有 div#test1 和div#test2 let div = document.createElement ( 'div') test1.appendChild(div) test2. appendChild(div) ,请问最终 div 出现在哪里? 1.test1 里面 2.test2 里面 3.test1 里面和test2 里面

答案是2(送子观音)

一个元素不能出现在两个地方,除非复制一份

如何深拷贝node

两种方法

  • 旧方法:parentNode.childChild(childNode)

  • 新方法:childNode.remove()

思考

如果一个node被移除页面(DOM树)

那么它还可以再次回到页面中吗?

改属性

写标准属性

改class:div.className = 'red blue'(全覆盖)

改class:div.classList.add('red')

改style:div.style = 'width:100px; color:blue;'

改style的一部分: div.style.width = '200px'

大小写: div.style.backgroundColor = 'white'

改data-*属性:div.dataset.x = 'frank'

读标准属性

div.classList / a.href

div.getAttribute('class') / a.getAttribute('href')

两种方法都可以,但值可能稍微有些不同

改事件处理函数

div.onclick默认为null

默认点击div不会有任何事情发生,但是如果你把div.onclick改为一个函数fn,那么点击div的时候,浏览器就会调用这个函数,并且是这样调用的fn.call(div,event), div会被会被当作this,event则包含了点击事件的所有信息,比如说坐标。

div.addEventListener

是div.onclick的升级版

改内容

改文本内容

两者几乎没有区别

改HTML内容

改标签

改爸爸

想要找一个新爸爸?

newParent.appendChild(div)

直接这样就可以了,直接从原来的地方消失

查爸爸

node.parentNode或者node.parentElement

查爷爷

node.parentNode.parentNode

查子代

node.childNodes或者node.children

思考:当子代变化时,两者也会实时变化吗?

查兄弟姐妹

node.parentNode.childNodes还要排除自己

node.parentNode.children还有排除自己

查看老大

node.firstChild

查看老幺

node.lastChild

查看上一个哥哥/姐姐

node.previousSibling

查看下一个弟弟/妹妹

node.nextSibling

遍历一个div里面的所有元素

看数据结构多么有用

DOM操作是跨线程的

还记得《JS世界》里讲的浏览器功能划分吗?浏览器分为渲染引擎和JS引擎。

跨线程操作

各线程各司其职

  • JS引擎不能操作页面,只能操作JS

  • 渲染引擎不能操作JS,只能操作页面

  • document.body.appendChild(div1)

  • JS是如何改变页面的?

跨线程通信

  • 当浏览器发现JS在body里面加了个div1对象

  • 浏览器就会通知渲染引擎在页面里也新增一个div元素

  • 新增的div元素所有属性都照抄div1对象

图示跨线程操作

截屏2023-02-13 下午4.12.50

插入新标签的完整过程

在div1放入页面之前

你对div1所有的操作都属于JS线程内的操作

把div1放入页面之时

浏览器会发现JS的意图

就会通知渲染线程在页面中渲染div1对应的元素

把div1放入页面之后

你对div1的操作都有可能会触发重新渲染

div1.id = 'newId' 可能会重新渲染,也可能不会

div1.title = 'new', 可能会重新渲染,也可能不会

如果你连续对div1多次操作,浏览器可能会合并成一次操作,也可能不会

属性同步

标准属性

对div1的标准属性的修改,会被浏览器同步到页面中

比如id,className,title等

*data-属性

同上

非标准属性

对非标准属性的修改,则只会停留在JS线程中

不会同步到页面里

比如x属性,示例代码

启示

如果你有自定义属性,又想被同步到页面中,请使用data-作为前缀

图示

截屏2023-02-13 下午4.22.55

Property vs Attribute

property属性

JS线程中div1的所有属性,叫做div1的property

attribute也是属性

渲染引擎中div1对应标签的属性,叫做attribute

区别

  • 大部分时候,同名的property和attribute值相等

  • 但如果不是标准属性,那么它俩只会在一开始时相等

  • 但注意attribute只支持字符串

  • 而property支持字符串,布尔登类型

最后更新于

这有帮助吗?