在DOM特性和属性之间不仅会有一些非常细微的行为差别,在其他一些方面也会有很多bug以及跨浏览器问题。
但特性和属性都是很重要的概念:特性是DOM构建的一个组成部分,而属性是元素保持运行时信息的主要手段,并且通过属性可以访问这些运行时信息。看个例子。
var img = document.getElementsByTagName('img')[0];
var newSrc = 'img/thumb.jpg';
image.src = newSrc;
在上述代码中,我们创建一个图像标签,获取他的引用,并将他的src属性修改为一个新值,这看起来非常简单,但是我们运行测试验证一下。
img.src === newSrc;
img.getAttribute('src') === 'img/goods.webp';
测试src属性的值就是我们新赋的值,毕竟如果我们赋值x=213,当然希望x的值就是213,我们并没有改变其特性(attribute),所以它应该保持不变,对吧?
但当我们两个测试都失败了,我们看到src属性的值不是我们所赋予的值,而是类似如下的值
file:///E:/code/bookShopin/img/thumb.jpg"
给一个属性赋值,难道不应该去往那个属性就是所赋予的值吗?更奇怪的是,即使我们并没有改变元素上的特性(attribute),失败的测试表明,src的特性值已经变成如下内容了:
"img/thumb.jpg"
到底发生了什么事情,我们将检查与元素属性和特性相关的所有浏览器难题,我们会研究为什么结果不是我们所预期的。这同样适用于css和元素样式,构造一个动态web应用程序时,我们会在设置或获取元素样式方面遇到很多的困难,我们讨论一下元素的样式处理知识。
DOM特性和DOM属性
在访问元素的特性值时,有两种选择:使用传统的DOM方法叫getAttribute和setAttribute,或使用DOM对象上与之对应的属性。举例来说,一个元素保存在变量e中,要获取其id特性的话,我们可以使用如下方式,
e.getAttribute('id');
e.id
性能注意事项
总的来说,属性的访问速度比相应的DOM特性方法的访问速度要快,特别是在IE中,经过在多个浏览器上测试,属性的滑入和设置操作几乎总比getAttribute()和setAttribute()快。所以要提高性能,我们可能想要实现一个方法,在属性值存在的时候就访问属性值,属性值不存在的时候利用DOM方法作为后备,考虑如下代码。
(function () {
var translations = {
'for': 'htmlFor',
'class': 'className',
readonly: 'readOnly',
maxlength: 'maxLength',
cellspacing: 'cellSpacing',
rowspan: 'rowSpan',
colspan: 'colSpan',
tabindex: 'tabIndex',
cellpadding: 'cellPadding',
usemap: 'useMap',
frameborder: 'frameBorder',
contenteditable: 'contentEditable'
};
window.attr = function (element, name, value) {
var property = translations[name] || name;
var propertyExists = typeof element[property] !== 'undefined';
if(typeof value !== 'undefined') {
if(propertyExists){
element[property] = value;
}else {
element.setAttribute(name, value);
}
}
return propertyExists ?
element[property] :
element.getAttribute(name);
}
})();
跨浏览器的attribute问题
五大浏览器都会将表单input元素的id和name特性作为元素的属性值进行引用,产生的这些属性,会主动覆盖form元素上已经存在的同名属性。
<input type="text" for="sufubo" id="test">
<form id="testForm" action="/" >
<input type="text" id="id"/>
<input type="text" name="action"/>
</form>
<script type="text/javascript">
var form = document.getElementById('testForm');
form.id === <input type="text" id="id"/>
form.action === <input type="text" name="action"/>
</script>
测试显示了,这个不幸的特性会导致标签数据的损失。
我们可以用获取描述元素特性本身的原始DOM节点,该节点任然没有被浏览器所修改。可以使用getAttributeNode(‘action’).nodeValue来进行获取action特性的值,该问题不能被认为是一个bug,因为这是浏览器预期的行为,当元素的引用很容易使用像document.getElementById()或其他类似的方法获取到时,该问题就很具有破坏性。
URL规范化
在所有的现代浏览器中,有一个违反最少意外原则的特性,在访问一个引用了URL的属性时,该URL值会自动将原始值转换成完成规范的URL。
<a href="package.json" id="testSubject">self</a>
<script type="text/javascript">
var link = document.getElementById('testSubject');
var linkHref = link.getAttributeNode('href').nodeValue;
link.href === 'package.json'
</script>
通过DOM的原始节点找出该标签的原始值。我们要对该值进行验证,测试失败,因为该值被转为为规范的URL值了。
type特性
另外还有一个IE特性,IE8以及之前的版本对元素的type特性的影响,没有任何合理的解决方案,一旦元素被插入到文档,他的type特性就不能再改变了。如果修改,IE就会抛出一个异常。
样式属性命名
我们编写一个简单的api自动将样式转化为驼峰格式
//访问样式的简单方式
function style(element, name, value) {
name = name.replace(/-([a-z])/ig,
function (all, letter) {
return letter.toUpperCase();
});
if(typeof value !== 'undefined') {
element.style[name] = value;
}
return element.style[name];
}
对于隐藏的元素,如果希望获取它在非隐藏状态时的尺寸,我们可以使用一个技巧,暂时取消元素的隐藏,然后获取值,然后再将其隐藏,我们希望那这种做法不会有明显的视觉效果,而是只在幕后做,如何才能将一个隐藏的元素,在不可见的情况下变为不隐藏的呢?
方法如下:
1.将display属性设置为block
2.将visibility设置为hidden
3.将position设置为absolute
4.获取元素尺寸
5.恢复先前更改的属性
将display属性修改为block,可以让我们获取offsetHeight和offsetWidth的真实值,但他将元素变成显示状态而因此可见,要让该元素不可见,需要将visibility设置为hidden,但是这种做法将导致在元素的位置上显示一片空白,所以我们需要将position属性设置为absolute,以便将元素移出正常的显示流。代码实现如下。
(function () {
var PROPERTES = {
position: 'absolute',
visibility: 'hidden',
display: 'block'
};
window.getDimensions = function (element) {
//把原先的保存一下
var previous = {};
for(var key in PROPERTIES) {
previous[key] = element.style[key];
element.style[key] = PROPERTIES[key];
}
var result = {
width: element.offsetWidth,
height: element.offsetHeight
}
for (key in PROPERTIES) {
element.style[key] = previous[key];
}
return result;
}
})();
获取计算样式
function fetchComputedStyle(element, property) {
if(window.getComputedStyle) {
var computedStyles = window.getComputedStyle(element);
if(computedStyles) {
property = property.replace(/([A-Z])/g, '-$1').toLowerCase();
return computedStyles.getpropertyValue(property);
}
}else if (element.currentStyle) {
property = property.replace(
/-([a-z])/ig,
function (all, letter) {
return letter.toUpperCase();
});
return element.currentStyle[property];
)
}
}
这个函数不论是在元素上显示声明的,还是继承自样式表的,都可以获取得到。还要注意,两种方式都指定了color属性,但是返回的是在元素上显式指定的值,元素的style特性指定的样式优先级永远高于继承的样式,即便继承的样式标记为!important也没用。
在处理样式属性的时候,还有一个问题我们需要注意:混合属性,css允许我们使用快捷方式表示混合属性,比如border属性,不必强迫单独对四个边框都指定颜色,宽度,边框样式,只需要使用如下规则即可:
border: 1px solid crimson;
注意,在获取属性的时候,我们需要检索的是底层的单个属性,我们不能检索border,而是应该检索像border-top-color和border-top-width这样的属性,就想在示例中所做的那样。
总结
在遇到跨浏览器兼容性问题时,获取和设置DOM特性、属性以及样式,可能不是浏览器javaScript开发中最严重的问题,但确实有它的问题,值得庆幸的是,我们学到了可以处理这些问题的方式,使得代码可以兼容跨浏览器,而无需使用浏览器探测。