浏览器环境,规格

浏览器的构成部分:

  • 文档对象模型DOM,也就是document对象,是页面的入口点
  • 浏览器对象模型BOM,提供了诸如 navigator, location 等对象
  • JavaScript本身

image.png

DOM树

根据文档对象模型DOM,每个HTML标签都是一个对象,嵌套的标签是父级标签的子标签,所以我们有 children 这个属性可以访问。嵌套的文本也是一个对象。

document.body 表示通过JS访问body标签的对象

文本节点的特殊字符

  • 换行符↵
  • 空格␣

都是有效的字符,是DOM的一部分。是一个#text节点。但是有空格,换行有2个顶级忽略。

  • head标签前
  • 标签后

    自动补全标签

    浏览器会帮我们自动补齐标签。
    比如未关闭的标签 ```html

    hello

=>

hello

  1. <a name="cyk19"></a>
  2. ## 其他类型节点
  3. 比如注释节点,也是一个节点类型
  4. ```html
  5. <ol>
  6. <li>An elk is a smart</li>
  7. <!-- comment -->
  8. <li>...and cunning animal!</li>
  9. </ol>

一共有12种节点类型,常用的有4种

  • document
  • 元素节点,div, p..
  • 文本节点
  • 注释节点

    遍历DOM

    image.png

    最顶层documentElement和body

  • <html> = document.documentElement

  • <body> = document.body
  • <head> = document.head

    子节点childNodes, firstChild, lastChild

  • 子节点,指的是直系子元素

  • 子孙元素,子元素及子元素的子元素

    childNodes

    是一个集合,包含所有子节点, 包括文本节点。

    可迭代

    因为是一个集合,一个类数组的可迭代对象,所以我们可以迭代它

    1. for (let node of document.body.childNodes) {
    2. console.log(node)
    3. }

    可转换为真正的数组

    转换后可以使用数组方法

    1. Array.from(document.body.childNodes).filter

    兄弟节点,父节点,快捷第一个最后一个

  • nextSibling 下一个

  • previousSibling 上一个
  • parentNode 父节点
  • firstChild lastChild

    纯元素导航(真正想要的)

    上面所说的所有dom导航api,获取的节点都包含了文本,注释等等。而通常我们希望获取真真的元素节点(标签节点)
    从图上看到,在之前的api增加 element 字眼就可以了。

  • 所有子元素 children

  • 父元素 parentElement
    • 一个特例 document.documentElement.parentElement = null ,而 document.documentElement.parentNode = document(整个文档),因为 parentElement 不认为整个文档是一个元素节点!
  • 前一个兄弟元素 previousElementSibling
  • 后一个兄弟元素 nextElementSibling
  • 第一个和最后一个元素 firstElemenetChild lastElementChild

image.png

特定导航

方便起见,某些类型的DOM元素提供了特定的属性,方便我们访问相关元素,比如Table

  • table.rows 获取所有 tr 元素的集合
  • table.caption/tHead/tFoot 获取元素 <caption> <thead> <tfoot>
  • table.tBodies 获取 <tbody> 的集合,没错,可以有多个
  • thead tbody tfoot 里也有大量的 rows ,也就是行,我们也可以获取。 tbody.rows
  • <tr> 标签也有对应的属性

    • tr.cells 获取单元格集合
    • tr.sectionRowIndex 当前 trthead tbody 或 tfoot 的索引位image.png
    • tr.rowIndex 在整个表格中的 tr 的编号

      1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/241313/1628695755357-6569eaad-1492-4dab-93df-97068e310ee3.png#height=537&id=f76XE&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1074&originWidth=692&originalType=binary&ratio=1&size=86411&status=done&style=none&width=346)

      总结

      dom的导航(访问dom节点)有2组

  • 一组访问所有节点(文本,注释,标签):

    • childNodes
    • parentNode
    • firstChild, lastChild
    • previousSibling, nextSibling
  • 一组访问元素节点(只有标签)
    • children
    • parentElement
    • firstElementChild, lastElementChild
    • previousElementSibling, nextElementSibling

      特殊的访问属性

      如table,还有form

      DOM搜索

      document.getElementById或直接id引用

      ```javascript // index.html

// index.js document.getElementById(‘a’) a // 直接id也可以访问到,但是不安全,如果有全局同名变量,变量优先。

  1. <a name="pbRvF"></a>
  2. ## querySelectorAll 和 querySelector
  3. `querySelectorAll(css pattern)` 十分强大,返回符合参数要求的所有元素的集合, `querySelector` 只查找第一个匹配到的。
  4. 强大的原因在于接收的参数是一个css查询器,灵活
  5. ```javascript
  6. <ul>
  7. <li>The</li>
  8. <li>test</li>
  9. </ul>
  10. <ul>
  11. <li>has</li>
  12. <li>passed</li>
  13. </ul>
  14. <script>
  15. let elements = document.querySelectorAll('ul > li:last-child');
  16. for (let elem of elements) {
  17. alert(elem.innerHTML); // "test", "passed"
  18. }
  19. </script>

