DOM 编程
目录
- 前置知识
- DOM是一棵树
- 手写 DOM 库
前置知识
- 简单JS语法(变量、if else、循环)
- JS的七种数据类型
- JS的五个
Falsy值 - 函数、数组是对象
- 用
div和span标签 - 简单CSS布局
网页DOM是一棵树
JS如何操作这棵树
浏览器往window上加一个
document
在控制台中打
|
|
- JS用
document操作网页
即文档对象模型
Document Object Model
DOM很难用
具体操作
- 获取元素(标签)
- 节点的增删改查
获取元素(标签)
有很多API
window.id_xxx或者id_xxx,有可能取不到document.getElementById('id_xxx'),无#号document.getElementsByTagName('div')[0],伪数组document.getElementsByClassName('red')[0],伪数组document.querySelector('#id_xxx')document.querySelectorAll('.red')[0],伪数组
直接用 ID (省略 id)获取
|
|
如果 id 值为 window下已存在的属性,比如parent,只可用document.getElementById('parent')才能获取到,但工作中不使用这样与全局属性冲突的命名
判断用哪一个
- 工作中用
querySelector和querySelectorAll - 做
demo直接用id_xxx,千万别让人发现 - 兼容IE用
getElement(s)ByXXX,IE已死有事烧纸
|
|
获取特定的元素
获取
html元素
document.documentElement- 反人类
document.documentElement.tagName,返回的值HTML
获取
head元素
document.head
获取
body元素
document.body
获取窗口(不是元素)
window- 可以监听事件
window.onclick = ()=>{console.log('Hi')}
获取所有元素
document.all- 这个
document.all是个奇葩,第6个falsy值 - IE 专属,被用来判断是否 IE
- 隔绝 ie 代码
|
|
获取的元素是什么
显然是一个对象,需要搞清它的原型
拿
div对象举例
console.dir(div1)原型链
|
|
chrome显示错了,应该加xxx.prototype- 自身属性:
className、id、style等等
元素的六层原型链图解
- 第一层:
HTMLDivElement.prototype,这里面是所有div共有的属性,不用细看 - 第二层:
HTMLElement.prototype,这里面是所有HTML标签的共有属性,不用细看 - 第三层:
Element.prototype,这里面是所有XML、HTML标签的共有属性 - 第四层:
Node.prototype,这里是所有节点的共有属性,节点包括XML标签、文本、注释和HTML标签。文本,注释等等 - 第五层:
EventTarget.prototype,这里最重要的函数属性是addEventListener - 第六层,最后一层原型就是
Object.prototype了
div完整原型链
包括自身属性和共有属性
节点和元素
MDN有完整描述:Node.nodeType,xxx.nodeType得到一个数字节点
Node包括以下几种
1表示元素Element,也叫标签Tag3表示文本Text8表示注释Comment9表示文档Document11表示文档片段DocumentFragment
记住1和2即可
节点的增删改查
增:创建元素的API
创建一个标签节点
let div1 = document.createElement('div')document.createElement('style')document.createElement('script')document.createElement('li')
创建一个文本节点
let text1 = document.createTextNode('你好'),将字符串变为文本节点(对象)- text1-> Text-> CharacterData-> Node
|
|
标签里面插入文本
- Node 层提供的接口
div1.appendChild(text1),连接标签和文本节点,appendChild()的参数只接受节点类型type 'Node' div1.innerText = '你好'- Element层提供的接口
div1.textContent = '不送' - 但是注意不能混用:
div1.appendChild('走好') - 分别是6层不同原型上的不同接口
|
|
增(续)
插入并显示到页面中
- 创建的标签默认处于JS线程中
- 必须把它插入到
body或者head里面,才会生效 document.body.appendChild(div1)- 或者找到
已在页面中的元素(标签).appendChild(div) - 注意分清括号里的是否有引号
|
|
问题
appendChild()的独占性
- 代码
|
|
问:最终div出现在哪里?
- 答:出现在test2里,同一个元素(节点)不能出现在 DOM 的两个地方,除非复制一份(
let div2 = div1.cloneNode()),否则会被移走
|
|
删
两种方法
- 旧方法:
parentNode.removeChild(childNode),由Node提供(或者parentElement.removeChild(childNode)) - 新方法:
childNode.remove(),由Element提供,不支持 IE
|
|
思考
- 如果一个
node被移除页面(DOM树) - 那么它还可以再次回到页面中吗?
- 可以,只是移出页面,还存在与 JS 线程中
改属性
写标准属性
- 改
id:div2.id = 'div2' - 改
class:div.className = 'red blue' // 全覆盖,不于保留字冲突 div2 += ' red',注意有空格,加一个,不覆盖属性- 改
class:div.classList.add('red'),新 API - 改
style覆盖之前全部:div.style = 'width:100px;color:blue;',一般不用 - 改
style的一部分:div.style.width = '200px' div2.style.color = 'blue',改什么就写什么- 大小写法:
div.style.backgroundColor = 'white',JS 不支持含有-的 key,全部改为大小写组成的字符串 - 麻烦的写法:
div2.style['background-color'] = 'gray' - 改
data-*属性:div.dataset.xxx = 'fuck' div2.setAttribute('data-xxx','text内容')
不用
div2.class = 'red'
class为保留字,JS 对象不能用保留字作为key- 比如
idv.if非法只能用div2.className = 'blue'
加一个,不覆盖
|
|
读标准属性,获取
div.classList或a.href,或div2.dataset.xxxdiv.getAttribute('class')或a.getAttribute('href'),更保险- 两者皆可,但值可能不同
|
|
小结
改事件处理函数
div.onclick默认为null
运行原理
- 默认点击
div不发生任何事,应为并未赋值 - 如果把
div.onclick改为一个函数fn,点击会使浏览器调用它 - 这样调用的:
fn.call(div, event)的 div 会被当做this event则包含了点击事件的所有信息,比如坐标
|
|
div.addEventListener
- 是
div.onclick的升级版,就像推荐用.call(),只使用它
|
|
改内容
改文本内容
div.innerText = 'xxx'div.textContent = 'xxx',标准浏览器- 两者几乎没有区别
改HTML内容(包括文本和标签节点)
div.innerHTML = '<strong>重要内容</strong>',显示粗体的文本- 赋值给
innerHTML的字符过长会影响页面加载
|
|
改子标签
|
|
改父标签
|
|
查看元素的API
查自己:直接打出 id
查父标签:
parentNode或parentElement
node.parentNode或者node.parentElement
查祖标签:调两次
parentNode或parentElement
node.parentNode.parentElement
查子代标签
div.childNodes,返回伪数组NodeList[]node.childNodes或者node.children
|
|
- 为了解决不确定性,使用
Element提供的 API:.children,来代替Node提供的 API,childNodes - 即使有回车,也只返回元素(标签)
- 优先使用不包括文本节点的
.children
思考:当子代变化时,两者也会实时变化吗?
|
|
querySelectorAll不会实时响应页面的改变- 即获取一次后就不变
- 实时更新
- 区别于
computedStyle和getComputedStyle()
查兄弟标签
node.parentNode.childNodes并且排除自己,还得排除所有文本节点node.parentElement.children并且排除自己
遍历并排除自己
|
|
查(续)
查看老大
node.firstChild
查看老幺
node.lastChild
查看上一个兄弟
node.previousSiblingnode.previousElementSibling
查看下一个兄弟
node.nextSiblingnode.nextElementSibling
|
|
查所有
- 遍历一个元素里的所有标签
- 即数据结构中的遍历一棵树
|
|
- DOM 就是数据结构中的树
DOM跨线程操作
为什么 DOM 操作慢
- 浏览器分为渲染引擎和JS引擎
跨线程操作
各线程各司其职,互不相干
- JS引擎不能操作页面,只能操作JS window、DOM、BOM 对象
- 渲染引擎不能操作JS,只能操作页面
document.body.appendChild(div1)是通过跨线程通信,改变页面
跨线程通信
- 当浏览器发现JS在body里面加了个
div1对象 - 浏览器就会通知其渲染引擎在页面里也新增一个div元素
- 新增的div元素所有属性都照抄
div1对象
|
|
插入新标签的完整过程
在
div1放入页面之前
- 对于
div1所有的操作都属于JS线程内的操作
把
div1放入页面之时
- 浏览器发现JS的操作
- 然后通知渲染进程在页面中渲染
div1对应的元素
把
div1放入页面之后
- 对于
div1的操作都有可能会触发重新渲染 div1.id = 'newId'可能会重新渲染,也可能不会(新的 id 有不同的 CSS)div.title = 'new'根据浏览器内核的不同,有可能重新渲染- 如果连续多次对
div1操作,浏览器可能会合并成一次操作,也可能不会(动画的例子) - 创建过程(JS)-> 通信过程(浏览器)-> 渲染过程(渲染引擎)
|
|
|
|
此时
|
|
属性同步
- 看属性,下菜碟
标准属性
- 对
div1的标准属性的修改,会被浏览器同步到页面中 - 比如
id、className、title等
data-*属性
- 同上
非标准属性
- 对于非标准属性的修改,只会停留在JS线程中
- 不会同步到页面里
- 比如
x123属性:示例代码
|
|
操作JS
|
|
启示
- 如果有自定义属性,又想被同步到页面中,请使用
data-作为前缀
Property(JS执行线程) V.S. Attribute(渲染线程 HTML页面属性)
property属性
- JS线程中
div1的所有属性,叫做div1的property style、id、className
attribute也是属性
- 渲染引擎中
div1对应标签的属性,叫做attribute
区别
- 大部分时候吗,同名的
property和attribute的值相等 - 当不是标准属性,那么两者只会在一开始相等
attribute只支持字符串property支持字符串、布尔等类型()
|
|
网上都说 DOM 操作慢,实际上只是比 JS 操作慢,DOM 操作比网络请求还是快很多的。 关于这一部分内容,大家可以延伸阅读一些文章:
封装 DOM 库
BOM/DOM/Canvas/jQuery/Date/LocalStorage/RegExp/Lodash/Underscore/URL 处理
封装 DOM 实现下面三个接口:
|
|
GitHub Repo源代码链接
手写 DOM 库 代码:https://github.com/FrankFang/dom-demo-1/tree/master
参考文章
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河 掘 思 知 简