DOM操做是JS中的一大重点,本次回顾DOM操做,总结如文。javascript
尽管是一篇总结,但我尽可能把内容说清,逻辑理顺,我的认为读起来仍是容易接受。对许多细节和注意点进行标注,须要展开的内容,也附上拓展连接。css
但愿能合各位看官的口味,废了好几天,但愿求各位一赞,你看收藏夹那么多灰,不介意一块儿分点吧?刚开始写点博文,但愿给点鼓励。html
配套脑图: html5
本文面向HTML,不讨论XML等文档,下文中“文档===HTML”。java
节点:前面提过,节点是DOM树的组成单元。在JS看来,一个节点就是JS对象。下面用node
表示任意的节点。git
节点类型:并不是全部的节点都是同样的,DOM规定文档中有12种节点类型,分别用常量1 ~ 12
(有与之对应的常量名称Node.XXX_NODE
)表示,能够经过node.nodeType
属性获取节点的类型常量。web
如今,有些类型的节点已经弃用了,常见到的只有几种类型的节点,包括:面试
元素节点: 类型常量为Node.ELEMENT_NODE
或1
。最多见的一类节点,对应文档中的元素。大部分DOM操做都是在元素节点层次的。编程
文本节点:类型常量为Node.TEXT_NODE
或2
。对应文档中的文本,任何文档内容都有对应的文本节点,即便空格和换行符。
空格和换行不会对页面内容产生影响,但它们确实以文本节点的形式存在于DOM树中。
Document节点:类型常量为Node.DOCUMENT_NODE
或9
。它不对应文档的内容,而是做为文档的入口节点,每一个文档都有且仅有一个入口,由于这种独特性,赋予一个特殊的变量名称document
。
注释节点:类型常量为Node.COMMENT_NODE
或8
。它对应文档中的注释标签,文档的注释内容也是可读取和修改的。
节点类:DOM内置许多节点类,类之间存在继承关系,造成一套节点类框架。每一个节点对象都属于节点类,拥有该类和其父类的方法与属性,这使得操做节点十分简单。节点类框架的一部分大概如图:
这些节点上有丰富的属性和方法,是继承的结果,能够看到,一个HTML标签元素至少有四层的继承关系。
以<a>
标签为例,它属于HTMLAnchorElement
类,得到了a.target
,a.download
等属性,接着继承了HTMLElement
类上的title
, hidden
等属性和click()
等方法,又从Element
类继承了tagName
, className
等属性和getAttribute()
, setAttribute()
等方法,再从Node
类继承了nodeType
(前面说过的节点类型), appenChild()
, removeChild()
等方法,最后从EventTarget
类中继承了事件相关的属性和方法。
不要混淆节点类型和节点类这两个概念。前者是一个生活中的类别,后者是编程意义上的类。节点对象的
nodeType
属性表示了它的类型,而节点类是该节点的从属的类。由于Dode
是一个抽象类,因此,若是知道了某个节点从属的类,咱们就知道它的节点类型。
区分节点与元素节点。咱们常常关心元素节点(简称元素),由于这是一类最常使用的节点,可是并不是全部节点都是元素。
前面的说法太抽象了,让咱们用实际的例子看看文档、DOM树与节点的关系。
Live DOM Viewer是一个能够根据HTML文档实时查看DOM树的网站。你把下面的例子复制过去,或者本身去探索。
一个简单的HTML文档:
<!DOCTYPE HTML>
<html>
<body>
A simple text.
<ol title="this is a title">
<li>czpcalm</li>
<!-- comment -->
</ol>
</body>
</html>
复制代码
它对应的DOM图(颜色区分了节点类型):
留意这个图,你要注意几点:
总共有4种类型的节点,分别是标签节点
(红色),文本节点
(灰色),注释节点
(黄色)和DOCTYPE节点
(紫色)。
文档没有<head>
标签却出现了HEAD
节点。这是由于HTML必然存在<html>
,<head>
,<body>
标签,不存在时会自动补上。顺便一提,当出现<table>
标签时,也必定会有<tbody>
标签。
文档中的文本都会造成文本节点的内容,包括空格␣
和换行↵
。第一,单独的空格和换行都会造成对应的文本节点
;第二,有内容的文本节点的值包含前导和后继的空白。
不是说HTML中的空白字符都被忽略吗?怎么这里又说全都是有效的字符?
在从文档解析生成DOM树的过程当中,HTML中的任何字符都是有效的;不过,在接下去的页面渲染的过程当中,空白内容被忽略。因此从文档到页面的整过过程当中,空白确实被忽略了。
理解DOM树中的父子关系对应文档中的包含(嵌套)关系。一个极佳的类比是文件树,把元素看作文件夹,文本看作文件,文件夹中能够存放文件和新的文件夹,而后一层层深刻下去,DOM树也是如此。
几个原则能够帮咱们快速理解这个DOM树的构建:
自动补全
, 上面提到的自动添加必要元素,另外,还会自动补齐缺乏的关闭标签。<head>
前面的空白会被忽略(历史缘由)。<body>
后若是有内容,会被移到<body>
里面。操做节点前,先要找到节点。导航是从一个节点到另外一个节点;搜索是从一个范围中选出知足条件的节点。
Node
的类规定了节点具备的许多属性,方便咱们从某个节点中找到跟它相关的另外节点。
顶级节点通常直接获取:
document
-- 入口节点。document.documentElement
-- HTML节点。document.head
-- head节点。document.body
-- body节点。对node
节点,有如下属性:
node.parentNode
-- 获取节点的父节点。
node.previousSibling
-- 获取节点的上一个兄弟节点。
node.nextSibling
-- 获取节点的下一个兄弟节点。
node.childNodes
-- 获取节点的孩子节点列表
。没有子节点返回空列表。
node.firstChild
和node.lastChild
-- 获取第一个和最后一个孩子节点,同node.childNodes[0]
和node.childNodes[node.childNodes.length-1]
。
助记:都是两个单词拼写的。
这些是属性,不是方法。不要错误使用bode.childNodes()之类的。
以上导航是基于节点的,包括元素节点、文本节点、注释等。如1.3中的例子,document.body.firstChild
获得的是A simple text
所在的文本节点。
由于咱们常常只关心元素节点,DOM也提供了一组纯元素的导航属性, 对元素elem
或者节点node
, 有:
node.parentElement
-- 父元素节点,该属性来自Node
类。elem.previousElementSibling
-- 上一个兄弟元素elem.nextElementSibling
-- 下一个兄弟元素elem.children
-- 孩子元素列表elem.firstElementChild
和elem.lastElementChild
,第一个和最后一个孩子元素节点助记:children特殊,其它都有Element,没有Node。
解惑:
node.parentNode
与node.parentElement
有区别吗?父节点不该该都是元素节点吗?通常状况下,两者等效,但html.parentNode === document
, document不是元素节点。
提醒:上面的导航属性都是只读属性。
elem.parentElement = anotherElem; 是错误的
。
常常地,咱们老是直接从文档中找出知足某些条件的元素,从而获取到目标元素。
document/element.getElementBy*()
系列getElementBy*
系列方法大家确定不陌生,但我想要提醒的是,注意个人写法document/element.getElementBy*()
,表示这是两个不一样的类上的方法(参看上面节点类)。
首先,是两个来自Document
类的方法:
document.getElementById(id)
: 根据id获取文档中的元素。document.getElementsByName(name)
: 根据name获取文档中的元素。不多使用。其次,是Document
类和Element
类都具备的方法:
document/element.getElementsByTagName(tagName)
: 根据标签名称获取文档或某个元素内的元素。document/element.getElementByClassName(className)
:根据类名获取文档或某个元素内的元素。注意:
没有element.getElementById(id)
。
注意:不要忘记或多加了
s
。除了document.getElementById()
,其它方法的返回结果都是一个集合,没有知足条件的元素则是空集合。
document/element.querySelectorAll/querySelector()
如今,推荐使用一组更强大的搜索新方法, 它们支持CSS选择器:
document/element.querySelectorAll(CSSSelector)
:返回知足选择器的一组节点列表。document/element.querySelector(CSSSelector)
: 返回第一个知足选择器的元素。补充:
elem.matches(selector)
能够检查某个元素是否与选择器匹配。
querySelector*
源自Selectors API 规范,与CSS选择器结合,更加灵活,强大,已被全部现代浏览器支持。是如今比较推荐的作法。
getElementBy*()
源自DOM2标准,被认为是传统接口,老项目中普遍使用。或者须要兼容IE8以前使用。可是有较好的性能,如今仍然有人在用。
补充:做为结果的集合。在以集合返回结果的时候,或者返回一个
NodeList
对象,或者返回一个HTMLCollection
对象,通常来讲,两者都是可迭代的类数组对象,能够经过下标范围或for...of
遍历。注意:大部分结果集,包括
getElementBy*
和node.childNode
等,都是动态的,即便查询并保存,以后若是修改文档,该结果集也会随之变化。但querySelectorAll()
返回的结果是静态的。
有一些特殊的元素搜索方法,就我所知有:
elem.closest(selector)
-- 在elemd
的父元素上查找最近一个知足选择器的元素。document.elementFromPoint(clientX,clientY)
-- 返回相对当前视口坐标嵌套最深的(最上方)的元素。这一节涉及对某个节点的操做,通常不会引发文档内容(DOM树)的变化。
这些操做基于**Node**接口,对全部节点都是通用的。
判断节点类型:node.nodeType
或node instanceof <NodeClass>
。二者均可以用于判断节点类型,当须要明确的节点类的时候,只能经过后者。
node.nodeType === Node.ELEMENT_NODE; //或node.nodeType === 1;
node instanceof Element; //与上面等效
node instanceof HTMLInputElement; //判断是不是输入元素
复制代码
获取节点名称:node.nodeName
,对于元素节点,返回对应的标签名称,如audio
。对其它类型节点,返回#与节点类型字符串,如#text
, #comment
,#document
。也能经过节点名称判断节点类型,但基本不用。
获取或设置节点值:node.nodeValue
,文本节点或注释节点返回文本内容,元素节点与document节点返回null。读写属性,支持node.nodeValue = "A simple text"
。一样的,空白文本也被包含在内容里。
提醒:文本节点和注释节点有一个
data
属性,使用与nodeValue
相同,但它不是在Node接口上的。
判断节点是否拥有子节点:node.hasChildNodes()
,返回true当节点有子节点时。
判断节点是否拥有特定子节点:node.contains(childNode)
,返回true当childNode是node的子节点。
大部分状况下咱们都是在元素节点上操做它的文本子节点,因此元素是咱们最关心的节点,**Element**接口提供了更多的属性和方法。这里只考虑HTML元素。
解惑:那何时须要操做文本节点?会看1.3节的例子,
A simple text
的父节点是body
,但body有其它元素节点。假如没有文本这种类型的节点,很难在body节点上只修改A simple text
的内容。
判断元素类型:elem.tagName
或elem.nodeName
,效果同样,返回标签的字符串,如audio
。另外,使用instanceof
能够实现不一样级别的类型判断。
元素内容:有几个属性和元素内容相关:
elem.innerHTML
-- 获取或设置元素内的HTML片断。设置的内容会被当成HTML片断解析,可能会引发文档结构的变化。
注意:HTML片断内的脚本不会执行。
elem.textContent
-- 获取或设置元素的文本内容(标签被忽略)。设置的文本以安全模式(不会被解析)写入。
特性(attribute)是指html中写在标签内的特性,而属性(property)是只元素节点做为编程对象具备的属性。
特性 -- 属性同步机制
:对标准规定的特性,元素对象具备响应的读写属性, 如a.href
。这种机制极大的方便了在JS中获取或修改元素的特性。
提醒:对不一样HTML元素,规定的特性不一样,属性也就不一样,如: 存在
a.href
但不存在div.href
。
通用的特性操做接口:
elem.hasAttribute(name)
-- 检查是否存在某个特性。elem.getAttribute(name)
-- 获取某一特性的值。elem.setAttribute(name, value)
-- 设置某一特性。elem.removeAttribute(name)
-- 删除某一特性。elem.attributes()
-- 获取全部的特性对,每一个特性对具备name
,value
属性。特殊的data-*
:data-*特性是一种合法且安全的传递自定义数据的方式。可经过elem.dataset.name
读取或修改data特性的值。属性名称采用驼峰写法,如elem上的data-apple-price
对应elem.dataset.applePrice
。
修改样式有两种方式,一是把样式写到某个类里,而后在代码中修改元素的类,一是直接修改elem.style.*
。前者适用于随状态改变样式的状况,在代码可维护性上更加,用得较多。后者适用于频繁计算或切换的样式。
elem.classList
: 一个包含elem全部类的可迭代的类数组对象。这个对象有几个方法,方便咱们改变元素的类。
elem.classList.contains(class)
-- 检查是否有某个类。elem.classList.add(class)
-- 添加某个类。elem.classList.remove(class)
-- 移除某个类。elem.classList.toggle(class)
-- 切换某个类,若是有就删除,没有就添加。elem.className
: 一个读写属性,把元素的class特性当成一个总体看待。如HTML中某个元素elem存在特性class="first red"
,那么elem.className
的值为first red
。区分:经过
elem.classList
或elem.className
均可以对元素的类进行改动,前者更加灵活,且具备相应的方法,后者是一个总体的字符串属性,适合删除全部的类从新设置。
若是须要直接设定元素的样式,能够设置elem.style.*
。
elem.style.width = "20px"; //设置元素宽度
elem.style.backgroundColor = "red"; //设置颜色
复制代码
提醒:多词CSS属性的转化方式也是驼峰写法,如
padding-left
对应elem.style.paddingLeft
,-webkit-border-radius
对应elem.style.WebkitBorderRadius
,-
意味着大写。
补充:常用
node.hidden = true
实现节点隐藏,也能用于隐藏文本,在元素上,它的效果与elem.style.display:none
一致。
这种方式其实是经过元素上的style
特性实现的,它的优先级高于通常的CSS,因此都是有效的,除非你在其它地方用了!important
。elem.style.*
也是可读取的,**可是它们只会读取元素中style
特性存在的属性,**对其它CSS是无效的。但咱们每每须要的是最终应用在元素的样式数值。这种时候,须要使用
getComputedStyle(elem)
:获取元素最终应用的样式,它返回一个样式对象,好比,可经过getComputedStyle(elem).backgroundColor
获取颜色值。注意:
getComputedStyle
不支持简写属性,如getComputedStyle(elem).padding是无效的。
<style> div { font-size: 20px; } </style>
<div id="div" style="padding:20px;">
czpcalm
</div>
<script> console.log(div.style.fontSize); //"" style特性上没有该CSS属性,因此为空 console.log(div.style.padding); //20px console.log(getComputedStyle(div).fontSize); //20px </script>
复制代码
若是须要清除代码设定的样式
, 能够把elem.style.cssAttr
设为""
(空字符串),反作用是原来html中的特性样式也会被清除。
还有一种基本不会使用的重写整个元素style特性的方式elem.cssText = csstext
,csstext不包含选择器和花括号。
当设计元素的大小变化或位置移动时,咱们须要获取元素的位置或尺寸。设置则用CSS方式设置。
位置是相对于参照物的,一个元素,有相对于定位父元素
,相对于视口
,相对于文档
三种关系位置。
提醒:定位父元素是指CSS定位元素(position为relative,absolute,fixed)或td,th,table元素,或者是body元素。
相对于定位父元素:elem.offsetLeft/offsetTop
,相对于参照父节点的左/上边距。elem.offsetParent
获取元素的定位父元素。
相对于视口:**elem.getBoundingClientRect()**获取元素的定位矩形elemRect
。
elem.getBoundingClientRect().left/top/right/bottom
分别表示元素盒子(含边框)四角到视口左或上边的距离。elem.getBoundingClientRect().width/height
, 与elem.offsetWidth/offsetHeight
等效,盒子的宽高。elem.getBoundingClientRect().x/y
,通常状况下,与elem.getBoundingClientRect().left/top
等效,且不兼容IE,不推荐使用。相对于文档:没有直接获取的方式,但能够经过相对于视口+滚动距离
简单计算。
elem.scrollTop + elem.getBoundingClientRect().top
。elem.scrollLeft + elem.getBoundingClientRect().left
。元素盒子的尺寸也有多种状况,须要考虑边框、内边距、是否为标准盒子模型、甚至是否有滚动条。
含边框的盒子尺寸:elem.offsetWidth/offsetHeight
或者elem.getBoundingClientRect().width/height
均可以获取含边框的宽高。
边框宽度:elem.clientLeft/clientTop
-- 元素左上角边框的宽度。能够理解为左边框宽度和上边框的宽度,若是存在滚动条,也包含滚动条的宽度。
补充:通常来讲,上边框和左边框是经常使用的。若是四条边框宽度不一,能够经过
getComputedStyle(elem).borderRight
获取,注意这是含单位的字符串。
elem.clientWidth/clientHeight
-- 内容宽度高度,包含padding,不包含滚动条。
不含边框:elem.clientWidth/clientHeight
,获取元素盒子内容宽高,不含边框和滚动条,含padding。
内边距问题与盒子类型:在涉及内边距的时候,须要getComputedStyle(elem)
方法获取,而且须要考虑是否为标准盒子。
若是元素的内容存在滚动时,咱们可能须要知道与滚动相关的尺寸:
elem.scrollLeft/scrollTop
-- 水平和垂直方向上滚动的长度。可写属性,经过设置该值改变控制内容滚动。
elem.scrollWidth/scrollHeight
-- 元素内容的长度和宽度,包括滚动的内容。
区分:在没有内容溢出发生滚动时,
clientWidth/Height
与scrollWidth/Height
等效;存在滚动时,前者是盒子的可视内容大小,后者是内容的大小,包括须要滚动查看的部分。
关于位置和尺寸,没有图片难以说清,不理解请参看:
DOM操做中,常常须要修改文档结果或内容。这类操做涉及节点的插入、移除、替换等。
文档的操做有一套普遍使用的传统方法。也有一套新推出的API方法,它们更加灵活易用,但IE不兼容。
插入节点能够分三步走:
建立一个节点:
let elem = document.createElement(tagName)
。let text = document.createTextNode(data)
。let dupNode = node.cloneNode(deep)
, deep为true表示深拷贝,经常使用。默认为false。编辑节点的属性和内容
把节点插入文档树中
传统方式:传统方式须要在父节点上执行对节点的插入
parentNode.appendChild(node)
-- 把node做为最后一个子节点插入。parentNode.insertBefore(node, nextSibling)
-- 在nextSibling以前插入node。现代方式:能够实现多位置插入,你能够在父节点上执行插入或在兄弟节点上执行插入。
parentNode.prepend(...nodes or strings)
。在第一个子节点以前插入。parentNode.append(...nodes or strings)
。在最后一个子节点以后插入。nextSibling.before(...nodes or strings)
。在本节点以前同级插入。previousSibling.after(...nodes or strings)
。在本节点以后同级插入。提醒:参数的形式说明它们支持一次插入多个,而且字符串会做为文本节点插入。
有一种状况,咱们但愿直接描述节点的插入HTML代码段,这种时候,可使用以前的elem.innerHTML
属性, 或者使用**elem.insertAdjacentHTML(position, html)**进行插入。其中,position
的可选值有:"beforebegin"
, "afterbegin"
, "beforeend"
, afterend
.
区分:节点对象存在不一样于节点在文档树中。前者只是在代码中能够经过node等变量名称引用节点,但建立一个节点,并不会对文档树结构有影响,只有插入文档树后,节点才成为文档的一部分。反之,从文档中移除节点,并不会致使节点变量实效,它仍是存在,能够继续被修改,并在某个时间点从新插入文档中。
从文档树中移除更加简单。
node.remove()
,移除节点。但IE不兼容,须要使用传统方式,获取其父节点,在父节点上移除子节点:node.parentNode.removeChild(node)
。与移除相似,使用node.repalceWith(...nodes or strings)
,一样IE使用parentNode.replaceChild(newNode, node)
。
事件
是某事发生的信号, 全部的DOM对象都具备这些信号。事件处理程序
是当一个事件信号发生时运行的函数,用于对事件做出响应。事件处理
就是为事件分配正确的处理函数,在事件发生时做出正确处理。事件类型
是事件的分类,常见的事件类型有
click
, contextmenu
, dbclick
, mousedown
, mouseup
,mouseover
, mouseout
等。keydown
, keyup
focus
, blur
, focusin
, focusout
,submit
, reset
cut
, copy
, paste
load
, unload
, error
, abort
事件对象
是事件在编程上的对象,具备不少和事件相关的属性。经常在处理函数调用时自动做为参数传入。**假设一个div元素中放了一个button元素,若是点击了button,算不算点击了div?若是算,是先点击了button仍是先点击了div?**由于元素的嵌套关系,使得事件的产生对象每每是嵌套的,而不是独立的,在上面的中,还有body元素,html元素,甚至整个文档document。
第一个问题,对大多数的事件来讲,应该是确定的,一个在子元素上发生的事件,也看作在该元素上发生了。
第二个问题,也被当作事件流
问题,即如何肯定事件在节点中的传播顺序。巧的是,当年IE和Netscape分别提出了事件冒泡
和事件捕获
这两个几乎彻底相反的概念。
button->div->body->html->document
。document->html->body->div->button
。为了统一这两种观点,DOM事件规范提出了3阶段的事件流:事件捕获阶段->目标阶段->事件冒泡阶段
。
然而,事件捕获阶段不多被使用,下面的讨论会忽略事件捕获,默认事件从目标上开始冒泡。
注意:并不是全部事件都会冒泡。
有3种为设置事件处理函数的方式:
on<event>
特性。on<event>
属性。node.addEventListener(event, handler, capture)
和removeEventListener(event, handler)
。capture
是一个布尔值,表示是否在捕获阶段响应。对比:HTML特性的方式具备很大的局限性,代码量限制,维护性差,某些事件不支持等。DOM0级事件也对某些事件不支持,关键在于没法为一个事件类型分配多个处理函数。DOM2解决了这些问题,是最通用的方式,推荐使用。
<div id="div1" onclick="alert(event.type+': div1')">div1</div>
<div id="div2" onclick="handler('div2',event)">div2</div>
<div id="div3">div3</div>
<div id="div4">div4</div>
<script> function handler(message, event) { alert(event.type + ": " + message); } div3.onclick = e => handler("div3", e); //处理函数具备多参数,须要嵌套调用。 div4.addEventListener("click", e => handler("div4", e), false); </script>
复制代码
上面的例子中, div1和div2使用了HTML特性添加事件处理,div3使用DOM0级事件处理,div4使用DOM2级事件处理。
HTML特性的内容会成为事件处理函数的内容,DOM事件处理赋值的是函数对象。好比对一个处理函数func
,特性上的写法是onclick="func()"
,而DOM0级事件处理的写法是node.onclick=func
。区分它们的主要点在于理解特性的内容会被套上一个函数外壳,而后成为处理函数。onclick="func()"
最后的效果是:
node.onclick = function(event){ //传递的事件参数名为event
func(); //这一行是来自特性
}
复制代码
这样,你应该能明白为何特性上是函数调用,而DOM上是函数对象赋值了。
由于这种设定,还产生两个要注意的点。
第一是事件对象参数event
,若是你习惯了在DOM事件处理中使用变量名称e
而不是event
,那么在特性内容上可能写出相似e.type
的表达,这是错误的,由于特性传入的事件变量名是event
。
第二,在试图阻止事件的默认响应的时候,可能会写出这样的代码:
<a href="www.baidu.com" onclick="handler()">百度</a>
<script> function handler(){ /*....处理点击事件*/ return false; //返回false阻止跳转 } </script>
复制代码
然而点击以后仍是发生跳转了,问题出在哪里?让咱们看一下最后生成的处理函数:
a.onclick = function(event){
handler();
}
复制代码
事件处理函数只是调用了handler
,handler
返回了false,而事件处理函数并无理会,因此,外部看来,事件处理函数没有返回值。正确的写法应该是onclick="return handler()"
或者onclick="handler();return false;"
。
DOM2级事件处理以前,一个事件只能对应一个事件处理函数。若是你想解除事件处理函数,能够简单的采用node.on<event>=null
的方式。
DOM2node.addEventListener()
支持添加多个处理函数,这些处理函数的执行顺序与添加顺序一致,可使用node.removeEventListener(event, handler)
解除处理函数。
注意:
removeEventListener(handler)
必须使用添加时的同一个函数,而不是具备相同执行体的函数。node.addEventListener("click", ()=>alert("直接添加函数表达式将没法别移除!"), false); let handler = ()=>alert("something") node.addEventListener("click", handler, false); node.removeEventListerner("click", ()=>alert("somthing")); //无效,传入的是不一样的函数对象 node.removeEventListener("click", handler); //成功移除 复制代码
让咱们看另外一个问题,事件处理函数的参数问题。
咱们只编写事件处理函数,函数在事件发生时自动被调用,它的调用语句相似这样
node.handler(event); //event在这里是实参,这句语句是函数调用
复制代码
咱们要根据调用规范本身的handler
函数。
调用时传入了一个参数,根据JS函数参数的特色,若是在事件处理函数中没有使用到event
,那处理函数能够不声明这个参数。其次,形参可使用任何合法变量名,咱们能够不函数定义写成handler(e)
,而后在函数内使用e
而不是event
。
它的调用方式决定了handler
最多只能有一个参数。但在事件处理时确实有须要多个参数的时候,这种时候要使用嵌套的函数调用,如上面的例子。
最后,注意这种调用方式使得函数内部this === node
。这颇有用,方便咱们在事件处理的时候获取到事件发生对象的信息。
事件对象event
在调用事件处理函数时自动传入,它具备不少属性和方法,在事件处理的时候大有用处。
event.preventDefault()
-- 取消事件的默认行为,只有event.cancelable
为true时才有效。
补充:若是使用
on<event>
方式添加处理函数,在函数返回false也能够取消默认事件。
event.stopPropagation()
-- 中止事件的继续冒泡,上层的事件响应不会再发生。
event.stopImmediatePropagation()
-- 中止事件继续响应。包括事件冒泡和当前目标的其它处理函数也不会发生。
event.type
-- 事件类型字符串,在使用一个处理函数处理多类事件时,能够判断当前发生的事件类型。
**event.target
与event.currentTarget
**-- 事件的最小目标和事件的当前目标。
区分:事件在最小目标上发生,而后往上冒泡的过程当中,
event.target
始终不变,指向最小目标,但event.currentTarget === this
,会随着冒泡过程指向当前正在处理的节点。
event.phase
-- 事件流阶段,整数,1表明捕获,2表明目标阶段,3表明冒泡阶段。
event.bubbles
-- 布尔值,事件是否冒泡。
event.cancelable
-- 布尔值,事件是否可取消默认行为。
event.trusted
-- 布尔值,若是事件是浏览器发生的,为true,若是事件是js代码发生的,为false。
上面的属性都是只读的。
这些属性和方法是通用的,对具体的事件类型,有更多的事件属性和方法,将在每种事件中详细说明。
鼠标事件是最多见的一类事件,有:
click
-- 鼠标左键点击触发,或触摸屏的点击。
提醒:点击一次的含义是鼠标在目标上按下并松起,若是鼠标按下后滑动到元素外部松起,或者元素位置变化致使鼠标松起时再也不元素上方,不能造成有效点击。
contextmenu
-- 鼠标右键点击事件,该事件浏览器通常有默认的菜单,若是须要实现自定义菜单,须要阻止默认行为。
dbclick
-- 双击鼠标左键。双击具备选择文本的默认行为。
mousedown/mouseup
-- 鼠标任意键按下/松起。
mouseenter/mouseleave
-- 鼠标进入/离开元素,不会冒泡。
mouseover/mouseout
-- 鼠标进入/离开元素,会冒泡,进入/离开子元素时也会触发。
区分:
mouseenter/mouseleave
与mouseover/mouseout
事件相似,可是后者会冒泡,且在进入子元素会触发mouseout
。
mousemove
-- 鼠标按下后松起前发送移动。
鼠标点击时,会发生一系列事件,它们具备特定的顺序,以某次双击为例,依次触发事件mousedown->mouseup->click->mousedown->mouseup->click->dbclick
。
鼠标事件有一些适合获取事件相关信息的属性:
鼠标按键:按键属性只对mousedown/mouseup
有意义,event.button
,数字1~5
,表明鼠标上的按键,依次是:鼠标左键,中键,右键,前进,后退。
坐标:它们是事件发生时刻(定点类)的坐标,或者实时的(mousemove
)坐标。
event.pageX/pageY
-- 相对于文档的坐标。event.clientX/clientY
-- 相对于窗口的坐标。event.screenX/screenY
-- 相对于屏幕的坐标,较少使用。组合键:在鼠标事件发生时,若是下列按键被按下,对应的属性为true。用于在一个事件类型上绑定多种任务。
event.shiftKey
-- shift键
是否被按下。event.ctrlKey
-- ctrl键
是否被按下。event.altKey
-- alt键
是否被按下。event.mateKey
-- cmd键
(Mac专用)是否被按下。提醒:若是想处理
ctrl键
,应该注意在Mac下使用cmd键
,因此应该判断if(event.ctrlKey||event.cmdKey)
。
相关目标:event.relatedTarget
。mouseenter/mouseleave
和mouseover/mouseout
事件的属性。若是鼠标从divA->divB
,在divA上发生mouseout(mouseleave),event.target === divA
而且event.relatedTarget === divB
。相反,在divB上发生mouseover(mouseenter),event.target ===divB
且event.relatedTarget === divA
。
这是一篇鼠标拖放事件的文章,应该能对鼠标事件的使用有所帮助。
键盘事件经常被用于建立各类热键。如今普遍使用的有两类键盘事件:
keydown
-- 按下任意键盘触发。持续按住按键会持续触发该事件。keyup
-- 松开任意按键。补充:不要试图监听
Fn键
,它是在比OS更低的级别上实现的,没有键盘事件。补充:有一个被取消的
keypress
事件,这里不讨论。
键盘事件最重要的属性是键码,咱们经常须要获取键盘的键码,根据按下的键盘作出响应。
event.code
-- 键码。键码是惟一的。在按键判断时,常用的属性。键码是字符串,常见规则:
Digit<num>
或Numpad<num>
(小键盘)。如Digit2
, Numpad2
Key<letter>
, 如KeyZ
, KeyA
F4
, Tab
,Enter
Left/Right
区分左右,左shift的键码为ShiftLeft
, 右shift的键码为ShiftRight
。补充:一个被废弃的
event.keyCode
与event.code
有一样的功能,但键码是基于数字的。可能在兼容IE时须要用到。
event.key
-- 键。表征按键的含义而不是位置。跟是否按下shift
、键盘语言有关。如按下z键
时表示字符z
,而shift+z
表示字符Z
。左右shift的key
也同样。只在使用基于意义的时候才使用event.key
判断。
键盘事件也一样支持组合键,可使用event.ctrlKey
, event.shiftKey
, event.altKey
, event.metaKey
获取其它按键是否被按下,而不用单独监听它们。
有一类与资源相关的事件,认识它们的最好方式是经过页面生命周期:
DOMContentLoaded
-- 文档被加载而且DOM树构建完成后(图片、样式可能还未加载)。load
-- 页面内容加载完毕,全部图片、样式资源都已应用。此时进行的操做都是安全的。beforeunload
-- 用户正打算离开,能够在此时讯问用户是否离开,或者保存一些数据。unload
-- 用户已经离开,但仍能够进行少许操做,如释放资源,发送数据。load
事件是这里最重要的一个事件。它不只能够用于确保页面加载完成后执行任务,还能够添加到img
, script
, style
这些节点中,在它们完成资源加载时候进行事件处理。
表单是一类特殊的元素,须要频繁的获取或修改,因此,通用的节点获取方式上,增长了额外的属性,方便表单操做。
document.forms
-- 获取文档中的表单元素集合。这是一个命名集合
。命名意味着能够经过表单名称方式document.forms.formName
获取表单,而集合自己又支持下标方式document.form[0]
。
form.elements
-- 获取表单form的输入元素集合,一样,这是一个动态的
命名集合。能够经过form.elements.inputName
或者form.elements[index/inputName]
获取。
form.inputName
或form[index/inputName]
-- 获取表单form的输入元素。form.elements.inputName
的简写。
注意:
(1)radio、checkbox等多个输入共享一个name时,
form.elements.inputName
或form.inputName
返回一个集合。(2)没法获取到type为image的输入组件。
(3)若是表单中有
<fieldset>
(输入组),它会成为一个form.elements
的一个元素,可进一步经过fieldset.elements
获取表单组中的输入控件集合。
input.formName
-- 获取输入元素所在的表单。form.inputName
的反向引用。
<form name="myForm">
<input type="text" name="user"/>
<input type="radio" name="sex" value="male"/>
<input type="radio" name="sex" value="female"/>
</form>
<script> let form = document.forms.myForm; //或者document.forms[0] let userInput = form.user; //或者form.elements.user let sexInput = form.sex; sexInput[0].checked = true; //选择第一个radio console.log(sexInput.value); //male </script>
复制代码
根据属性--特性同步,表单对象有如下属性,它们与对应的特性意义相同。
form.action
form.method
form.name
form.target
另外,表单有两个经常使用的方法:
form.submit()
-- 手动提交表单form.reset()
-- 初始化表单内容一个表单含有多个输入字段,它们能够是各种型的input标签
,select标签
,textarea标签
,button标签
。每这些表单字段有一些通用的属性(固然还有其它的同步属性):
input.value
-- 字段的值。对type="file"
的input,它是只读的,表示计算机上的文件地址。input.name
-- 字段名称。input.type
-- 字段类型,对textarea
是"textarea"
。对select
是"select-one"
或"select-multiple"
。input.tabindex
-- 字段的tab索引。input.disabled
-- 字段是否被禁用。input.readOnly
-- 字段是否只可读。这些属性都是可写的,大大便利了咱们对表单元素的操做。
最关键,要数input.value
,对不一样字段,它有不一样的用法:
button
, reset
, submit
类型:表示用于显示的字符串。radio
, select(单选)
类型:一个与被选中字段value相同的值。textbox
,select(多选)
类型:一个数组,对应被勾选的选线的值。有些事件是表单特有的:
submit
-- 表单提交前发生的事件。reset
-- 点击reset按钮时。input
-- 在字段输入任意内容后。change
-- 字段失去焦点后,与以前输入发生变化时。此外,也有一些事件在表单上发挥了主要做用:
pressdown
focus/blur
-- 得到或焦点时,不会冒泡。
focusin/focusout
-- 得到或失去焦点时,与上相似,可是会冒泡。
cut/copy/paste
-- 剪切板事件
补充:能够经过
e.preventDefault()
(或返回false)取消剪切板事件默认行为,来禁用页面/输入的剪切板功能。event.clipboardData
可用于读写剪切板内容。input.addEventListener("past", (event)=>{ let data = e.clipboardData; alert("Data in clipboard: " + data+". But you can't paste!"); return false; }) 复制代码
表单客户端验证几乎成为表单最重要的一个功能,这但是JS诞生的原因呀!
html5提供了一套表单的验证API,但我的认为难用且界面依赖浏览器,使用很少,更可能是自定义表单的验证方式。
表单验证主要有两种思路,一是在客户完成每一个字段输入时,独立验证,每一个字段,全部字段都经过后才容许提交操做。这种方式主要利用change
, input
等事件。
一是在用户发起提交操做时,对整个表单进行验证,若是不经过则提醒用户更正,阻止表单提交,直到全部字段正确。这种方式主要使用submit
事件。
累了,贴个图总结吧。
花了我好几天,一边学习,一边整理。感谢阅读!