matches

检查elem是否与给定css选择器匹配,返回boolean

  1. elem.matches('a[href$="zip"]')

closest

elem.closest(css) 方法会查找与css选择器匹配的最近的祖先,elem自己也会被搜索。

  1. <h1>Contents</h1>
  2. <div class="contents">
  3. <ul class="book">
  4. <li class="chapter">Chapter 1</li>
  5. <li class="chapter">Chapter 1</li>
  6. </ul>
  7. </div>
  8. <script>
  9. let chapter = document.querySelector('.chapter'); // LI
  10. alert(chapter.closest('.book')); // UL
  11. alert(chapter.closest('.contents')); // DIV
  12. alert(chapter.closest('h1')); // null(因为 h1 不是祖先)
  13. </script>

历史api

以下返回的都是集合

  • getElementsByClassName
  • getElementsByName
  • getElementsByTagName

    实时集合与静态集合

    getElementsBy* 获取的是实时集合,而 querySelector(All) 获取的是静态集合。 ```javascript
    First div
Second div
``` ```javascript
First div
Second div

  1. <a name="GewIX"></a>
  2. # 节点属性type, tag和content
  3. <a name="xGOE5"></a>
  4. ## DOM节点类
  5. 此图一解一直困惑的为什么一个dom节点有那么多属性,而且像 `div` ,在TS里,可以是 `HTMLElement` , 也可以是 `HTMLDivElement` 。这些都跟DOM节点的内建类有关!<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/241313/1628780107481-4da67647-7c0f-4856-8fd3-16cb1ce265d7.png#height=388&id=jw7mS&margin=%5Bobject%20Object%5D&name=image.png&originHeight=776&originWidth=1142&originalType=binary&ratio=1&size=69917&status=done&style=none&width=571)<br />所有的DOM都继承自最上面的 `EventTarget` , `Element` 又被很多具体的标签类继承。
  6. - EventTarget,让所有dom节点都支持了事件对象event
  7. - Node,节点抽象类,如 `parentNode, nextSibling` 这类api,由此提供
  8. - Element,元素抽象类,提供了如 `nextElementSibling, children` 等dom导航api,也提供了 `getElementsBy*, querySelector` 这样的搜索方法。
  9. - HTMLElement,所有HTML元素的基本类,具体的HTML元素都继承自它,如 `HTMLInputElement`
  10. 一层层的继承,让一个具体的标签DOM,获取很多属性和方法,按继承顺序叠加
  11. ```javascript
  12. // 例如一个input标签
  13. HTMLInputElement -> HTMLElement -> Element -> Node -> EventTarget -> Object(万物皆对象)

我们可以通过 instanceof 检测

a instanceof xx.prototype a的原型链上存不存在 xx的原型对象

  1. console.log(document.body.toString()) // [object HTMLBodyElement]
  2. document.body instanceof HTMLElement // true
  3. document.body instanceof Element // true
  4. document.body instanceof Node // true
  5. document.body instanceof EventTarget // true

