DOM 笔记

DOM => document object model

DOM 对象 => 宿主对象

DOM 是操作 HTML 和 XML的

JavaScript中的三种对象

  1. 本地对象 Native Object

    Object Function Array Number Boolean Error EvalError SyntaxError RangeError ReferenceError TypeError URIError Date RegExp
    复制代码

    本地对象就是可以直接new的对象,本地给你内置好了的对象

  2. 内置对象 Built-in Object

    Global Math 
    ECMA => isNaN parseInt Number decodeURI encodeURI Infinity NaN undefined 
    复制代码

    global在JavaScript中是不存在的,全局下的这一些方法都属于global

    本地对象内置对象都是ECAM的内部对象

  3. 宿主对象 Host Object

    浏览器对象 window(BOM) 和 Document(DOM) -> DOM 是被BOM包含的 现在是被拆开的,为什么是拆开的呢 是因为 DOM 是有 W3C 规范的
    复制代码

    执行JS脚本的环境提供的对象,浏览器对象,叫做宿主对象,由于每个宿主不同,执行JS的脚本不同所以会造成兼容性问题

  4. 模型

    模型就是一套方法的集合,有序的排列起来,从中找到相适应的方法解决相应的问题

Document

document 是一个对象是文档的老大 里面存了属性和方法

  • document.getElementByid 在Ie8以下是不区分大小写的,在ie8以下是可以根据标签的name获取到这个元素的
  • document.getElementsByTagName 根据标签名获取一组元素 返回的是类数组
  • document.getElementsByClassName 根据className 获取一组元素 返回的是类数组
    • byClassName兼容到IE8
  • document.getElementsByName 根据标签的Name获取一组元素 返回的是类数组 原则上是必须得有name数组的标签但是根据浏览器的不同 只要你写name就可以用到 非常不常用
  • document.querySelector 根据css获取元素 获取到当前DOM的第一个
  • document.querySelectorAll 根据css获取元素 获取一组元素
    • querySelector,.querySelectorAll 这两个兼容到 IE7 企业一般不让用
    • 存在性能问题
    • 致命的弱点获取的DOM不实时更新
      • 就比如我们 remove 删除了这个DOM元素 这个元素还会在这个伪数组中保存

节点树

节点包含元素 => 节点中的元素叫做元素节点 => 元素节点等于DOM元素

遍历节点树 元素节点树

image-20210219225555153

  • parentNodes 获取父级节点 => 最顶级是document
  • childNodes 获取子级节点
    1. 元素节点 = 1
    2. 属性节点 = 2
    3. 文本节点 test = 3
    4. 注释节点 comment = 8
    5. document = 9
    6. documentFragment = 11
  • lastChild firstChild
  • nextSibling previousSibling
遍历元素节点树
  • parentElement => 最顶级是Html IE9及一下不支持
  • children IE7及一下不支持
  • childElementCount = children.length IE9及一下不支持
  • firstElementChild lastElemntChild IE9及一下不支持
  • nextElementSibling previousElementChild IE9及一下不支持
为什么parentNodes的顶级是document而parentElement是html?

因为document是文档节点,而html是元素节点,parentElement是获取元素节点的方法,HTML的父级是document节点,而不是元素HTML就是最大的元素

获取元素兼容性解决方案
/**
 * @description:  获取所有子元素
 * @param {*} node
 * @return {*}
 */
function getChildElementNodes(node) {
    var len = node.childNodes.length,
        childNodes = node.childNodes,
        nodeArr = [],
        elem;
    for (var i = 0; i < len; i++) {
        elem = childNodes[i]
        if (elem.nodeType === 1) {
            nodeArr.push(elem)
            // nodeArr[nodeArr['length']] = elem
            // nodeArr['length']++
        }
    }
    return nodeArr;
}

/**
 * @description: 获取第一个元素节点
 * @param {*} node
 * @return {*}
 */
function getFirstELementChildNode(node) {
    var elem = node.firstChild
    while (elem && elem.nodeType !== 1) {
        elem = elem.nextSibling
    }
    return elem
}

/**
 * @description: 获取最后一个元素节点
 * @param {*} node
 * @return {*}
 */
function getLastELementChildNode(node) {
    var elem = node.lastChild
    while (elem && elem.nodeType !== 1) {
        elem = elem.previousSibling
    }
    return elem;
}

