JS 回顾 8
约 10763 字大约 36 分钟
2026-02-07
属性的操作
HTML 元素包括标签名和若干个键值对,这个键值对就称为“属性”(attribute)。
<a id="test" href="http://www.example.com">
链接
</a>上面代码中,a 元素包括两个属性:id 属性和 href 属性。
属性本身是一个对象(Attr 对象),但是实际上,这个对象极少使用。一般都是通过元素节点对象(HTMlElement 对象)来操作属性。
Element.attributes 属性
元素对象有一个 attributes 属性,返回一个类似数组的动态对象,成员是该元素标签的所有属性节点对象,属性的实时变化都会反映在这个节点对象上。其他类型的节点对象,虽然也有 attributes 属性,但返回的都是 null,因此可以把这个属性视为元素对象独有的。
单个属性可以通过序号引用,也可以通过属性名引用。
// HTML 代码如下
// <body bgcolor="yellow" onload="">
document.body.attributes[0]
document.body.attributes.bgcolor
document.body.attributes['ONLOAD']注意,上面代码的三种方法,返回的都是属性节点对象,而不是属性值。
属性节点对象有 name 和 value 属性,对应该属性的属性名和属性值,等同于 nodeName 属性和 nodeValue 属性。
// HTML 代码为
// <div id="mydiv">
var n = document.getElementById('mydiv');
n.attributes[0].name // "id"
n.attributes[0].nodeName // "id"
n.attributes[0].value // "mydiv"
n.attributes[0].nodeValue // "mydiv"下面代码可以遍历一个元素节点的所有属性。
var para = document.getElementsByTagName('p')[0];
var result = document.getElementById('result');
if (para.hasAttributes()) {
var attrs = para.attributes;
var output = '';
for(var i = attrs.length - 1; i >= 0; i--) {
output += attrs[i].name + '->' + attrs[i].value;
}
result.textContent = output;
} else {
result.textContent = 'No attributes to show';
}元素的标准属性
HTML 元素的标准属性(即在标准中定义的属性),会自动成为元素节点对象的属性。
var a = document.getElementById('test');
a.id // "test"
a.href // "http://www.example.com/"上面代码中,a 元素标签的属性 id 和 href,自动成为节点对象的属性。
这些属性都是可写的。
var img = document.getElementById('myImage');
img.src = 'http://www.example.com/image.jpg';上面的写法,会立刻替换掉 img 对象的 src 属性,即会显示另外一张图片。
这种修改属性的方法,常常用于添加表单的属性。
var f = document.forms[0];
f.action = 'submit.php';
f.method = 'POST';上面代码为表单添加提交网址和提交方法。
注意,这种用法虽然可以读写属性,但是无法删除属性,delete 运算符在这里不会生效。
HTML 元素的属性名是大小写不敏感的,但是 JavaScript 对象的属性名是大小写敏感的。转换规则是,转为 JavaScript 属性名时,一律采用小写。如果属性名包括多个单词,则采用骆驼拼写法,即从第二个单词开始,每个单词的首字母采用大写,比如 onClick。
有些 HTML 属性名是 JavaScript 的保留字,转为 JavaScript 属性时,必须改名。主要是以下两个。
for属性改为htmlForclass属性改为className
另外,HTML 属性值一般都是字符串,但是 JavaScript 属性会自动转换类型。比如,将字符串 true 转为布尔值,将 onClick 的值转为一个函数,将 style 属性的值转为一个 CSSStyleDeclaration 对象。因此,可以对这些属性赋予各种类型的值。
属性操作的标准方法
元素节点提供六个方法,用来操作属性。
getAttribute()getAttributeNames()setAttribute()hasAttribute()hasAttributes()removeAttribute()
注意点:
这六个方法对所有属性(包括用户自定义的属性)都适用。
getAttribute() 只返回字符串,不会返回其他类型的值。
这些方法只接受属性的标准名称,不用改写保留字,比如 for 和 class 都可以直接使用。另外,这些方法对于属性名是大小写不敏感的。
var image = document.images[0];
image.setAttribute('class', 'myImage');上面代码中,setAttribute 方法直接使用 class 作为属性名,不用写成 className。
Element.getAttribute()
Element.getAttribute 方法返回当前元素节点的指定属性。如果指定属性不存在,则返回 null。
// HTML 代码为
// <div id="div1" align="left">
var div = document.getElementById('div1');
div.getAttribute('align') // "left"Element.getAttributeNames()
Element.getAttributeNames() 返回一个数组,成员是当前元素的所有属性的名字。如果当前元素没有任何属性,则返回一个空数组。使用 Element.attributes 属性,也可以拿到同样的结果,唯一的区别是它返回的是类似数组的对象。
var mydiv = document.getElementById('mydiv');
mydiv.getAttributeNames().forEach(function (key) {
var value = mydiv.getAttribute(key);
console.log(key, value);
})上面代码用于遍历某个节点的所有属性。
Element.setAttribute()
Element.setAttribute 方法用于为当前元素节点新增属性。如果同名属性已存在,则相当于编辑已存在的属性。该方法没有返回值。
// HTML 代码为
// <button> Hello World </button>
var b = document.querySelector('button');
b.setAttribute('name', 'myButton');
b.setAttribute('disabled', true);上面代码中,button 元素的 name 属性被设成 myButton,disabled 属性被设成 true。
这里有两个地方需要注意,首先,属性值总是字符串,其他类型的值会自动转成字符串,比如布尔值 true 就会变成字符串 true;
其次,上例的 disable 属性是一个布尔属性,对于 <button> 元素来说,这个属性不需要属性值,只要设置了就总是会生效,因此 setAttribute 方法里面可以将 disabled 属性设成任意值。
Element.hasAttribute()
Element.hasAttribute 方法返回一个布尔值,表示当前元素节点是否包含指定属性。
var d = document.getElementById('div1');
if (d.hasAttribute('align')) {
d.setAttribute('align', 'center');
}上面代码检查 div 节点是否含有 align 属性。如果有,则设置为居中对齐。
Element.hasAttributes()
Element.hasAttributes 方法返回一个布尔值,表示当前元素是否有属性,如果没有任何属性,就返回 false,否则返回 true。
var foo = document.getElementById('foo');
foo.hasAttributes() // trueElement.removeAttribute()
Element.removeAttribute 方法移除指定属性。该方法没有返回值。
// HTML 代码为
// <div id="div1" align="left" width="200px">
document.getElementById('div1').removeAttribute('align');
// 现在的 HTML 代码为
// <div id="div1" width="200px">dataset 属性
有时,需要在 HTML 元素上附加数据,供 JavaScript 脚本使用。一种解决方法是自定义属性。
<div id="mydiv" foo="bar">上面代码为 div 元素自定义了 foo 属性,然后可以用 getAttribute() 和 setAttribute() 读写这个属性。
var n = document.getElementById('mydiv');
n.getAttribute('foo') // bar
n.setAttribute('foo', 'baz')这种方法虽然可以达到目的,但是会使得 HTML 元素的属性不符合标准,导致网页代码通不过校验。
更好的解决方法是,使用标准提供的 data-* 属性。
<div id="mydiv" data-foo="bar">然后,使用元素节点对象的 dataset 属性,它指向一个对象,可以用来操作 HTML 元素标签的 data-* 属性。
var n = document.getElementById('mydiv');
n.dataset.foo // bar
n.dataset.foo = 'baz'上面代码中,通过 dataset.foo 读写 data-foo 属性。
删除一个 data-* 属性,可以直接使用 delete 命令。
delete document.getElementById('myDiv').dataset.foo;除了 dataset 属性,也可以用 getAttribute('data-foo')、removeAttribute('data-foo')、setAttribute('data-foo')、hasAttribute('data-foo') 等方法操作 data-* 属性。
注意,data- 后面的属性名有限制,只能包含字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)。而且,属性名不应该使用 A 到 Z 的大写字母,比如不能有 data-helloWorld 这样的属性名,而要写成 data-hello-world。
转成 dataset 的键名时,连词线后面如果跟着一个小写字母,那么连词线会被移除,该小写字母转为大写字母,其他字符不变。反过来,dataset 的键名转成属性名时,所有大写字母都会被转成连词线+该字母的小写形式,其他字符不变。比如,dataset.helloWorld 会转成 data-hello-world。
Text 节点
文本节点(Text)代表元素节点(Element)和属性节点(Attribute)的文本内容。如果一个节点只包含一段文本,那么它就有一个文本子节点,代表该节点的文本内容。
通常我们使用父节点的 firstChild、nextSibling 等属性获取文本节点,或者使用 Document 节点的 createTextNode 方法创造一个文本节点。
// 获取文本节点
var textNode = document.querySelector('p').firstChild;
// 创造文本节点
var textNode = document.createTextNode('Hi');
document.querySelector('div').appendChild(textNode);浏览器原生提供一个 Text 构造函数。它返回一个文本节点实例。它的参数就是该文本节点的文本内容。
// 空字符串
var text1 = new Text();
// 非空字符串
var text2 = new Text('This is a text node');注意,由于空格也是一个字符,所以哪怕只有一个空格,也会形成文本节点。比如,<p> </p> 包含一个空格,它的子节点就是一个文本节点。
文本节点除了继承 Node 接口,还继承了 CharacterData 接口。
属性
以下的属性和方法大部分来自 CharacterData 接口。
data
data 属性等同于 nodeValue 属性,用来设置或读取文本节点的内容。
// 读取文本内容
document.querySelector('p').firstChild.data
// 等同于
document.querySelector('p').firstChild.nodeValue
// 设置文本内容
document.querySelector('p').firstChild.data = 'Hello World';wholeText
wholeText 属性将当前文本节点与毗邻的文本节点,作为一个整体返回。大多数情况下,wholeText 属性的返回值,与 data 属性和 textContent 属性相同。但是,某些特殊情况会有差异。
举例来说,HTML 代码如下。
<p id="para">A <em>B</em> C</p>这时,文本节点的 wholeText 属性和 data 属性,返回值相同。
var el = document.getElementById('para');
el.firstChild.wholeText // "A "
el.firstChild.data // "A "但是,一旦移除 <em> 节点,wholeText 属性与 data 属性就会有差异,因为这时其实 <p> 节点下面包含了两个毗邻的文本节点。
el.removeChild(para.childNodes[1]);
el.firstChild.wholeText // "A C"
el.firstChild.data // "A "length
length 属性返回当前文本节点的文本长度。
(new Text('Hello')).length // 5nextElementSibling
nextElementSibling 属性返回紧跟在当前文本节点后面的那个同级元素节点。如果取不到元素节点,则返回 null。
// HTML 为
// <div> Hello <em> World </em> </div>
var tn = document.querySelector('div').firstChild;
tn.nextElementSibling
// <em> World </em>previousElementSibling
previousElementSibling 属性返回当前文本节点前面最近的同级元素节点。如果取不到元素节点,则返回 null:。
方法
以下 5 个方法都是编辑 Text 节点文本内容的方法。
appendData()
appendData():在 Text 节点尾部追加字符串。
deleteData()
deleteData():删除 Text 节点内部的子字符串,第一个参数为子字符串开始位置,第二个参数为子字符串长度。
insertData()
insertData():在 Text 节点插入字符串,第一个参数为插入位置,第二个参数为插入的子字符串。
replaceData()
replaceData():用于替换文本,第一个参数为替换开始位置,第二个参数为需要被替换掉的长度,第三个参数为新加入的字符串。
subStringData()
subStringData():用于获取子字符串,第一个参数为子字符串在 Text 节点中的开始位置,第二个参数为子字符串长度。
// HTML 代码为
// <p> Hello World </p>
var pElementText = document.querySelector('p').firstChild;
pElementText.appendData('!');
// 页面显示 Hello World!
pElementText.deleteData(7, 5);
// 页面显示 Hello W
pElementText.insertData(7, 'Hello ');
// 页面显示 Hello WHello
pElementText.replaceData(7, 5, 'World');
// 页面显示 Hello WWorld
pElementText.substringData(7, 10);
// 页面显示不变,返回 "World "remove()
remove 方法用于移除当前 Text 节点。
// HTML 代码为
// <p> Hello World </p>
document.querySelector('p').firstChild.remove()
// 现在 HTML 代码为
// <p> </p>splitText()
splitText 方法将 Text 节点一分为二,变成两个毗邻的 Text 节点。它的参数就是分割位置(从零开始),分割到该位置的字符前结束。如果分割位置不存在,将报错。
分割后,该方法返回分割位置后方的字符串,而原 Text 节点变成只包含分割位置前方的字符串。
// html 代码为 <p id="p"> foobar </p>
var p = document.getElementById('p');
var textnode = p.firstChild;
var newText = textnode.splitText(3);
newText // "bar"
textnode // "foo"父元素节点的 normalize 方法可以将毗邻的两个 Text 节点合并。
接上面的例子,文本节点的 splitText 方法将一个 Text 节点分割成两个,父元素的 normalize 方法可以实现逆操作,将它们合并。
p.childNodes.length // 2
// 将毗邻的两个 Text 节点合并
p.normalize();
p.childNodes.length // 1DocumentFragment 节点
DocumentFragment 节点代表一个文档的片段,本身就是一个完整的 DOM 树形结构。它没有父节点,parentNode 返回 null,但是可以插入任意数量的子节点。它不属于当前文档,操作 DocumentFragment 节点,要比直接操作 DOM 树快得多。
它一般用于构建一个 DOM 结构,然后插入当前文档。document.createDocumentFragment 方法,以及浏览器原生的 DocumentFragment 构造函数,可以创建一个空的 DocumentFragment 节点。然后再使用其他 DOM 方法,向其添加子节点。
var docFrag = document.createDocumentFragment();
// 等同于
var docFrag = new DocumentFragment();
var li = document.createElement('li');
li.textContent = 'Hello World';
docFrag.appendChild(li);
document.querySelector('ul').appendChild(docFrag);上面代码创建了一个 DocumentFragment 节点,然后将一个 li 节点添加在它里面,最后将 DocumentFragment 节点移动到原文档。
注意,DocumentFragment 节点本身不能被插入当前文档。当它作为 appendChild()、insertBefore()、replaceChild() 等方法的参数时,是它的所有子节点插入当前文档,而不是它自身。
一旦 DocumentFragment 节点被添加进当前文档,它自身就变成了空节点(textContent 属性为空字符串),可以被再次使用。如果想要保存 DocumentFragment 节点的内容,可以使用 cloneNode 方法。
document
.querySelector('ul')
.appendChild(docFrag.cloneNode(true));上面这样添加 DocumentFragment 节点进入当前文档,不会清空 DocumentFragment 节点。
下面是一个例子,使用 DocumentFragment 反转一个指定节点的所有子节点的顺序。
function reverse(n) {
var f = document.createDocumentFragment();
while(n.lastChild) f.appendChild(n.lastChild);
n.appendChild(f);
}DocumentFragment 节点对象没有自己的属性和方法,全部继承自 Node 节点和 ParentNode 接口。也就是说,DocumentFragment 节点比 Node 节点多出以下四个属性。
children:返回一个动态的HTMLCollection集合对象,包括当前DocumentFragment对象的所有子元素节点。firstElementChild:返回当前DocumentFragment对象的第一个子元素节点,如果没有则返回null。lastElementChild:返回当前DocumentFragment对象的最后一个子元素节点,如果没有则返回null。childElementCount:返回当前DocumentFragment对象的所有子元素数量。
CSS 操作
CSS 与 JavaScript 是两个有着明确分工的领域,前者负责页面的视觉效果,后者负责与用户的行为互动。但是,它们毕竟同属网页开发的前端,因此不可避免有着交叉和互相配合。
HTML 元素的 style 属性
操作 CSS 样式最简单的方法,就是使用网页元素节点的 getAttribute() 方法、setAttribute() 方法和 removeAttribute() 方法,直接读写或删除网页元素的 style 属性。
div.setAttribute(
'style',
'background-color:red;' + 'border:1px solid black;'
);上面的代码相当于下面的 HTML 代码。
<div style="background-color:red; border:1px solid black;" />style 不仅可以使用字符串读写,它本身还是一个对象,部署了 CSSStyleDeclaration 接口,可以直接读写个别属性。
e.style.fontSize = '18px';
e.style.color = 'black';CSSStyleDeclaration 接口
CSSStyleDeclaration 接口用来操作元素的样式。三个地方部署了这个接口。
- 元素节点的
style属性(Element.style) CSSStyle实例的style属性window.getComputedStyle()的返回值
CSSStyleDeclaration 接口可以直接读写 CSS 的样式属性,不过,连词号需要变成骆驼拼写法。
var divStyle = document.querySelector('div').style;
divStyle.backgroundColor = 'red';
divStyle.border = '1px solid black';
divStyle.width = '100px';
divStyle.height = '100px';
divStyle.fontSize = '10em';
divStyle.backgroundColor // red
divStyle.border // 1px solid black
divStyle.height // 100px
divStyle.width // 100px上面代码中,style 属性的值是一个 CSSStyleDeclaration 实例。这个对象所包含的属性与 CSS 规则一一对应,但是名字需要改写,比如 background-color 写成 backgroundColor。
改写的规则是将横杠从 CSS 属性名中去除,然后将横杠后的第一个字母大写。如果 CSS 属性名是 JavaScript 保留字,则规则名之前需要加上字符串 css,比如 float 写成 cssFloat。
注意,该对象的属性值都是字符串,设置时必须包括单位,但是不含规则结尾的分号。比如,divStyle.width 不能写为 100,而要写为 100px。
另外,Element.style 返回的只是行内样式,并不是该元素的全部样式。通过样式表设置的样式,或者从父元素继承的样式,无法通过这个属性得到。元素的全部样式要通过 window.getComputedStyle() 得到。
实例属性
CSSStyleDeclaration.cssText
CSSStyleDeclaration.cssText 属性用来读写当前规则的所有样式声明文本。
var divStyle = document.querySelector('div').style;
divStyle.cssText = 'background-color: red;'
+ 'border: 1px solid black;'
+ 'height: 100px;'
+ 'width: 100px;';注意,cssText 的属性值不用改写 CSS 属性名。
删除一个元素的所有行内样式,最简便的方法就是设置 cssText 为空字符串。
divStyle.cssText = '';CSSStyleDeclaration.length
CSSStyleDeclaration.length 属性返回一个整数值,表示当前规则包含多少条样式声明。
// HTML 代码如下
// < div id = "myDiv"
// style = "height: 1px; width: 100%; background-color: #CA1;"
// ></div>
var myDiv = document.getElementById('myDiv');
var divStyle = myDiv.style;
divStyle.length // 3上面代码中,myDiv 元素的行内样式共包含 3 条样式规则。
CSSStyleDeclaration.parentRule
CSSStyleDeclaration.parentRule 属性返回当前规则所属的那个样式块(CSSRule 实例)。如果不存在所属的样式块,该属性返回 null。
该属性只读,且只在使用 CSSRule 接口时有意义。
var declaration = document.styleSheets[0].rules[0].style;
declaration.parentRule === document.styleSheets[0].rules[0]
// true实例方法
CSSStyleDeclaration.getPropertyPriority()
CSSStyleDeclaration.getPropertyPriority 方法接受 CSS 样式的属性名作为参数,返回一个字符串,表示有没有设置 important 优先级。如果有就返回 important,否则返回空字符串。
// HTML 代码为
// <div id="myDiv" style="margin: 10px!important; color: red;"/>
var style = document.getElementById('myDiv').style;
style.margin // "10px"
style.getPropertyPriority('margin') // "important"
style.getPropertyPriority('color') // ""上面代码中,margin 属性有 important 优先级,color 属性没有。
CSSStyleDeclaration.getPropertyValue()
CSSStyleDeclaration.getPropertyValue 方法接受 CSS 样式属性名作为参数,返回一个字符串,表示该属性的属性值。
// HTML 代码为
// <div id="myDiv" style="margin: 10px!important; color: red;"/>
var style = document.getElementById('myDiv').style;
style.margin // "10px"
style.getPropertyValue("margin") // "10px"CSSStyleDeclaration.item()
CSSStyleDeclaration.item 方法接受一个整数值作为参数,返回该位置的 CSS 属性名。
// HTML 代码为
// <div id="myDiv" style="color: red; background-color: white;"/>
var style = document.getElementById('myDiv').style;
style.item(0) // "color"
style.item(1) // "background-color"上面代码中,0 号位置的 CSS 属性名是 color,1 号位置的 CSS 属性名是 background-color。
如果没有提供参数,这个方法会报错。如果参数值超过实际的属性数目,这个方法返回一个空字符值。
CSSStyleDeclaration.removeProperty()
CSSStyleDeclaration.removeProperty 方法接受一个属性名作为参数,在 CSS 规则里面移除这个属性,返回这个属性原来的值。
// HTML 代码为
// <div id="myDiv" style="color: red; background-color: white;">
// 111
// </div>
var style = document.getElementById('myDiv').style;
style.removeProperty('color') // 'red'
// HTML 代码变为
// <div id="myDiv" style="background-color: white;">上面代码中,删除 color 属性以后,字体颜色从红色变成默认颜色。
CSSStyleDeclaration.setProperty()
CSSStyleDeclaration.setProperty 方法用来设置新的 CSS 属性。该方法没有返回值。
该方法可以接受三个参数。
- 第一个参数:属性名,该参数是必需的。
- 第二个参数:属性值,该参数可选。如果省略,则参数值默认为空字符串。
- 第三个参数:优先级,该参数可选。如果设置,唯一的合法值是
important,表示 CSS 规则里面的!important。
// HTML 代码为
// <div id="myDiv" style="color: red; background-color: white;">
// 111
// </div>
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')
// trueCSS 对象
浏览器原生提供 CSS 对象,为 JavaScript 操作 CSS 提供一些工具方法。
这个对象目前有两个静态方法。
CSS.escape()
CSS.escape 方法用于转义 CSS 选择器里面的特殊字符。
<div id="foo#bar">上面代码中,该元素的 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;') // falsewindow.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 代码。
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 接口代表网页的一张样式表,包括 <link> 元素加载的样式表和 <style> 元素内嵌的样式表。
document 对象的 styleSheets 属性,可以返回当前页面的所有 StyleSheet 实例(即所有样式表)。它是一个类似数组的对象。
var sheets = document.styleSheets;
var sheet = document.styleSheets[0];
sheet instanceof StyleSheet // true如果是 <style> 元素嵌入的样式表,还有另一种获取 StyleSheet 实例的方法,就是这个节点元素的 sheet 属性。
// HTML 代码为 <style id="myStyle"> </style>
var myStyleSheet = document.getElementById('myStyle').sheet;
myStyleSheet instanceof StyleSheet // true严格地说,StyleSheet 接口不仅包括网页样式表,还包括 XML 文档的样式表。
所以,它有一个子类 CSSStyleSheet 表示网页的 CSS 样式表。我们在网页里面拿到的样式表实例,实际上是 CSSStyleSheet 的实例。这个子接口继承了 StyleSheet 的所有属性和方法,并且定义了几个自己的属性,下面把这两个接口放在一起介绍。
实例属性
StyleSheet.disabled
StyleSheet.disabled 返回一个布尔值,表示该样式表是否处于禁用状态。手动设置 disabled 属性为 true,等同于在 <link> 元素里面,将这张样式表设为 alternate stylesheet,即该样式表将不会生效。
注意,disabled 属性只能在 JavaScript 脚本中设置,不能在 HTML 语句中设置。
StyleSheet.href
StyleSheet.href 返回样式表的网址。对于内嵌样式表,该属性返回 null。该属性只读。
document.styleSheets[0].hrefStyleSheet.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');StyleSheet.title
StyleSheet.title 属性返回样式表的 title 属性。
StyleSheet.type
StyleSheet.type 属性返回样式表的 type 属性,通常是 text/css。
document.styleSheets[0].type // "text/css"StyleSheet.parentStyleSheet
CSS 的 @import 命令允许在样式表中加载其他样式表。StyleSheet.parentStyleSheet 属性返回包含了当前样式表的那张样式表。如果当前样式表是顶层样式表,则该属性返回 null。
if (stylesheet.parentStyleSheet) {
sheet = stylesheet.parentStyleSheet;
} else {
sheet = stylesheet;
}StyleSheet.ownerNode
StyleSheet.ownerNode 属性返回 StyleSheet 对象所在的 DOM 节点,通常是 <link> 或 <style>。对于那些由其他样式表引用的样式表,该属性为 null。
// HTML 代码为
// <link rel="StyleSheet" href="example.css" type="text/css" />
document.styleSheets[0].ownerNode // [object HTMLLinkElement]CSSStyleSheet.cssRules
CSSStyleSheet.cssRules 属性指向一个类似数组的对象(CSSRuleList 实例),里面每一个成员就是当前样式表的一条 CSS 规则。使用该规则的 cssText 属性,可以得到 CSS 规则对应的字符串。
var sheet = document.querySelector('#styleElement').sheet;
sheet.cssRules[0].cssText
// "body { background-color: red; margin: 20px; }"
sheet.cssRules[1].cssText
// "p { line-height: 1.4em; color: blue; }"每条 CSS 规则还有一个 style 属性,指向一个对象,用来读写具体的 CSS 命令。
cssStyleSheet.cssRules[0].style.color = 'red';
cssStyleSheet.cssRules[1].style.color = 'purple';CSSStyleSheet.ownerRule
有些样式表是通过 @import 规则输入的,它的 ownerRule 属性会返回一个 CSSRule 实例,代表那行 @import 规则。如果当前样式表不是通过 @import 引入的,ownerRule 属性返回 null。
实例方法
CSSStyleSheet.insertRule()
CSSStyleSheet.insertRule 方法用于在当前样式表的插入一个新的 CSS 规则。
var sheet = document.querySelector('#styleElement').sheet;
sheet.insertRule('#block { color: white }', 0);
sheet.insertRule('p { color: red }', 1);该方法可以接受两个参数,第一个参数是表示 CSS 规则的字符串,这里只能有一条规则,否则会报错。第二个参数是该规则在样式表的插入位置(从 0 开始),该参数可选,默认为 0(即默认插在样式表的头部)。注意,如果插入位置大于现有规则的数目,会报错。
该方法的返回值是新插入规则的位置序号。
注意,浏览器对脚本在样式表里面插入规则有很多 限制。所以,这个方法最好放在 try...catch 里使用。
CSSStyleSheet.deleteRule()
CSSStyleSheet.deleteRule 方法用来在样式表里面移除一条规则,它的参数是该条规则在 cssRules 对象中的位置。该方法没有返回值。
document.styleSheets[0].deleteRule(1);实例:添加样式表
网页添加样式表有两种方式。一种是添加一张内置样式表,即在文档中添加一个 <style> 节点。
// 写法一
var style = document.createElement('style');
style.setAttribute('media', 'screen');
style.innerHTML = 'body{color:red}';
document.head.appendChild(style);
// 写法二
var style = (function () {
var style = document.createElement('style');
document.head.appendChild(style);
return style;
})();
style.sheet.insertRule('.foo{color:red;}', 0);另一种是添加外部样式表,即在文档中添加一个 <link> 节点,然后将 href 属性指向外部样式表的 URL。
var linkElm = document.createElement('link');
linkElm.setAttribute('rel', 'stylesheet');
linkElm.setAttribute('type', 'text/css');
linkElm.setAttribute('href', 'reset-min.css');
document.head.appendChild(linkElm);CSSRuleList 接口
CSSRuleList 接口是一个类似数组的对象,表示一组 CSS 规则,成员都是 CSSRule 实例。
获取 CSSRuleList 实例,一般是通过 StyleSheet.cssRules 属性。
// HTML 代码如下
// <style id="myStyle">
// h1 { color: red; }
// p { color: blue; }
// </style>
var myStyleSheet = document.getElementById('myStyle').sheet;
var crl = myStyleSheet.cssRules;
crl instanceof CSSRuleList // trueCSSRuleList 实例里面,每一条规则(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 代码如下
// <style id="myStyle">
// .myClass {
// color: red;
// background-color: yellow;
// }
// </style>
var myStyleSheet = document.getElementById('myStyle').sheet;
var ruleList = myStyleSheet.cssRules;
var rule = ruleList[0];
rule instanceof CSSRule // true实例属性
CSSRule.cssText
CSSRule.cssText 属性返回当前规则的文本,还是使用上面的例子。
rule.cssText
// ".myClass { color: red; background-color: yellow; }"如果规则是加载(@import)其他样式表,cssText 属性返回 @import 'url'。
CSSRule.parentStyleSheet
CSSRule.parentStyleSheet 属性返回当前规则所在的样式表对象(StyleSheet 实例),还是使用上面的例子。
rule.parentStyleSheet === myStyleSheet // trueCSSRule.parentRule
CSSRule.parentRule 属性返回包含当前规则的父规则,如果不存在父规则(即当前规则是顶层规则),则返回 null。
父规则最常见的情况是,当前规则包含在 @media 规则代码块之中。
// HTML 代码如下
// <style id="myStyle">
// @supports (display: flex) {
// @media screen and (min-width: 900px) {
// article {
// display: flex;
// }
// }
// }
// </style>
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 // trueCSSRule.type
CSSRule.type 属性返回一个整数值,表示当前规则的类型。
最常见的类型有以下几种。
- 1:普通样式规则(CSSStyleRule 实例)
- 3:
@import规则 - 4:
@media规则(CSSMediaRule 实例) - 5:
@font-face规则
CSSStyleRule 接口
如果一条 CSS 规则是普通的样式规则(不含特殊的 CSS 命令),那么除了 CSSRule 接口,它还部署了 CSSStyleRule 接口。
CSSStyleRule 接口有以下两个属性。
CSSStyleRule.selectorText
CSSStyleRule.selectorText 属性返回当前规则的选择器。
var stylesheet = document.styleSheets[0];
stylesheet.cssRules[0].selectorText // ".myClass"注意,这个属性是可写的。
CSSStyleRule.style
CSSStyleRule.style 属性返回一个对象(CSSStyleDeclaration 实例),代表当前规则的样式声明,也就是选择器后面的大括号里面的部分。
// HTML 代码为
// <style id="myStyle">
// p { color: red; }
// </style>
var styleSheet = document.getElementById('myStyle').sheet;
styleSheet.cssRules[0].style instanceof CSSStyleDeclaration
// trueCSSStyleDeclaration 实例的 cssText 属性,可以返回所有样式声明,格式为字符串。
styleSheet.cssRules[0].style.cssText
// "color: red;"
styleSheet.cssRules[0].selectorText
// "p"CSSMediaRule 接口
如果一条 CSS 规则是 @media 代码块,那么它除了 CSSRule 接口,还部署了 CSSMediaRule 接口。
该接口主要提供 media 属性和 conditionText 属性。前者返回代表 @media 规则的一个对象(MediaList 实例),后者返回 @media 规则的生效条件。
// HTML 代码如下
// <style id="myStyle">
// @media screen and (min-width: 900px) {
// article { display: flex; }
// }
// </style>
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 // trueMediaQueryList 实例属性
MediaQueryList 实例有三个属性。
MediaQueryList.media
MediaQueryList.media 属性返回一个字符串,表示对应的 MediaQuery 条件语句。
var mql = window.matchMedia('(min-width: 400px)');
mql.media // "(min-width: 400px)"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);
}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
Mutation Observer API 用来监视 DOM 变动。DOM 的任何变动,比如节点的增减、属性的变动、文本内容的变动,这个 API 都可以得到通知。
概念上,它很接近事件,可以理解为 DOM 发生变动就会触发 Mutation Observer 事件。但是,它与事件有一个本质不同:事件是同步触发,也就是说,DOM 的变动立刻会触发相应的事件;Mutation Observer 则是异步触发,DOM 的变动并不会马上触发,而是要等到当前所有 DOM 操作都结束才触发。
这样设计是为了应付 DOM 变动频繁的特点。举例来说,如果文档中连续插入 1000 个 <p> 元素,就会连续触发 1000 个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;而 Mutation Observer 完全不同,只在 1000 个段落都插入结束后才会触发,而且只触发一次。
Mutation Observer 有以下特点。
- 它等待所有脚本任务完成后,才会运行(即异步触发方式)。
- 它把 DOM 变动记录封装成一个数组进行处理,而不是一条条个别处理 DOM 变动。
- 它既可以观察 DOM 的所有类型变动,也可以指定只观察某一类变动。
构造函数
使用时,首先使用MutationObserver构造函数,新建一个观察器实例,同时指定这个实例的回调函数。
var observer = new MutationObserver(callback);上面代码中的回调函数,会在每次 DOM 变动后调用。该回调函数接受两个参数,第一个是变动数组,第二个是观察器实例,下面是一个例子。
var observer = new MutationObserver(function (mutations, observer) {
mutations.forEach(function(mutation) {
console.log(mutation);
});
});实例方法
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'])。
// 开始监听文档根节点(即<html>标签)的变动
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);上面代码的观察器,观察<body>的所有下级节点(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>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){
// ...
});