nodeType判断节点类型

曾经用该方法,判断某个节点返回值类型是什么,这个方法已经过时了。

  1. elem.nodeType // 1 元素 3 文本 9 document对象本身

标签nodeName和tagName

tagName 适用 Elemenet 类定义的
nodeName 适用 Node 类定义的,因为Element继承自Node,所以对于元素来说,nodeName === tagName。 对于text, comment的话,有自己的nodeName。

  1. <body><!-- comment -->
  2. <script>
  3. // for comment
  4. alert( document.body.firstChild.tagName ); // undefined(不是一个元素)
  5. alert( document.body.firstChild.nodeName ); // #comment
  6. // for document,注意:document不是元素!!!
  7. alert( document.tagName ); // undefined(不是一个元素)
  8. alert( document.nodeName ); // #document
  9. </script>
  10. </body>

tagNamenodeName 对于元素,返回值都是大写的标签

  1. document.body.tagName // BODY
  2. document.body.nodeName // BODY

innerHTML, outerHTML

前者是元素里的所有
后者包括元素直接外层(closet parent),元素自身,元素里

nodeValue/data

innerHTML只对元素有效,这个对节点有效

  1. <body>
  2. Hello
  3. <!-- Comment -->
  4. <script>
  5. let text = document.body.firstChild;
  6. alert(text.data); // Hello
  7. let comment = text.nextSibling;
  8. alert(comment.data); // Comment
  9. </script>
  10. </body>

textContent

获取文本内容,节点内所有文本。
可以以安全方式写入文本,避免innerHTML

  1. <div id="news">
  2. <h1>Headline!</h1>
  3. <p>Martians attack people!</p>
  4. </div>
  5. <script>
  6. // Headline! Martians attack people!
  7. alert(news.textContent);
  8. </script>

hidden

style = "dispaly: none" 做相同的事,更简洁。

特性和属性

dom属性

属性和JS对象是相似的,我们可以给dom对象随意增加自定义属性,方法。

  1. document.body.sayTagName = function() {
  2. alert(this.tagName);
  3. };
  4. document.body.sayTagName();
  5. // 直接动Element.prototype
  6. Element.prototype.sayHi = function() {
  7. alert(`Hello, I'm ${this.tagName}`);
  8. };
  9. document.documentElement.sayHi(); // Hello, I'm HTML
  10. document.body.sayHi(); // Hello, I'm BODY

HTML特性

标准特性可转换为DOM属性

标签拥有的特性,浏览器解析HTML,会根据标签创建DOM对象,辨别标准的特性,是,则创建对应的dom属性。

  1. <body id="test" something="non-standard">
  2. <script>
  3. // id是标准的特性,转换为dom的属性
  4. alert(document.body.id); // test
  5. // 非标准的特性没有获得对应的属性
  6. alert(document.body.something); // undefined
  7. </script>
  8. </body>

不同标签,不同特性

每个标签有自己的特性,一个元素的特性对另一个元素来说可能就是不存在的。

  1. <body id="body" type="...">
  2. <input id="input" type="text">
  3. <script>
  4. alert(input.type); // text
  5. alert(body.type); // undefined:DOM 属性没有被创建,因为它不是一个标准的特性
  6. </script>
  7. </body>

所以非标准特性不会转换为DOM属性。

如何访问非标特性

