【愣锤笔记】MVVM时代下仍需掌握的DOM - 基础篇

栏目: 后端 · 发布时间: 5年前

内容简介:在当前MVVM大行其道的环境下提到DOM一词,很多人可能会感到有些诧异。这种差诧异或许来自于类似“都什么年底了还操作DOM啊”的声音!说的没错,MVVM时代,虚拟dom东征西战,一枝独秀,着实不可否认其强大的威力。然而,DOM操作作为前端的基础,自诞生以来便左右着我们的页面效果。随着JQuery十年戎马生涯的落幕,DOM似乎暗淡了许多,但其在前端中的左右却从未动摇。即使是MVVM框架下的例如ElementUl/IView等等最流行的ui库,打开他们的源码,依旧会有类似的dom.js/event.js等工具集

在当前MVVM大行其道的环境下提到DOM一词,很多人可能会感到有些诧异。这种差诧异或许来自于类似“都什么年底了还操作DOM啊”的声音!说的没错,MVVM时代,虚拟dom东征西战,一枝独秀,着实不可否认其强大的威力。

然而,DOM操作作为前端的基础,自诞生以来便左右着我们的页面效果。随着JQuery十年戎马生涯的落幕,DOM似乎暗淡了许多,但其在前端中的左右却从未动摇。即使是MVVM框架下的例如ElementUl/IView等等最流行的ui库,打开他们的源码,依旧会有类似的dom.js/event.js等 工具 集(这里暂且称其工具函数集合吧),这些ui库里面,避免不了基本的事件绑定啊/添加移除类啊等等。

不管任何时候,DOM依旧是前端必须掌握且需要投入一定时间研究的基础。不能只停留在jq事件的dom操作或者只是掌握那几个最常见的api。

:point_down:下面开始有趣的DOM之旅吧!

DOM

DOM全称Document Object Modal 文档对象模型

Node

Node是js的构造函数,所有节点都从Node上继承最常见的属性和方法,例如:

  • childNodes/firstChild/nodeName/nodeType等
  • appendChild()/cloneNode()/removeChild()等
  • 其他更多

节点类型

document.nodeType // 文档节点,9
document.doctype.nodeType // 文档类型声明节点,10
document.createElement('a').nodeType // 元素节点,1
document.createDocumentFragment().nodeType // 11
document.createTextNode('aaa').nodeType // 文本节点,3
复制代码

节点的值

可以通过节点的 nodeValue 属性获取节点的值,但是除了 TextComment外 ,其余节点基本都返回 null

创建节点

// 创建元素节点,例如div
document.createElement('div')

// 创建文本节点
document.createTextNode('a text')

// 创建注释节点
document.createComment('a comment 节点')
复制代码

插入元素或文本

// 替换#app内部的内容
document.getElementById('app').innerHTML = '<div>asdasdasd</div>'

// 替换#app及其内容,本身也会被替换掉
document.getElementById('app').outerHTML = '<div>asdasdasd</div>'

// 创建一个文本节点,并替换#app内的内容
document.getElementById('app').textContent = 'a text'

---
上面这些方法,如果不是赋值,而是直接作为属性取值,则会返回取到的节点字符串
---

var app = document.getElementById('app')
// 在#app开始标签之前插入,#app需要有父节点
app.insertAdjacentHTML('beforebegin', '<span>hello</span>')
// 在#app开始标签之后插入
app.insertAdjacentHTML('beforeend', '<span>beforeEnd</span>')
// #app结束标签之前插入
app.insertAdjacentHTML('afterbegin', '<span>afterbegin</span>')
// 在#app结束标签之后插入,#app需要有父节点
app.insertAdjacentHTML('afterend', '<span>afterEnd</span>')
复制代码

插入节点

可以通过 appendChild()insertBefore() 插入节点

// 插入节点
var div = document.createElement('div')
app.appendChild(div)
复制代码

insertBefore控制插入的位置,第一个参数是待插入节点,第二个参数是插入位置(即一个插入这个节点的前面,类似于一个参考节点)

// 将div节点插入到#app的第二个p节点的前面
app.insertBefore(div, p[1])