/**
 * @description:  获取下一个兄弟元素
 * @param {*} node
 * @return {*}
 */
function getNextSiblingElementNode(node) {
    var elem = node.nextSibling
    while (elem && elem.nodeType !== 1) {
        elem = elem.nextSibling
    }
    return elem
}

/**
 * @description: 获取上一个兄弟元素
 * @param {*} node
 * @return {*}
 */
function getPreviousSiblingElementNode(node) {
    var elem = node.previousSibling
    while (elem && elem.nodeType !== 1) {
        elem = elem.previousSibling
    }
    return elem;
}
复制代码

节点的属性方法

元素节点   = 1 
属性节点   = 2
文本节点  test  = 3
注释节点  comment  = 8
document = 9
documentFragment = 11
// ------------------------
getAttributeNode() 获取属性节点 
attributes 获取属性集合 => 类数组
复制代码
  • nodeName 元素节点的nodeName 大写 只读
  • nodeValue 节点的值 可读 可写
    • 元素节点没用nodeValue属性
    • 属性 注释 文本可用
  • value 获取属性节点的值
  • nodeType 获取节点类型 只读
  • hasChildNodes 该元素的是否有子节点

DOM结构树

DOM对象慕名思意,每一个DOM都是一个实例化的对象

image-20210303224120196

DOM操作深入

  • getElementById()
    • 只有在Document.prototype 上有getElementById()
  • getElementsByName()
    • 只有在Document.prototype 上有getElementsByName()
  • getElementsByTagName() | getElementByClassName() | querySelector | querySelectorAll
    • Document.prototype 和 Element.prototype 都有 getElementsByTagName() | getElementByClassName() | querySelector | querySelectorAll
  • document.body
    • 获取的时body元素
    • 在HTMLdocument上
  • document.head
    • 获取的时head元素
    • 在HTMLdocument上
  • document.title
    • 获取的是title里面的文本,不是title元素
    • 在HTMLdocument上
  • document.documentElement
    • 获取html元素
    • 在Doducment上
// 示例
// 1. 当我们想选择某个ID下的某些标签可以利用DOM原型这个关系进行选择
document.getElementById('box').getElementsByTagName('div') // 由于Element.prototype上也有getElementsByTagName所以可以进行选择
复制代码
小案例
/**
 * @description: 获取任意子元素
 * @param {Number}  子元素索引 不传返回全部
 * @return {*} 任意子元素
 */
HTMLElement.prototype.getChild = function () {
  // var nodes = [],
  var nodes = {
      splice: Array.prototype.splice,
      push: Array.prototype.push,
      length: 0
    },
    arg1 = arguments[0],
    len = this.childNodes.length,
    child = this.childNodes
  for (var i = 0; i < len; i++) {
    if (child[i].nodeType === 1) {
      nodes[nodes.length] = child[i]
      nodes.length++
    }
  }
  return arg1 ? nodes[arg1] || nodes : nodes

}
/**
 * @description: 获取N层父节点
 * @param {Number} 获取的第N层不传查上一层,大于顶级节点的索引返回最后一层
 * @return {*} N层的父元素
 */
HTMLElement.prototype.getParent = function () {
  var parentNode = this,
    oldParentNode,
    arg1 = arguments[0] || 1
  while (arg1 && parentNode) {
    oldParentNode = parentNode
    parentNode = parentNode.parentNode;
    arg1--
  }
  return parentNode || oldParentNode;
}
复制代码

