上面代码中,删除
color属性以后,字体颜色从红色变成默认颜色。
(5)CSSStyleDeclaration.setProperty()
CSSStyleDeclaration.setProperty方法用来设置新的 CSS 属性。该方法没有返回值。
该方法可以接受三个参数。
第一个参数:属性名,该参数是必需的。
第二个参数:属性值,该参数可选。如果省略,则参数值默认为空字符串。
第三个参数:优先级,该参数可选。如果设置,唯一的合法值是
important,表示 CSS 规则里面的
!important。
// HTML 代码为
//
var style = document.getElementById('myDiv').style;
style.setProperty('border', '1px solid blue');
上面代码执行后,
myDiv元素就会出现蓝色的边框。
CSS 模块的侦测
CSS 的规格发展太快,新的模块层出不穷。不同浏览器的不同版本,对 CSS 模块的支持情况都不一样。有时候,需要知道当前浏览器是否支持某个模块,这就叫做“CSS模块的侦测”。
一个比较普遍适用的方法是,判断元素的
style对象的某个属性值是否为字符串。
typeof element.style.animationName === 'string';
typeof element.style.transform === 'string';
如果该 CSS 属性确实存在,会返回一个字符串。即使该属性实际上并未设置,也会返回一个空字符串。如果该属性不存在,则会返回
undefined。
document.body.style['maxWidth'] // ""
document.body.style['maximumWidth'] // undefined
上面代码说明,这个浏览器支持
max-width属性,但是不支持
maximum-width属性。
注意,不管 CSS 属性名的写法带不带连词线,
style属性上都能反映出该属性是否存在。
document.body.style['backgroundColor'] // ""
document.body.style['background-color'] // ""
另外,使用的时候,需要把不同浏览器的 CSS 前缀也考虑进去。
var content = document.getElementById('content');
typeof content.style['webkitAnimation'] === 'string'
这种侦测方法可以写成一个函数。
function isPropertySupported(property) {
if (property in document.body.style) return true;
var prefixes = ['Moz', 'Webkit', 'O', 'ms', 'Khtml'];
var prefProperty = property.charAt(0).toUpperCase() + property.substr(1);
for(var i = 0; i < prefixes.length; i++){
if((prefixes[i] + prefProperty) in document.body.style) return true;
}
return false;
}
isPropertySupported('background-clip')
// true
CSS 对象
浏览器原生提供 CSS 对象,为 JavaScript 操作 CSS 提供一些工具方法。
这个对象目前有两个静态方法。
CSS.escape()
CSS.escape方法用于转义 CSS 选择器里面的特殊字符。
上面代码中,该元素的
id属性包含一个
#号,该字符在 CSS 选择器里面有特殊含义。不能直接写成
document.querySelector('#foo#bar'),只能写成
document.querySelector('#foo\\#bar')。这里必须使用双斜杠的原因是,单引号字符串本身会转义一次斜杠。
CSS.escape方法就用来转义那些特殊字符。
document.querySelector('#' + CSS.escape('foo#bar'))
CSS.supports()
CSS.supports方法返回一个布尔值,表示当前环境是否支持某一句 CSS 规则。
它的参数有两种写法,一种是第一个参数是属性名,第二个参数是属性值;另一种是整个参数就是一行完整的 CSS 语句。
// 第一种写法
CSS.supports('transform-origin', '5px') // true
// 第二种写法
CSS.supports('display: table-cell') // true
注意,第二种写法的参数结尾不能带有分号,否则结果不准确。
CSS.supports('display: table-cell;') // false
window.getComputedStyle()
行内样式(inline style)具有最高的优先级,改变行内样式,通常会立即反映出来。但是,网页元素最终的样式是综合各种规则计算出来的。因此,如果想得到元素实际的样式,只读取行内样式是不够的,需要得到浏览器最终计算出来的样式规则。
window.getComputedStyle()方法,就用来返回浏览器计算后得到的最终规则。它接受一个节点对象作为参数,返回一个 CSSStyleDeclaration 实例,包含了指定节点的最终样式信息。所谓“最终样式信息”,指的是各种 CSS 规则叠加后的结果。
var div = document.querySelector('div');
var styleObj = window.getComputedStyle(div);
styleObj.backgroundColor
上面代码中,得到的背景色就是
div元素真正的背景色。
注意,CSSStyleDeclaration 实例是一个活的对象,任何对于样式的修改,会实时反映到这个实例上面。另外,这个实例是只读的。
getComputedStyle方法还可以接受第二个参数,表示当前元素的伪元素(比如
:before、
:after、
:first-line、
:first-letter等)。
var result = window.getComputedStyle(div, ':before');
下面的例子是如何获取元素的高度。
var elem = document.getElementById('elem-container');
var styleObj = window.getComputedStyle(elem, null)
var height = styleObj.height;
// 等同于
var height = styleObj['height'];
var height = styleObj.getPropertyValue('height');
上面代码得到的
height属性,是浏览器最终渲染出来的高度,比其他方法得到的高度更可靠。由于
styleObj是 CSSStyleDeclaration 实例,所以可以使用各种 CSSStyleDeclaration 的实例属性和方法。
有几点需要注意。
CSSStyleDeclaration 实例返回的 CSS 值都是绝对单位。比如,长度都是像素单位(返回值包括
px后缀),颜色是
rgb(#, #, #)或
rgba(#, #, #, #)格式。
CSS 规则的简写形式无效。比如,想读取
margin属性的值,不能直接读,只能读
marginLeft、
marginTop等属性;再比如,
font属性也是不能直接读的,只能读
font-size等单个属性。
如果读取 CSS 原始的属性名,要用方括号运算符,比如
styleObj['z-index'];如果读取骆驼拼写法的 CSS 属性名,可以直接读取
styleObj.zIndex。
该方法返回的 CSSStyleDeclaration 实例的
cssText属性总是返回空字符串。
CSS 伪元素
CSS 伪元素是通过 CSS 向 DOM 添加的元素,主要是通过
:before和
:after选择器生成,然后用
content属性指定伪元素的内容。
下面是一段 HTML 代码。
Test content
CSS 添加伪元素
:before的写法如下。
#test:before {
content: 'Before ';
color: #FF0;
}
节点元素的
style对象无法读写伪元素的样式,这时就要用到
window.getComputedStyle()。JavaScript 获取伪元素,可以使用下面的方法。
var test = document.querySelector('#test');
var result = window.getComputedStyle(test, ':before').content;
var color = window.getComputedStyle(test, ':before').color;
此外,也可以使用 CSSStyleDeclaration 实例的
getPropertyValue方法,获取伪元素的属性。
var result = window.getComputedStyle(test, ':before')
.getPropertyValue('content');
var color = window.getComputedStyle(test, ':before')
.getPropertyValue('color');
StyleSheet 接口
概述
StyleSheet接口代表网页的一张样式表,包括
元素加载的样式表和
var myStyleSheet = document.getElementById('myStyle').sheet;
myStyleSheet instanceof StyleSheet // true
严格地说,
StyleSheet接口不仅包括网页样式表,还包括 XML 文档的样式表。所以,它有一个子类
CSSStyleSheet表示网页的 CSS 样式表。我们在网页里面拿到的样式表实例,实际上是
CSSStyleSheet的实例。这个子接口继承了
StyleSheet的所有属性和方法,并且定义了几个自己的属性,下面把这两个接口放在一起介绍。
实例属性
StyleSheet实例有以下属性。
(1)StyleSheet.disabled
StyleSheet.disabled返回一个布尔值,表示该样式表是否处于禁用状态。手动设置
disabled属性为
true,等同于在
元素里面,将这张样式表设为
alternate stylesheet,即该样式表将不会生效。
注意,
disabled属性只能在 JavaScript 脚本中设置,不能在 HTML 语句中设置。
(2)StyleSheet.href
StyleSheet.href返回样式表的网址。对于内嵌样式表,该属性返回
null。该属性只读。
document.styleSheets[0].href
(3)StyleSheet.media
StyleSheet.media属性返回一个类似数组的对象(
MediaList实例),成员是表示适用媒介的字符串。表示当前样式表是用于屏幕(screen),还是用于打印(print)或手持设备(handheld),或各种媒介都适用(all)。该属性只读,默认值是
screen。
document.styleSheets[0].media.mediaText
// "all"
MediaList实例的
appendMedium方法,用于增加媒介;
deleteMedium方法用于删除媒介。
document.styleSheets[0].media.appendMedium('handheld');
document.styleSheets[0].media.deleteMedium('print');
(4)StyleSheet.title
StyleSheet.title属性返回样式表的
title属性。
(5)StyleSheet.type
StyleSheet.type属性返回样式表的
type属性,通常是
text/css。
document.styleSheets[0].type // "text/css"
(6)StyleSheet.parentStyleSheet
CSS 的
@import命令允许在样式表中加载其他样式表。
StyleSheet.parentStyleSheet属性返回包含了当前样式表的那张样式表。如果当前样式表是顶层样式表,则该属性返回
null。
if (stylesheet.parentStyleSheet) {
sheet = stylesheet.parentStyleSheet;
} else {
sheet = stylesheet;
}
(7)StyleSheet.ownerNode
StyleSheet.ownerNode属性返回
StyleSheet对象所在的 DOM 节点,通常是
或
var myStyleSheet = document.getElementById('myStyle').sheet;
var crl = myStyleSheet.cssRules;
crl instanceof CSSRuleList // true
CSSRuleList 实例里面,每一条规则(CSSRule 实例)可以通过
rules.item(index)或者
rules[index]拿到。CSS 规则的条数通过
rules.length拿到。还是用上面的例子。
crl[0] instanceof CSSRule // true
crl.length // 2
注意,添加规则和删除规则不能在 CSSRuleList 实例操作,而要在它的父元素 StyleSheet 实例上,通过
StyleSheet.insertRule()和
StyleSheet.deleteRule()操作。
CSSRule 接口
概述
一条 CSS 规则包括两个部分:CSS 选择器和样式声明。下面就是一条典型的 CSS 规则。
.myClass {
color: red;
background-color: yellow;
}
JavaScript 通过 CSSRule 接口操作 CSS 规则。一般通过 CSSRuleList 接口(
StyleSheet.cssRules)获取 CSSRule 实例。
// HTML 代码如下
//
var myStyleSheet = document.getElementById('myStyle').sheet;
var ruleList = myStyleSheet.cssRules;
var rule = ruleList[0];
rule instanceof CSSRule // true
CSSRule 实例的属性
(1)CSSRule.cssText
CSSRule.cssText属性返回当前规则的文本,还是使用上面的例子。
rule.cssText
// ".myClass { color: red; background-color: yellow; }"
如果规则是加载(
@import)其他样式表,
cssText属性返回
@import 'url'。
(2)CSSRule.parentStyleSheet
CSSRule.parentStyleSheet属性返回当前规则所在的样式表对象(StyleSheet 实例),还是使用上面的例子。
rule.parentStyleSheet === myStyleSheet // true
(3)CSSRule.parentRule
CSSRule.parentRule属性返回包含当前规则的父规则,如果不存在父规则(即当前规则是顶层规则),则返回
null。
父规则最常见的情况是,当前规则包含在
@media规则代码块之中。
// HTML 代码如下
//
var myStyleSheet = document.getElementById('myStyle').sheet;
var ruleList = myStyleSheet.cssRules;
var rule0 = ruleList[0];
rule0.cssText
// "@supports (display: flex) {
// @media screen and (min-width: 900px) {
// article { display: flex; }
// }
// }"
// 由于这条规则内嵌其他规则,
// 所以它有 cssRules 属性,且该属性是 CSSRuleList 实例
rule0.cssRules instanceof CSSRuleList // true
var rule1 = rule0.cssRules[0];
rule1.cssText
// "@media screen and (min-width: 900px) {
// article { display: flex; }
// }"
var rule2 = rule1.cssRules[0];
rule2.cssText
// "article { display: flex; }"
rule1.parentRule === rule0 // true
rule2.parentRule === rule1 // true
(4)CSSRule.type
CSSRule.type属性返回一个整数值,表示当前规则的类型。
最常见的类型有以下几种。
1:普通样式规则(CSSStyleRule 实例)
3:
@import规则
4:
@media规则(CSSMediaRule 实例)
5:
@font-face规则
CSSStyleRule 接口
如果一条 CSS 规则是普通的样式规则(不含特殊的 CSS 命令),那么除了 CSSRule 接口,它还部署了 CSSStyleRule 接口。
CSSStyleRule 接口有以下两个属性。
(1)CSSStyleRule.selectorText
CSSStyleRule.selectorText属性返回当前规则的选择器。
var stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].selectorText // ".myClass"
注意,这个属性是可写的。
(2)CSSStyleRule.style
CSSStyleRule.style属性返回一个对象(CSSStyleDeclaration 实例),代表当前规则的样式声明,也就是选择器后面的大括号里面的部分。
// HTML 代码为
//
var styleSheet = document.getElementById('myStyle').sheet;
styleSheet.cssRules[0].style instanceof CSSStyleDeclaration
// true
CSSStyleDeclaration 实例的
cssText属性,可以返回所有样式声明,格式为字符串。
styleSheet.cssRules[0].style.cssText
// "color: red;"
styleSheet.cssRules[0].selectorText
// "p"
CSSMediaRule 接口
如果一条 CSS 规则是
@media代码块,那么它除了 CSSRule 接口,还部署了 CSSMediaRule 接口。
该接口主要提供
media属性和
conditionText属性。前者返回代表
@media规则的一个对象(MediaList 实例),后者返回
@media规则的生效条件。
// HTML 代码如下
//
var styleSheet = document.getElementById('myStyle').sheet;
styleSheet.cssRules[0] instanceof CSSMediaRule
// true
styleSheet.cssRules[0].media
// {
// 0: "screen and (min-width: 900px)",
// appendMedium: function,
// deleteMedium: function,
// item: function,
// length: 1,
// mediaText: "screen and (min-width: 900px)"
// }
styleSheet.cssRules[0].conditionText
// "screen and (min-width: 900px)"
window.matchMedia()
基本用法
window.matchMedia()方法用来将 CSS 的
Media Query条件语句,转换成一个 MediaQueryList 实例。
var mdl = window.matchMedia('(min-width: 400px)');
mdl instanceof MediaQueryList // true
上面代码中,变量
mdl就是 mediaQueryList 的实例。
注意,如果参数不是有效的
MediaQuery条件语句,
window.matchMedia不会报错,依然返回一个 MediaQueryList 实例。
window.matchMedia('bad string') instanceof MediaQueryList // true
MediaQueryList 接口的实例属性
MediaQueryList 实例有三个属性。
(1)MediaQueryList.media
MediaQueryList.media属性返回一个字符串,表示对应的 MediaQuery 条件语句。
var mql = window.matchMedia('(min-width: 400px)');
mql.media // "(min-width: 400px)"
(2)MediaQueryList.matches
MediaQueryList.matches属性返回一个布尔值,表示当前页面是否符合指定的 MediaQuery 条件语句。
if (window.matchMedia('(min-width: 400px)').matches) {
/* 当前视口不小于 400 像素 */
} else {
/* 当前视口小于 400 像素 */
}
下面的例子根据
mediaQuery是否匹配当前环境,加载相应的 CSS 样式表。
var result = window.matchMedia("(max-width: 700px)");
if (result.matches){
var linkElm = document.createElement('link');
linkElm.setAttribute('rel', 'stylesheet');
linkElm.setAttribute('type', 'text/css');
linkElm.setAttribute('href', 'small.css');
document.head.appendChild(linkElm);
}
(3)MediaQueryList.onchange
如果 MediaQuery 条件语句的适配环境发生变化,会触发
change事件。
MediaQueryList.onchange属性用来指定
change事件的监听函数。该函数的参数是
change事件对象(MediaQueryListEvent 实例),该对象与 MediaQueryList 实例类似,也有
media和
matches属性。
var mql = window.matchMedia('(max-width: 600px)');
mql.onchange = function(e) {
if (e.matches) {
/* 视口不超过 600 像素 */
} else {
/* 视口超过 600 像素 */
}
}
上面代码中,
change事件发生后,存在两种可能。一种是显示宽度从600像素以上变为以下,另一种是从600像素以下变为以上,所以在监听函数内部要判断一下当前是哪一种情况。
MediaQueryList 接口的实例方法
MediaQueryList 实例有两个方法
MediaQueryList.addListener()和
MediaQueryList.removeListener(),用来为
change事件添加或撤销监听函数。
var mql = window.matchMedia('(max-width: 600px)');
// 指定监听函数
mql.addListener(mqCallback);
// 撤销监听函数
mql.removeListener(mqCallback);
function mqCallback(e) {
if (e.matches) {
/* 视口不超过 600 像素 */
} else {
/* 视口超过 600 像素 */
}
}
注意,
MediaQueryList.removeListener()方法不能撤销
MediaQueryList.onchange属性指定的监听函数。
Mutation Observer API
概述
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。
这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入1000个
元素,就会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。
Mutation Observer 有以下特点。
它等待所有脚本任务完成后,才会运行(即异步触发方式)。
它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
MutationObserver 构造函数
使用时,首先使用
MutationObserver构造函数,新建一个观察器实例,同时指定这个实例的回调函数。
var observer = new MutationObserver(callback);
上面代码中的回调函数,会在每次 DOM 变动后调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,下面是一个例子。
var observer = new MutationObserver(function (mutations, observer) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});
MutationObserver 的实例方法
observe()
observe()方法用来启动监听,它接受两个参数。
第一个参数:所要观察的 DOM 节点
第二个参数:一个配置对象,指定所要观察的特定变动
var article = document.querySelector('article');
var options = {
'childList': true,
'attributes':true
} ;
observer.observe(article, options);
上面代码中,
observe()方法接受两个参数,第一个是所要观察的DOM元素是
article,第二个是所要观察的变动类型(子节点变动和属性变动)。
观察器所能观察的 DOM 变动类型(即上面代码的
options对象),有以下几种。
childList:子节点的变动(指新增,删除或者更改)。
attributes:属性的变动。
characterData:节点内容或节点文本的变动。
想要观察哪一种变动类型,就在
option对象中指定它的值为
true。需要注意的是,至少必须同时指定这三种观察的一种,若均未指定将报错。
除了变动类型,
options对象还可以设定以下属性:
subtree:布尔值,表示是否将该观察器应用于该节点的所有后代节点。
attributeOldValue:布尔值,表示观察
attributes变动时,是否需要记录变动前的属性值。
characterDataOldValue:布尔值,表示观察
characterData变动时,是否需要记录变动前的值。
attributeFilter:数组,表示需要观察的特定属性(比如
['class','src'])。
// 开始监听文档根节点(即标签)的变动
mutationObserver.observe(document.documentElement, {
attributes: true,
characterData: true,
childList: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
});
对一个节点添加观察器,就像使用
addEventListener()方法一样,多次添加同一个观察器是无效的,回调函数依然只会触发一次。如果指定不同的
options对象,以后面添加的那个为准,类似覆盖。
下面的例子是观察新增的子节点。
var insertedNodes = [];
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
for (var i = 0; i < mutation.addedNodes.length; i++) {
insertedNodes.push(mutation.addedNodes[i]);
}
});
console.log(insertedNodes);
});
observer.observe(document, { childList: true, subtree: true });
disconnect(),takeRecords()
disconnect()方法用来停止观察。调用该方法后,DOM 再发生变动,也不会触发观察器。
observer.disconnect();
takeRecords()方法用来清除变动记录,即不再处理未处理的变动。该方法返回变动记录的数组。
observer.takeRecords();
下面是一个例子。
// 保存所有没有被观察器处理的变动
var changes = mutationObserver.takeRecords();
// 停止观察
mutationObserver.disconnect();
MutationRecord 对象
DOM 每次发生变化,就会生成一条变动记录(MutationRecord 实例)。该实例包含了与变动相关的所有信息。Mutation Observer 处理的就是一个个
MutationRecord实例所组成的数组。
MutationRecord对象包含了DOM的相关信息,有如下属性:
type:观察的变动类型(
attributes、
characterData或者
childList)。
target:发生变动的DOM节点。
addedNodes:新增的DOM节点。
removedNodes:删除的DOM节点。
previousSibling:前一个同级节点,如果没有则返回
null。
nextSibling:下一个同级节点,如果没有则返回
null。
attributeName:发生变动的属性。如果设置了
attributeFilter,则只返回预先指定的属性。
oldValue:变动前的值。这个属性只对
attribute和
characterData变动有效,如果发生
childList变动,则返回
null。
应用示例
子元素的变动
下面的例子说明如何读取变动记录。
var callback = function (records){
records.map(function(record){
console.log('Mutation type: ' + record.type);
console.log('Mutation target: ' + record.target);
});
};
var mo = new MutationObserver(callback);
var option = {
'childList': true,
'subtree': true
};
mo.observe(document.body, option);
上面代码的观察器,观察
的所有下级节点(
childList表示观察子节点,
subtree表示观察后代节点)的变动。回调函数会在控制台显示所有变动的类型和目标节点。
属性的变动
下面的例子说明如何追踪属性的变动。
var callback = function (records) {
records.map(function (record) {
console.log('Previous attribute value: ' + record.oldValue);
});
};
var mo = new MutationObserver(callback);
var element = document.getElementById('#my_element');
var options = {
'attributes': true,
'attributeOldValue': true
}
mo.observe(element, options);
上面代码先设定追踪属性变动(
'attributes': true),然后设定记录变动前的值。实际发生变动时,会将变动前的值显示在控制台。
取代 DOMContentLoaded 事件
网页加载的时候,DOM 节点的生成会产生变动记录,因此只要观察 DOM 的变动,就能在第一时间触发相关事件,也就没有必要使用
DOMContentLoaded事件。
var observer = new MutationObserver(callback);
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
上面代码中,监听
document.documentElement(即网页的
HTML 节点)的子节点的变动,
subtree属性指定监听还包括后代节点。因此,任意一个网页元素一旦生成,就能立刻被监听到。
下面的代码,使用
MutationObserver对象封装一个监听 DOM 生成的函数。
(function(win){
'use strict';
var listeners = [];
var doc = win.document;
var MutationObserver = win.MutationObserver || win.WebKitMutationObserver;
var observer;
function ready(selector, fn){
// 储存选择器和回调函数
listeners.push({
selector: selector,
fn: fn
});
if(!observer){
// 监听document变化
observer = new MutationObserver(check);
observer.observe(doc.documentElement, {
childList: true,
subtree: true
});
}
// 检查该节点是否已经在DOM中
check();
}
function check(){
// 检查是否匹配已储存的节点
for(var i = 0; i < listeners.length; i++){
var listener = listeners[i];
// 检查指定节点是否有匹配
var elements = doc.querySelectorAll(listener.selector);
for(var j = 0; j < elements.length; j++){
var element = elements[j];
// 确保回调函数只会对该元素调用一次
if(!element.ready){
element.ready = true;
// 对该节点调用回调函数
listener.fn.call(element, element);
}
}
}
}
// 对外暴露ready
win.ready = ready;
})(this);
// 使用方法
ready('.foo', function(element){
// ...
});
参考链接
Paul Kinlan, Detect DOM changes with Mutation Observers
Tiffany Brown, Getting to know mutation observers
Michal Budzynski, JavaScript: The less known parts. DOM Mutations
Jeff Griffiths, DOM MutationObserver – reacting to DOM changes without killing browser performance
Addy Osmani, Detect, Undo And Redo DOM Changes With Mutation Observers
Ryan Morr, Using Mutation Observers to Watch for Element Availability
EventTarget 接口
事件的本质是程序各个组成部分之间的一种通信方式,也是异步编程的一种实现。DOM 支持大量的事件,本章开始介绍 DOM 的事件编程。
介绍具体的事件之前,先来看看如何让 DOM 节点监听事件。
概述
DOM 节点的事件操作(监听和触发),都定义在
EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,
XMLHttpRequest、
AudioNode、
AudioContext)也部署了这个接口。
该接口主要提供三个实例方法。
addEventListener():绑定事件的监听函数
removeEventListener():移除事件的监听函数
dispatchEvent():触发事件
EventTarget.addEventListener()
EventTarget.addEventListener()用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。
target.addEventListener(type, listener[, useCapture]);
该方法接受三个参数。
type:事件名称,大小写敏感。
listener:监听函数。事件发生时,会调用该监听函数。
useCapture:布尔值,如果设为
true,表示监听函数将在捕获阶段(capture)触发(参见后文《事件的传播》部分)。该参数可选,默认值为
false(监听函数只在冒泡阶段被触发)。
下面是一个例子。
function hello() {
console.log('Hello world');
}
var button = document.getElementById('btn');
button.addEventListener('click', hello, false);
上面代码中,
button节点的
addEventListener()方法绑定
click事件的监听函数
hello(),该函数只在冒泡阶段触发。
关于参数,有两个地方需要注意。
首先,第二个参数除了监听函数,还可以是一个具有
handleEvent方法的对象,效果与监听函数一样。
buttonElement.addEventListener('click', {
handleEvent: function (event) {
console.log('click');
}
});
上面代码中,
addEventListener()方法的第二个参数,就是一个具有
handleEvent()方法的对象。
其次,第三个参数除了布尔值
useCapture,还可以是一个监听器配置对象,定制事件监听行为。该对象有以下属性。
capture:布尔值,如果设为
true,表示监听函数在捕获阶段触发,默认为
false,在冒泡阶段触发。
once:布尔值,如果设为
true,表示监听函数执行一次就会自动移除,后面将不再监听该事件。该属性默认值为
false。
passive:布尔值,设为
true时,表示禁止监听函数调用
preventDefault()方法。如果调用了,浏览器将忽略这个要求,并在控制台输出一条警告。该属性默认值为
false。
signal:该属性的值为一个 AbortSignal 对象,为监听器设置了一个信号通道,用来在需要时发出信号,移除监听函数。
下面是
once属性的例子,让监听函数只执行一次。
element.addEventListener('click', function (event) {
// 只执行一次的代码
}, {once: true});
addEventListener()方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用
removeEventListener()方法手动去除)。
function hello() {
console.log('Hello world');
}
document.addEventListener('click', hello, false);
document.addEventListener('click', hello, false);
执行上面代码,点击文档只会输出一行
Hello world。
如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数。
function print(x) {
console.log(x);
}
var el = document.getElementById('div1');
el.addEventListener('click', function () { print('Hello'); }, false);
上面代码通过匿名函数,向监听函数
print传递了一个参数。
监听函数内部的
this,指向当前事件所在的那个对象。
// HTML 代码如下
//
Hello
var para = document.getElementById('para');
para.addEventListener('click', function (e) {
console.log(this.nodeName); // "P"
}, false);
上面代码中,监听函数内部的
this指向事件所在的对象
para。
EventTarget.removeEventListener()
EventTarget.removeEventListener()方法用来移除
addEventListener()方法添加的事件监听函数。该方法没有返回值。
div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);
removeEventListener()方法的参数,与
addEventListener()方法完全一致。它的第一个参数“事件类型”,大小写敏感。
注意,
removeEventListener()方法移除的监听函数,必须是
addEventListener()方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
上面代码中,
removeEventListener()方法无效,因为监听函数不是同一个匿名函数。
element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);
上面代码中,
removeEventListener()方法也是无效的,因为第三个参数不一样。
EventTarget.dispatchEvent()
EventTarget.dispatchEvent()方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了
Event.preventDefault(),则返回值为
false,否则为
true。
target.dispatchEvent(event)
dispatchEvent()方法的参数是一个
Event对象的实例(详见《Event 对象》章节)。
para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);
上面代码在当前节点触发了
click事件。
如果
dispatchEvent()方法的参数为空,或者不是一个有效的事件对象,将报错。
下面代码根据
dispatchEvent()方法的返回值,判断事件是否被取消了。
var canceled = !cb.dispatchEvent(event);
if (canceled) {
console.log('事件取消');
} else {
console.log('事件未取消');
}
事件模型
监听函数
浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数。
HTML 的 on- 属性
HTML 语言允许在元素的属性中,直接定义某些事件的监听代码。
上面代码为
body节点的
load事件、
div节点的
click事件,指定了监听代码。一旦事件发生,就会执行这段代码。
元素的事件监听属性,都是
on加上事件名,比如
onload就是
on + load,表示
load事件的监听代码。
注意,这些属性的值是将会执行的代码,而不是一个函数。
一旦指定的事件发生,
on-属性的值是原样传入 JavaScript 引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。
使用这个方法指定的监听代码,只会在冒泡阶段触发。
上面代码中,