// 如果忽略第二个参数,则和appendChild一样,默认插入到最后面
app.insertBefore(div)
复制代码

移除节点/替换节点

移除一个节点,首先要找到该节点的父节点,然后在父节点上调用removeChild方法。

// 移除第二个p节点
p[1].parentNode.removeChild(p[1])
复制代码

替换节点,先找到父节点,然后在父节点调用replaceChild方法,接收两个参数,第一个为新节点,第二个是待替换的节点

// 将第二个p节点替换成一个newdiv节点
p[1].parentNode.replaceChild(newdiv, p[1])
复制代码

注意:这两个方法会返回被移除或替换的节点。该操作只是将节点从文档中移除,并不是真正的删除,其依旧存在于内存中,我们仍可以持有其引用。

克隆节点

  • node.cloneNode()方法用来克隆节点,接收一个参数,如果为true则克隆该节点及其子节点,如果为false则只克隆该节点。
  • 该方法会克隆节点的所有属性和内联事件,但是不会克隆addEventListener或node.onclick等形式添加的事件。
// 克隆#app节点
app.cloneNode(false)

// 克隆#app及其子节点
app.cloneNode(true)
复制代码

目前其默认值是false,但是DOM4规范其默认行为发生了变化为true。所以考虑到兼容,必须传参数使用。

childNodes

返回一个类数组包含所有直属子节点(包括文本节点/注释节点)

/// 返回#app的所有直属子节点
app.childNodes

// 验证该节点集合是实时的,而不是某一时刻的快照
var ns = app.childNodes
app.innerHTML = ""
console.log(ns) // #app被清空后,虽然之前定义了引用,这里依旧输出了空数组,因为是实时的。

// 可以借用数组方法将类数组转换成数组, es5:
Array.prototype.forEach.call(ns)
// es6中可以使用Array.from()
Array.from(ns).forEach(e => console.log(e))
复制代码

childNodes是实时的,而不是某一时刻的快照

html标签的换行会有文本节点产生,所以childNodes也会包含该文本节点。需要注意现代化开发的压缩代码,所以后续更多的只使用元素节点。

遍历DOM节点

普通节点

  • app.parentNode 父节点
  • app.firstChild 第一个子节点
  • app.lastChild 最后一个子节点
  • app.nextSibling 上一个兄弟节点
  • app.previousSibling 下一个兄弟节点

元素节点

  • app.parentElement 父元素节点
  • app.children 所有子元素节点
  • app.firstElementChild 第一个子元素节点
  • app.lastElementChild 最后一个子元素节点
  • app.nextElementSibling 上一个元素节点
  • app.previousElementSibling 下一个元素节点

判断节点是否包含另一个节点

调用节点的contains方法,可以判断该节点是否包含参数节点,包含则返回true,否则false:

// #app是否包含p[1]这个节点
app.contains(p[1])
复制代码

判断节点是否相等

具备以下条件,节点才相等:

  • 节点类型相等
  • 这些属性相等: nodeName/localName/namespaceURI/prefix/nodeValue
  • attributes NameNodeMaps相等
  • childNodes NodeLists相等 可以通过节点的isEqualNode方法判断
<input type="text">
<input type="text">
  
var ipts = document.querySelectorAll('input')
ipts[0].isEqualNode(ipts[1])

// 如果只是想判断是否是同一个节点引用,则可以使用全等运算符
ipts[0] === ipts[0]
复制代码

document下的节点

var doc = document

doc.doctype // 指向<!DOCTYPE>
doc.documentElement // 指向<html lang="en">
doc.head // 指向<head>
doc.body // 指向<body>
复制代码

获取文档中聚焦/激活状态的元素引用

// 返回文档中聚焦或者激活状态的节点
document.activeElement

// 判断文档是否有激活或聚焦状态的节点,返回true/false
document.hasFocus()
复制代码

全局对象

可以通过 document.defaultView 获取顶部的对象(全局对象),在浏览器中全局对象是window, document.defaultView 指向的是这个值,在非浏览器环境则访问到的是顶部对象的作用域。

元素节点