节点的创建

  • document.createElement 创建元素节点

    • Document.prototype
  • document.createTextNode 创建文本节点

    • Document.prototype
  • document.createComment 创建注释节点

    • Document.prototype
  • parent.appendChild 增加子节点

    • Node.prototype
    • 动态增加节点,动态剪切节点
      • 如我们在文档中获取了某个标签,appendChild到指定节点,我们发现之前获取的哪个节点到了指定的节点中.
      • 其实我们在使用appendChild的时候,他的操作流程是,剪切过来然后复制进去
    • 这个方法的参数必须节点或者元素
  • c.insterBefore(a,b) 插入节点

    • Node.prototype
    • c中插入a节点,在b节点前
  • parent.removeChild() 删除子节点

    • Node.prototype
    • 其实是剪切子节点,并没有删除这个节点,这个节点还在内存中,只是这个节点不再dom结构里了,他无法去删除掉存在内存中的这个dom对象
  • div.remove() 删除节点

    • Node.prototype
    • 删除节点并且删除这个dom对象
  • parent.replaceElement(newEle,oldEle) 替换节点

    • Node.prototype
  • div.innerHTML

    • HTMLElement.prototype
    • Element.prototype
    • 可读可写
    • 赋值 取值 追加值 添加HTML字符串
  • div.innerText

  • 火狐老版本不支持 使用 textContent 代替 textContent 老版本IE不支持

  • HTMLElement.prototype

  • div.setAttribute(‘type’,’value’) 设置属性

    • Element.prototype
  • div.getAttribute(‘type’) 获取属性

    • Element.prototype
  • div.dataset 获取属性

    • 获取自定义属性 data-*

      <div data-name='zs' data-age='18'>
          我是div
      </div>
      通过dataset获取标签中的自定义属性 返回值是一个对象 {name:'zs',age:'18'}
      复制代码
  • document.createDocumentFragMent() 创建文档片段**(碎片)**

    • 减少回流

元素如何变成元素节点

元素节点中必定存在一些方法比如 nodeName nodeType attribute 等

当我们使用选择器选择了元素是,他会经历那些步骤,以div为例

  1. 元素 => 实例化对象 => 节点
  2. 也就是说 选中div这个元素的时候 他会实例化 new HTMLDivELement,这个时候DOM对象就产生了,DOM对象等于DOM节点
  3. 这个对象存在内存中,因为他是引用类型,所以存在堆中.

回流

每一次DOM重新添加元素节点的时候,都会重新计算几何数据(元素在浏览器的位置),我们叫回流

滚动距离高度,可视尺寸

滚动距离
  • window.pageXOffset window.pageYOffset 获取X轴滚动距离和Y轴滚动距离

    • IE9及IE8以下不支持
    • IE9及IE8对应的属性是 document.documentElement.scrollTop document.documentElement.scrollLeft document.body.scrollTop document.body.scrollLeft
    • 不常见的是window.scrollX window.scrollY
    // 兼容性封装
    function getScroll() {
        if (window.pageXOffset !== undefined) {
            return {
                left: window.pageXOffset,
                right: window.pageYOffset
            }
        } else {
            return {
                left: document.body.scrollLeft + document.documentElement.scrollLeft,
                top: document.body.scrollTop + document.documentElement.scrollTop
            }
        }
    }
    复制代码

image-20210325215124610

浏览器可视区域的尺寸(窗口宽高)
  • 常规: window.innerWidth/window.innerHeight

    • 获取可视区域尺寸宽/高
  • IE9/IE8及以下:

    • 标准模式
      • docuemnt.docuemntELement.clientWidth/clientHeight
      • 获取可视区域的宽/高
    • 怪异模式
      • document.body.clientWidth/clientHeight
      • 获取可视区域的宽/高
    // 获取浏览器可视区域尺寸兼容性封装
    function getViewPortSize() {
      if (window.innerWidth) {
        return {
          width: window.innerWidth,
          height: window.innerHeight
        }
      } else {
        if (document.compatMode === 'BackCompat') { // 如果当前浏览器是怪异模式
          return {
            width: document.body.clientWidth,
            height: document.body.clientHeight
          }
        } else {
          return {
            width: document.documentElement.clientWidth,
            height: document.documentElement.clientHeight
          };
        }
      }
    }
    复制代码
获取整个文档尺寸(整个html的宽度高度)
  • document.body.scrollWidth/scrollHeight
    • 获取文档的宽/高
  • document.documentElement.scrollWidth/scrollHeight
    • 获取文档的宽高 IE567
获取元素的尺寸位置
  • oBox.getBoundingClientRect
    • 优点基本上所有尺寸都能获取的到,致命的缺点属性不实时
    • ie 678 没有宽高
{
    bottom: 150
    height: 120
    left: 30
    right: 150
    top: 30
    width: 120
    x: 30
    y: 30
}
复制代码
获取元素的位置
  • elem.offsetTop/offsetLeft 获取元素当前距离顶部距离左侧的位置
    • 注意是获取当前元素到上一个定位父级元素,**如果父级元素没有定位则向上查找,**如果所有的父级都没有定位则找到body
  • elem.offsetParent 获取上一个带有定位的父级元素 如果都没有定位则找到body