可以通过获取属性的api

  • hasAttribute
  • getAttribute
  • setAttribute
  • removeAttribute

    特性不区分大小写

    1. <div id="1" ID="1" />
    会自动全部转为小写。

    属性和特性的同步

    当标准特性被修改,对应属性也会自动更新,反之亦然。除了部分特性,如 input 的value。 ```javascript let input = document.querySelector(‘input’);

// 特性 => 属性 input.setAttribute(‘id’, ‘id’); alert(input.id); // id(被更新了)

// 属性 => 特性 input.id = ‘newId’; alert(input.getAttribute(‘id’)); // newId(被更新了

  1. 看下inputvalue
  2. ```javascript
  3. <input>
  4. <script>
  5. let input = document.querySelector('input');
  6. // 特性 => 属性
  7. input.setAttribute('value', 'text');
  8. alert(input.value); // text
  9. // 这个操作无效,属性 => 特性
  10. input.value = 'newValue';
  11. alert(input.getAttribute('value')); // text(没有被更新!)
  12. </script>

因为用户的行为会导致value的更改(引起input的属性value的变化),而特性一直是原始值,不会随用户输入改变而改变。

DOM属性类型是多种多样的

如复选框

  1. <input id="input" type="checkbox" checked>
  2. // input.checked true
  3. // input.getAttribute('checked') 空字符串

非标准的特性,dataset

  1. // 因为特性可以是非标准的,我们可以用来做一些奇奇怪怪的事
  2. <div show-info="name"></div>
  3. // 通过querySelector获取
  4. // 这里参数是CSS查询器
  5. let d = document.querySelector('[show-info=name]')
  6. // 设置其值
  7. d.innerHTML = 'Jack'
  1. // 利用特性来做css类的工作
  2. <style>
  3. /* 样式依赖于自定义特性 "order-state" */
  4. .order[order-state="new"] {
  5. color: green;
  6. }
  7. </style>
  8. <div class="order" order-state="new">
  9. A new order.
  10. </div>

为了防止自定义特性和后续规范冲突,规定存入 data- 的特性中供自定义使用

  1. <div id="d" data-about-whate="hello"></div>
  2. // 注意这里是使用驼峰来访问,如果你的data-自定义属性有多个连字符
  3. d.dataset.aboutWhat hello

总结:

  1. 特性是写在HTML上的。
  2. 属性是DOM对象中的。

    修改文档

    创建一个元素

  • document.createElement(tag) 指定标签创建一个元素节点
  • document.createTextNode(text) 创建一个文本节点

    插入方法

    插入节点

  • node.append(node or strings) 指定node末尾

  • node.prepend(node or string) 指定node的开头
  • node.before(node or string) 指定node前面
  • node.after(node or string) 指定node之后
  • node.replaceWith(node or string) 将指定node替换为给定的节点

image.png

插入HTML,文本节点,元素节点

where是固定的几个参数: beforebegin afterbegin beforeend afterend

  • elem.insertAdjacentHTML(where, html)
  • elem.insertAdjacentText(where, text)
  • elem.insertAdjacentElement(where, text)

    节点移除

  • node.remove()

    插入方法都默认会将节点从原位置剪切到新位置,原位置不存在,相当于删除。

克隆节点cloneNode

  • elem.cloneNode(deep?:boolean) 是否深克隆一个节点,包含所有特性和子元素。false则不克隆子元素

    DocumentFragement

    一个特殊的DOM节点,节点列表的包装器。通常成片增加DOM,减少渲染性能消耗(重排)

    古老的DOM操作方法

  • parentElement.appendChild(node) 将node插入到父元素的最后一个子元素的位置上

  • parentElement.insertBefore(node, nextSibling) 在parentElement的nextSibling前插入node
  • parentElement.replaceChild(node, oldChild) 将parentElement的后代中的oldChild替换为node
  • parentElement.removeChild(node) 将parenElement的子元素node删除

    样式和类

    className和classList

    为什么是 elem.className ,而不是 elem.class ?因此class是保留字,不能用作对象的属性,所以才引入了 className

    classList

    相对className的整体更改,更为方便,是一个特殊的对象,提供了 add/remove/toggle 的方法

Style

使用驼峰来写

  1. button.style.backgroundColor = 'red'

重置样式属性

document.body.style.display = ''
而不是
delete document.body.style.display

cssText

完全重写style, body.style.cssText = ''

单位

需要写明 10px 而不是 10
body.style.margin = '20px'