// 创建,接收一个参数,即元素类型tagName,元素节点的tagName和nodeName的一样。
// 传入的值在被创建元素前都会被转换成小写。
document.createElement('div')

// 获取元素标签名,返回的都是大写
var div = document.createElement('div')
div.nodeName // DIV
div.tagName // DIV
复制代码

获取元素属性与值的集合

该属性是实时的类数组

doc.getElementById('txt').attributes
复制代码

操作元素的属性节点

<a href="http://www.baidu.com" id="a" data-other="other prop">百度网</a>

var a = document.getElementById('a')

// 获取属性节点
a.getAttribute('href')
a.getAttribute('data-other')

// 设置属性节点
a.setAttribute('data-src', 'src string')

// 移除属性节点
a.removeAttribute('href')

// 监测元素是否含有某个属性节点
a.hasAttribute('href')
复制代码

getAttribute如果没取到则返回null

setAttribute必须传2个参数

hasAttribute不管这个属性有没有值,都返回true

元素类名

可以通过 a.classNamea.classList 获取元素类名。

className:

  • 如果没有类名,返回""
  • 类名会原样返回字符串,即使前后都有空格等
  • 更改通过对其进行重新赋值

classList:

  • ie9不支持
  • 可以通过className模拟实现,有类似等profill库
  • 有add/remove/contains/toggle等方法
// 添加
a.classList.add('f')
// 移除
a.classList.remove('a')
// 有则移除,无则添加
a.classList.toggle('e')
a.classList.toggle('b')
// 监测是否有某个类名
a.classList.contains('c')
复制代码

data-属性

// 获取data-属性:a.dataset.属性名,不存在则返回undefined
a.dataset.other

// 设置
a.dataset-other2 = 'data2'
复制代码

dataset在ie9中不支持,不过完全可以依旧使用getAttribute等属性使用

选择器

// id选择器
document.getElementById('app')

// 返回符合条件的首个元素节点
document.querySelector('#app')

// 返回符合条件的元素节点列表
document.querySelectorAll('li')

// 返回符合条件的标签列表
document.getElementsByTagName('div')

// 返回符合条件类名的节点
document.getElementsByClassName('flex1')
复制代码

querySelectorAll、getElementsByTagName、getElementsByClassName都是实时的,而不是快照。

这些方法都可以作用在节点上,从而在上下文中进行局部查找。

// children: 查找所有直接子元素
document.querySelector('ul').children

// html文档中方便使用的类数组列表
document.forms // 获取文档中所有的表单
document.images // 获取文档中所有的图片
document.links // 获取文档中所有的a标签
document.scripts // 获取文档中所有的scripts
document.styleSheets // 获取文档中所有的link和style
复制代码

元素偏移量

首先普及offsetParent概念:一个元素的祖元素中第一个position值不为static的那个元素。

  • offsetTopoffsetLeft 是计算距其 offsetParent 元素的顶部距离和左边距离。(即距离祖元素中第一个position值不为static的祖元素的上边距离和左边距离)

getBoundingClientRect

getBoundingClientRect获取元素相对于视口(可视区域)的各个距离,有如下值:

offsetWidth/offsetHeight
var rect = app.getBoundingClientRect()
rect.bottom
rect.height
rect.left
rect.right
rect.top
rect.width
复制代码

元素尺寸

offsetWidth/offsetHeight

元素滚动距离

// 获取窗口的滚动距离
document.documentElement.scrollTop
document.body.scrollTop // ie
document.documentElement.scrollLeft
document.body.scrollLeft // ie

// 设置窗口的滚动位置
document.documentElement.scrollTop = 0
document.documentElement.scrollLeft = 0
document.body // ie

// 使某个元素滚动到可视区域
// 接收一个参数,true为滚动到可视区域顶部,false为滚动到可视区域底部。默认ture
document.querySelector('#app').scrollIntoView()
document.querySelector('#app').scrollIntoView(false)
复制代码

滚动元素的尺寸

如果一个元素设置为超出滚动后,那么scrollHeight将获取其滚动元素的尺寸,例如一个div宽高50,overflow: scroll;里面有一个高度为1000px的p,那么该div的scrollHeight尺寸为1000。

div.scrollHeight
div.scrollWidth
复制代码