操作滚动条
  • window.scroll/scrollTo/scrollBy 操作滚动条 第一个参数是X轴第二个参数是Y轴
    • scroll/scrollTo 滚动至文档中的绝对位置(当前滚动条滚动到参数的位置)
    • scrollBy 滚动指定的距离 (当前滚动条的距离加上参数的距离)

浏览器的怪异模式和标准模式

!DOCTYPE html

浏览器有自己的各自的兼容性模式,浏览器默认的兼容格式(向后兼容).浏览器厂商一般兼容5个版本,如果写了DOCTYPE html就得遵守W3C规范,如果不写!DOCTYPE html 则按照浏览器自己的规则兼容

写了!DOCTYPE html 则为标准模式 ,不写DOCTYPE html则为怪异模式

验证是怪异模式还是标准模式

document.compatMode
// 标准模式  || 怪异模式
// "CSS1Compat" || "BackCompat"
复制代码

读写样式属性

DOM间接操作CSS 注: 不是操作样式表奥,DOM是无法操作样式表的

  • elem.style.CSS属性
    • 可读可写
    • 注意如果CSS属性单词出现两个及以上必须使用小驼峰 如: borderColor
    • 值 需字符串 如: elem.style.borderWidth = '10px'
    • 符合值必须进行拆解 如: 1px solid #fff 需拆解成 borderWidth = '1px'; borderStyle = 'solid'
    • 保留字前必须加css 如: elem.style.cssFloat = 'left'
  • elem.style 获取当前元素可以设置的样式集合
  • window.getComputedStyle(element,null) 获取元素计算样式属性
  • getComputedStyle的第二个参数如果写的话则是获取伪元素样式,例如: getComputedStyle(element,'after')
    • 如果该元素没有设置这个样式属性则为默认样式属性
    • IE8及以下不支持对应的属性是
      • element.currentStyle

设置class属性

如多条style需直接更换class名称

  • element.className 获取class属性,可读可写

事件

绑定事件 = 绑定事件的处理函数

onclick = function(){事件反馈}

绑定事件 事件处理函数

事件 + 事件处理函数 = 前端交互 交互体验

  • onclick = function(){}
    • 此时的onclick句柄
    • onclick = function(){} 事件句柄
    • oDiv.onclick = function(){} 事件句柄的绑定形式
  • onclick = null
  • element.addEventListener(事件类型,事件处理函数,布尔)
    • ie8及以下 element.attachEvent(事件类型,事件处理函数)
  • element.removeEventListener(事件类型,事件处理函数)
    • ie8及以下 element.detachEvent(事件类型,事件处理函数)
事件状态冒泡与捕获

事件的默认状态时冒泡状态

什么是事件冒泡?

事件冒泡就是从里层元素向外层元素同事件一层一层的触发为事件冒泡反之为事件捕获

  • event 事件对象的兼容
    • event || window.event
  • event.stopPropagation 阻止事件冒泡 ie8 及以下不支持 W3C标准
    • event.cancelBubble = true ie8及以下阻止事件冒泡
  • event.preventDefault 阻止默认事件 ie8及以下不支持 W3C标准
    • event.returnValue = false 阻止默认事件 Ie8及以下
    • return false 阻止默认事件 兼容性极强 但是不常用

事件流

描述事件从页面中接收事件的顺序 冒泡捕获

  • IE 提出的 事件冒泡流(Event Bubbling)
  • Netscape 提出的 事件捕获流 (Event Capturing)
事件流的三个阶段

事件捕获阶段 处于目标阶段 事件冒泡阶段

  • 先捕获 在冒泡
  • 处于目标阶段也就是事件源不处于冒泡或者捕获问题,按照代码执行顺序执行
DOM级别问题

事件模型

事件模型的规范

可以理解为三个阶段的规范

  • DOM0

    • onXXX = fun
    • elem.onXXX = fun
  • DOM1

    • 没有定义事件模型
  • DOM2

    • addEventListener(三个参数) W3C规范
    • removeEventListener(事件类型,事件处理函数)

事件对象

