📔
饥人谷前端体系课程笔记
黑马程序员笔记前端面试押题前端精进
  • 课程大纲
  • Git入门
    • 3. 软件安装详解
    • 4. Mac环境搭建
    • 5. 命令行入门
    • 6. 本地仓库
    • 7. Git远程仓库-GitHub
  • HTML全解
    • 8. HTML概览
    • 9. HTML标签
    • 10. HTML重难点
    • 11. HTML实践 & 手机调试
  • CSS全解
    • 12. CSS基础
    • 13. CSS布局(上)
    • 14. CSS布局(下)
    • 15. CSS定位
    • 16. CSS动画
  • HTTP全解
    • 17. URL 是什么
    • 18. 请求和响应 & Node.js Server
  • JS全解
    • 19. JavaScript概览
    • 20. 内存图与JS世界(精品课)
    • 21. Canvas 实践—画图板
    • 22. JS语法
    • 23. JS数据类型
    • 24. JS 对象
    • 25. JS 对象分类
    • 26. JS 数组
    • 27. JS 函数
    • 28. JS 实战,会动的代码
    • 29. JS运算符
    • 30. JS总结
  • JS编程接口
    • 31. DOM编程
    • 32. 手写DOM库
    • 33. JQuery中的设计模式(上)
    • 34. JQuery中的设计模式(下)
    • 35. DOM事件与事件委托
  • 项目 前端导航站点
    • 前端导航项目笔记
  • 前后分离
    • 40. AJAX的原理
    • 41. 异步与Promise
    • 42. 跨域、CORS、JSONP
    • 43. 静态服务器
    • 44. AJAX实战:Cookie、Session
  • JS进阶MVC
    • 48. MVC(上)
    • 49. MVC(中)
    • 50. MVC(下)
  • 项目构建
    • 51. Webpack(上)
    • 52. Webpack(下)
  • 算法与数据结构
    • 62. 伪代码与流程图
  • React全解
    • 75. React起手式
    • 76. React类组件和函数组件
    • 77. Class组件详解
    • 78. 函数组件
    • 79. Hooks原理解析
    • 80. Hooks各个击破
    • 81. 精通Redux
  • Node.js
    • 84. Node.js技术架构
    • 85. 文件模块
    • 86. 单元测试之文件模块
    • 87. 调试 Node.js 程序
    • 88. 静态服务器
    • 89. 命令行翻译工具
    • 90. 操作数据库
    • 91. 数据库基础知识
    • 92. Stream 流
    • 93. child_process
    • 94. 总结
  • TypeScript
    • 112. 基础
    • 113. 泛型
  • Next.js全解
    • Next.js 上
    • Next.js 下
  • ES6精讲
    • Promise,async/await
  • 大屏可视化笔记
    • 大屏可视化项目
  • SCSS全解
    • SCSS全解
  • 拓展
    • 一次性弄懂性能优化
    • Web性能优化
由 GitBook 提供支持
在本页
  • 获取元素
  • 获取特定元素
  • 元素的6层原型链
  • 节点的增删改查
  • 删
  • 改属性
  • 改事件处理函数
  • 改内容
  • 查
  • 跨线程操作
  • 插入新标签的完整过程
  • 属性同步
  • Property vs Attribute

这有帮助吗?

  1. JS编程接口

31. DOM编程

上一页JS编程接口下一页32. 手写DOM库

最后更新于2年前

这有帮助吗?

网页其实是一棵树。

image-20220515013517389

JS如何操作这棵树?

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

window.document

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

window.readIcon
//或把window省略
readIcon

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

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

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

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

获取元素

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

document.getElementById('idxxx')
document.getElementsByTagName('div')[0]
document.getElementsByClassName('red')[0]
document.querySelector('#idxxx')
document.querySelectorAll('.red')[0]

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

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

工作中用querySector和querySectorAll

例子:

document.querySelector('div>span:nth-child(2)')
document.querySelectorAll('div>span:nth-child(2)')//选出所有满足条件的元素
document.querySelectorAll('div>span:nth-child(2)')[0]//选中第一个
document.querySelectorAll('div>span:nth-child(2)')[1]//选中第二个

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

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

获取特定元素

  • 获取html元素

    • document.documentElement

  • 获取head元素

    • document.head

  • 获取body元素

    • document.body

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

    • window

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

      window.onClick = () => {console.log('hi')}

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

  • 获取所有元素

    • document.all

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

      if(document.all){
        console.log('3')
      }else{
        console.log('4')
      }

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

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

元素的6层原型链

获取到的元素是个啥?

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

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

let div = document.getElementsByTagName('div')[9]
console.dir(div)
div.__proto__ === HTMLDivElement
//false

div.__proto__ === HTMLDivElement.prototype
//true
//一个对象的下划线proto等于它的构造函数的prototype

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

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

  • 第一层原型 HTMLDivElement.prototype

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

  • 第二层原型 HTMLElement.prototype

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

  • 第三层原型 Element.prototype

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

只能展示 HTML吧

  • 第四层原型 Node.prototype

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

HTML 标签文本注释等等

  • 第五层原型 EventTarget.prototype

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

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

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

head.requestFullscreen() //head是#head

Node和Element的区别

节点Node包括以下几种:

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

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

  • 3表示文本 Text

  • 8表示注释 Comment

  • 9表示文档 Document

  • 11 表示文档片段 DocumentFragment

记住1和2即可

例子:

div.nodeType
div.childNodes
div.firstChild.textContent
div.firstChild.nodeType

节点的增删改查

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

增

  • 创建一个标签节点

let div1 = document.createElement('div")
document.createElement('style')
document.createElement('script')
document.createElement("ti")
  • 创建一个文本节点

let text1 = document.createTextNode('你好")
  • 标签里面插入文本,你好是一个字符串,text1是一个对象

div1.appendChild(text1)
divl.innerText ="你好 或者 div1.textContent="你好"

但是不能用 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

let div2 = div1.cloneNode()
div2 = div1.cloneNode(true)

删

两种方法

  • 旧方法: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的升级版

改内容

改文本内容

div.innerText = 'xxx'

div.textContent = 'xxx'

两者几乎没有区别

改HTML内容

div.innerHTML ='<strong>重要内容</strong>'

改标签

div.innerHTML = "//先清空
div.appendChild(div2)//再加内容

改爸爸

想要找一个新爸爸?

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里面的所有元素

travel = (node,fn) => {
	fn(node);
	if(node.children){
		for(let i = 0; i < node.children.length;i++){
			travel(node.children[i],fn)
		}
	}
}
travel(div1,(node) => console.log(node))

看数据结构多么有用

DOM操作是跨线程的

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

跨线程操作

各线程各司其职

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

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

  • document.body.appendChild(div1)

  • JS是如何改变页面的?

跨线程通信

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

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

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

图示跨线程操作

插入新标签的完整过程

在div1放入页面之前

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

把div1放入页面之时

浏览器会发现JS的意图

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

把div1放入页面之后

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

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

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

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

属性同步

标准属性

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

比如id,className,title等

*data-属性

同上

非标准属性

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

不会同步到页面里

比如x属性,示例代码

启示

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

图示

Property vs Attribute

property属性

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

attribute也是属性

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

区别

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

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

  • 但注意attribute只支持字符串

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

截屏2023-02-13 下午4.12.50
截屏2023-02-13 下午4.22.55
div 原型链