style

元素的style属性返回一个CSSStyleDeclaration对象,该对象包含元素的内联样式,而不是计算后的样式,如果没有给元素写样式,则通过该属性获取的值是空置。

var domStyle = document.querySelector('#app').style
// 获取高度,宽度
style.height
style.width
// 连字符的属性需要使用驼峰命名法
domStyle.fontSize
// 对于暴露字属性在前面加上css
domStyle.cssFloat // domStyle.float 谷歌上测试也可以
复制代码

style获取的是内联的属性,如果是写在样式表中的属性,是获取不到的。

获取的是实际的内联属性,而不是计算后的值。即使样式表中通过important等方式使得权重高于内联的,获取到的依旧是内联样式中写的值。 获取的颜色值是 rgb

style对象获取/设置/移除的其他方法

// 设置属性,不能写复合属性,例如background/margin,而是分开的写法:background-color/margin-left等
// 用-分割的写法,而不是驼峰
dom.setProperty('background-color', '#f00')
domStyle.setProperty('background-color', '#f00')

// 获取
domStyle.getPropertyValue(属性名)
domStyle.getPropertyValue('background-color')

// 移除
domStyle.removeProperty('background-color')
复制代码

style对象设置/获取/移除多个内联属性

// 批量设置多个内联属性
domStyle.cssText = 'background-color: #000;color: 20px;margin: 30px;'

// 移除全部内联属性
domStyle.cssText = ''

// 获取style属性的内联属性
domStyle.cssText

// 通过setAttribute/getAttibute/removeAttribute也是可以实现相同的效果
dom.setAttribute('style', 'background-color: #000;color: #f1f1f1; 20px;margin: 30px;')

dom.getAttribute('style')

dom.removeAttribute('style')
复制代码

获取计算后的属性

var winStyle = window.getComputedStyle(dom)

winStyle.color
winStyle.border
winStyle.backgroundColor // 获取的是rgb颜色格式
winStyle.marginTop // 不能获取简写的格式,例如margin
复制代码

返回的颜色格式是rgb的格式,背景色返回的是rgba

不能获取简写的属性,例如margin/padding,而是marginTop

修改样式的最佳实践

更多的我们会通过给元素添加/移除某个class/id方式,来添加修改样式

DocumentFragment文档片段

DocumentFragment文档片段可以看作是一个空的文档模板,行为与实时DOM树类似,但是仅在内存中存在,可以附加到实时DOM中。

// 创建
document.createDocumentFragment()
// 例如:
var lis = ['hello! ', 'Every', 'bady'];
var fragment = document.createDocumentFragment();
lis.forEach(e => {
  var liElem = document.createElement('li');
  liElem.textContent = e;
  fragment.appendChild(liElem)
})
dom.appendChild(fragment)

// 文档片段插入到dom后,自身的节点内容就没了。例如上面的例子:
dom.appendChild(fragment) // 第一次将文档片段的内容插入到dom后
dom.appendChild(fragment) // 执行相同的操作,并不会插入了,因为此时的文档片段内容没了。

// 为了文档片段的内容可以多次利用,可以利用克隆的方式
dom.appendChild(fragment.cloneNode(true))
复制代码

绑定事件

// 内联事件,基本不用
<div onclick="alert('a')"></div>

// 属性事件(DOM 0 级事件)
window.onload = function () {}

// 绑定事件(DOM 2 级事件),ps:没有1级事件
window.addEventListener('scroll', (e) => {
    console.log(e)
}, false)
复制代码

易混事件区分

常见的click/onload/scroll/resize等事件就不介绍了。

// 鼠标按下,都是在输入法接收到键值之前
keydown // 任何按键按下都会触发,不管他是否产生字符码
keypress // 只有实际产生字符码才会触发,例如command键/option键/shift键等并不会触发

// 鼠标滑入
mouseenter // 鼠标滑入元素及其子元素时触发,不冒泡
mouseover // 鼠标滑入某个元素时触发,会冒泡

// 页面展示
window.onpageshow = function () {} // 展示页面时,触发
window.onload = function () {} // 页面加载完成后触发
// 两者的区别在于,从浏览器缓存读取的页面,并不会触发load事件,例如操作浏览记录的前进后退时