获取计算样式getComputedStyle

因为 ele.style 属性访问,只能访问到 style 特性的值,如下:

  1. <style>
  2. #box {
  3. color: blue;
  4. }
  5. </style>
  6. <div id="box" style="background: red"></div>
  7. box.style.background //red
  8. box.style.color // '' 空的,获取不到非直接style特性的值

因此我们有了这个方法
getComputedStyle(elem, [pseudo]) ,返回一个对象,包含所有样式的对象。

  • elem 指定元素
  • pseudo 伪元素

    通常需要完整的样式名称,如padding,到底需要哪个方向的,比如左,需要指明paddingLeft。

计算值和解析值

计算值:指的是style,css等样式层叠下来后,最终应用的那个值。
解析值:将计算值标准化后的值,比如1rem,解析为16px

  1. <style>
  2. #box{
  3. font-size: 16px;
  4. }
  5. #box{
  6. font-size: 1rem;
  7. }
  8. </style>
  9. // html fontSize假定为12px
  10. <div id="box">box</div>
  11. // 计算值:1rem
  12. // 解析值:12px

元素大小和滚动

盒模型

标准盒模型

现代浏览器默认模式,大小计算包含 content + padding + border

怪异盒模型|IE盒模型|替代盒模型

IE浏览器默认的模式,大小计算包含content + padding

盒模型转换

转为怪异盒模型,通过设置box-sizing: border-box
转为标准盒模型,通过设置box-sizing: content-box

总结

offsetParent

最近的具有定位样式,如relative, absolute, fixed的父元素。

offsetTop|Left

相对于最近的具有定位样式的父元素的左边距和上边距。

offsetWidth|Height

元素的完整宽高(其实就是元素的标准盒模型宽高),包含滚动条

clientLeft|clientTop

元素的左上外角到左上内角的距离,实际上就是元素的border宽度。这个属性只跟元素自身有关

clientWidth|clientHeight

内容部分的宽高,其实就是怪异盒模型宽高。不包含滚动条

scrollWidth|Height

类似clientWidth|Height,但还包含了滚动出去的部分

scrollLeft|Top(读写属性)

元素滚动出去的那部分距离,是对滚动容器的属性访问,而不是容器内很长的内容元素。

  1. <div id="wrap">
  2. <div id="content"></div>
  3. </div>
  4. <style>
  5. #wrap{width: 100px; height: 200px; owerflow: auto}
  6. #content{width: 100px; height: 1000px;}
  7. </style>
  8. // 获取滚动容器滚过了多少距离,wrap.scrollTop

window大小和滚动

clientWidth vs innerWidth

前者是文档(document.documentElement)的可用宽度
后者在有滚动条的情况下,会包含滚动条的宽度。

文档的宽度高度

测量文档的真实高度

  1. Math.max(
  2. document.body.scrollHeight, document.documentElement.scrollHeight,
  3. document.body.offsetHeight, document.documentElement.offsetHeight,
  4. document.body.clientHeight, document.documentElement.clientHeight
  5. );

获取当前滚动

获取已经滚动过的距离。

  1. document.documentElement.scrollLeft/Top

Safari 中需要使用 document.body

  1. document.body.scrollTop // 0 试了下并不行
  2. document.documentElement.scrollTop // 420 这个OK

通用API

优先使用这个获取页面滚动距离

  • window.pageXOffset/pageYOffset

    滚动

    scrollTo

    直接滚动到指定位置

    scrollBy

    相对于当前所在的位置滚动多少

    scrollIntoView

    elem.scrollIntoView(top?: true) 让元素滚动至视口处。参数为false,则滚动值视口底部。

    禁止滚动

    冻结滚动:document.body.style.overflow = hidden
    恢复滚动:document.body.style.overflow = ''
    这些操作会让滚动条消失或恢复,导致内容空间会闪动,可以通过比对冻结滚动前后的 cleintWidth 来给适当的元素增加padding。