每个事件处理函数都有一个事件对象 event

  • 事件处理函数中有个参数event是事件对象
    • ie8及以下的事件对象在window中
  • event.target || event.srcElement 是事件源
    • scrElement ie9及以下
事件代理,事件委派

事件代理,事件委派,是利用事件的冒泡进行事件的代理给父元素绑定事件取触发事件的事件源,因此会大大提升性能并且子元素增删会实时响应

parentEle.onclick = function(e){
	var e = e || window.event,
        tar = e.target || e.currentElement
    
}
复制代码
  • 取元素列表中事件源的下标
parentEle.onclick = function(e){
    var e = e || window.event,
        tar = e.target || e.currentElement
    for(var i = 0; i < len; i++){
       if( oList[i] === tar ){
           console.log(i)
       }
    }
}
// 最佳解决办法
parentEle.onclick = function(e){
    var e = e || window.event,
        tar = e.target || e.currentElement
 Array.prototype.indexOf.call(oList,tar)
}
复制代码
  • 事件代理究竟是干什么
    • 解决多次事件绑定绑定同一个回调函数,例如50个li绑定事件,如果重复绑定的话就绑定了50个回调函数并且,新增的标签没有事件

鼠标行为

坐标系
  • event.clientX/Y 鼠标位于当前可视区域的坐标(不包含滚动条的距离)

  • layerX/Y 与pageX/Y相同,IE11以下同clientX/Y

  • screenX/Y 鼠标位于屏幕的坐标

  • x/y 于clientX/Y相同, FF(火狐不支持)

  • pageX/Y 鼠标位于文档的坐标(包含滚动条的距离)

    • ie9及以下不支持(Jquery)
  • offsetX/Y 鼠标位置相对于块元素的坐标

    • 包含边框,safari不包含边框 不推荐使用

获取文档偏移量IE8及以下
  • document.documentELement.clientLeftt

鼠标事件

通常使用鼠标按下抬起事件 来模拟鼠标事件

  • mousedown 鼠标按下事件

  • mouseup 鼠标抬起事件

    鼠标事件事件对象中几大重要属性

    1. event.button 判断触发当前事件的是鼠标哪个按键
      1. 通常高级浏览器为 0 1 2 左中右
  • contextmenu 右键菜单事件

    • 通常给他禁止掉 event.prventDefault
  • mouseover 鼠标移入

  • mouseout 鼠标移出

  • mouseenter 鼠标移入

    • 与mouseover 本质的区别是在于 他不会触发冒泡及mouseenter是对被绑定的元素生效, mouseout 是对被绑定的元素及所有的子元素生效
  • mouseleave 鼠标移出

input事件

  • IE9及以下 onpropertychange 输入时反馈
  • IE9以上 oninput 输入时反馈
  • onchange 失去焦点时反馈 如果进入焦点与失去焦点值一样则不反馈
  • onfocus 进入焦点
  • onblur 失去焦点

自定义事件

如何自定义事件,想到自定义事件我们可以在HTMLElement.prototype上添加绑定事件的方法,下面以bindEvent为例子

// 为什么要在HTML的原型上添加
// 1. HTMLElement是元素节点的直接上级 例如HTMLDivElement 我们看到他的直接上级是HTMLELement所以在HTMLELement的原型上定义是最合理的
// 2. 我们要进行判断需要绑定的事件是什么事件 需要进行什么样的处理
// 3. 我们想到了自定义事件必须使用的构造器 Event https://developer.mozilla.org/zh-CN/docs/Web/API/Event 详见MDN
HTMLElement.prototype.bindEvent = function(type,callBack){
   var elem = this, // 储存元素
	   _event = new Event(type);
    
   elem.addEventListener(type,callBack,false)

   	function dispatch(){
        elem.dispatchEvent(_event)
    }
    function remove(){
        elem.removeEventListener(type,callBack,false)
    }
    return {
        dispatch,
        remove
    }
}
复制代码

监视DOM节点的变化

MutationObserver 通过构造函数可以监听DOM节点的变化

  • MutationObserver.prototype.disconnect 阻止监听/停止监听
  • MutationObserver.prototype.observe 在DOM更改时通过其回调函数接收通知
  • MutationObserver.prototype.akeRecords 通知队列中删除所有待处理的的通知
  • 详见: developer.mozilla.org/zh-CN/docs/…
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享