// 其他
offline // 离线时触发
online // 在线时触发
message // 跨文档传递时触发
hashchange // url中hash值的变化时触发
DOMContentLoaded // 页面解析完成后触发,资源不一定下载完成
复制代码

事件中的this/target/currentTarget

document.body.addEventListener('click', function (e) {
    console.log(this)
    console.log(e.currentTarget)
    console.log(e.target)
    console.log(this === e.target, this === e.currentTarget)
}, false)

// this
this指的是该事件绑定的元素或对象,这里指向body

// currentTarget
指的是该事件绑定的元素或对象,这里指向body,同this

// target
指的是事件的目标,可以理解为开始触发冒泡时的那个元素,或者说是鼠标点击的嵌套在最里面的那个元素。
这里指向div
复制代码

preventDefault

阻止事件的默认行为,例如a标签的跳转、输入框的输入等 。但是并不能阻止冒泡。

// 假设a是某个a元素
a.addEventListener('click', function (e) {
    e.preventDefault()
}, false)
复制代码

stopPropagation

阻止事件冒泡,但不会阻止默认事件。

a.addEventListener('click', function (e) {
    e.stopPropagation()
}, false)
复制代码

stopImmediatePropagation

stopImmediatePropagation方法不仅会阻止事件冒泡,还会阻止该元素在调用该方法后面的绑定事件的触发

app.addEventListener('click', function () {
    console.log('app first')
}, false)

app.addEventListener('click', function (e) {
    console.log('app second, 阻止app后面绑定的click事件的冒泡')
    e.stopImmediatePropagation()
}, false)

// 此次的app事件绑定不会触发,因为已经被上面的stopImmediatePropagation方法阻止掉了
app.addEventListener('click', function (e) {
    console.log('app third')
}, false)

document.body.addEventListener('click', function () {
    console.log('body click')
}, false)

// 最终输出如下:
// app first
// style.html:98 app second, 阻止app后面绑定的click事件的冒泡
复制代码

自定义事件

// 自定义事件
var cusEvent = document.createEvent('CustomEvent');

// 配置自定义事件的详情
cusEvent.initCustomEvent('myNewEvent', true, true, {
    myNewEvent: 'hello this is my new custom event!'
})

// 给#app绑定我们自定义的事件
var app = document.querySelector('#app');
app.addEventListener('myNewEvent', function (e) {
    console.log(e.detail.myNewEvent)
}, false)

// 在app上触发自定义事件
app.dispatchEvent(cusEvent)
复制代码

initCustomEvent接收四个参数:事件名称,是否冒泡,是否可以取消事件,传递给event.detail的值

事件委托

事件委托利用事件流来完成,给父级绑定事件,然后判断触发事件的target,执行对应的事件。

例如:给表个中的td添加事件。

var tableBox = document.querySelector('#table-box');
tableBox.addEventListener('click', function (e) {
    var target = e.target
    if (target.tagName.toLowerCase() === 'td') {
        console.log(target.textContent)
    }
}, false)
复制代码
【愣锤笔记】MVVM时代下仍需掌握的DOM - 基础篇

参考内容:

百尺竿头、日近一步。

我是愣锤,一名前端爱好者。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

代码大全(第2版)

代码大全(第2版)

[美] 史蒂夫·迈克康奈尔 / 金戈、汤凌、陈硕、张菲 译、裘宗燕 审校 / 电子工业出版社 / 2006-3 / 128.00元

第2版的《代码大全》是著名IT畅销书作者史蒂夫·迈克康奈尔11年前的经典著作的全新演绎:第2版不是第一版的简单修订增补,而是完全进行了重写;增加了很多与时俱进的内容。这也是一本完整的软件构建手册,涵盖了软件构建过程中的所有细节。它从软件质量和编程思想等方面论述了软件构建的各个问题,并详细论述了紧跟潮流的新技术、高屋建瓴的观点、通用的概念,还含有丰富而典型的程序示例。这本书中所论述的技术不仅填补了初......一起来看看 《代码大全(第2版)》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具