JS魔法堂:属性、特性,傻傻分不清楚
一、前言
或许你和我一样都曾经被下面的代码所困扰
var el = document.getElementById('dummy');
el.hello = "test";
console.log(el.getAttribute('hello')); // IE67下输出test,其他浏览器输出null
“搞毛啊?”,苦逼的Jser对着浏览器大呼一声。然后就用下面蹩脚的方式草草处理掉了。
function getAttr(el, prop){
return el[prop] || el.getAttribute(prop);
}
// 相应的赋值函数
function setAttr(el, prop, val){
el[prop] = val;
}
// 相应的清理函数
function removeAttr(el, prop){
delete el[prop];
}
虽然算是搞定了,但那到底什么是属性(Property)?什么是特性(Attribute)?还是傻傻分不清楚。有幸拜读了司徒正美的书,终于明白两者的区别,下面的内容为书中内容和项目中踩坑得来,实属不易啊!
二、从语义理解Property和Attribute
Property和Attribute均为外来词,首先我们看看它们的翻译究竟是什么先吧!
Property:属性、所有权,强调主题对象的特征
Attribute:属性、特性,强调主题对象的有别其他对象的特征
从上述语义推断,Attribute应该是Property的子集。
但不幸的是,浏览器并不这样理解,即使符合W3C标准规范也不是这样。
三、W3C规定Property和Attribute含义
看看图更健康

可以看到元素的“属性”被分为三块
1. standard attribute:标准属性(或固有属性),如id、name等DTD/Scheme中定义的标签属性。
特点:通过点方式或getAttribute均可访问、设置。
2. custom property:自定义属性,通过点方式访问、设置的非DTD/Scheme中定义的标签属性。
特点:仅仅能通过点方式操作属性。
3. custom attribute:自定义特性(显式特性),直接写在标签中或通过getAttribute等APIs访问、设置的非DTD/Scheme中定义的标签属性
特点:①. 可通过在html标签中显式声明,如<div customAttr="attrValue"></div>
②. 通过getAttribute等APIs操作属性。
而从IE8开始各大浏览器在这方面就遵守W3C标准了,所以就出现前言下代码片段的兼容性问题了。
四、custom attribute的类型——[object Attr]
我想大家了解一下[object Attr]类型对理解后续内容会有帮助,于是就在这里打个岔了。
custom attribute类型的属性对象类型就是[object Attr]。
浏览器支持:IE8+(IE567以[object Object]类型的形式提供与[object Attr]类型相同的APIs)、FF、Chrome
特点:
①. 虽然Attr被视为节点,但却不作为DOM树的一部分,因此没有父节点,也不属于所在html节点的子节点;
②. Attr节点的值为字符串(IE567除外),因此通过setAttribute等赋予非字符串类型的值时,会进行隐式类型转换。
属性值:
| 属性名 | 值或功能说明 |
| nodeType | 2 |
| nodeName | 属性名 |
| nodeValue | {Text} 属性值 |
| parentNode | null |
| childNodes | IE8返回null;IE9+和Chrome就返回以属性值(属性值类型为[object Text])为元素的NodeList对象;FF30.0就返回空的NodeList。 |
| name | 和nodeName一致 |
| value | 和nodeValue一致 |
| textContent | 设置或返回属性的文本内容 |
| specified | 用于判断属性值是否为自定义值,true表示是在文档中自定义设置的;false表示是DTD/Scheme设置的默认值。 |
创建:document.createAttribute({String} 属性名)
直接操作:
HTMLElement对象.setAttributeNode({Attr} attr);
HTMLElement对象.getAttributeNode({String} 属性名);
HTMLElement对象.removeAttributeNode({Attr} attr); // 返回被删除的Attr节点
注意:HTMLElement对象.removeAttributeNode({Attr} attr),当HTMLElement对象没有attr属性时,调用该方法会抛异常(NotFoundError: Failed to execute 'removeAttributeNode' on 'Element': The node provided is owned by another element.)
间接操作:
HTMLElement对象.setAttribute({String} 属性名, {Any} 属性值);
HTMLElement对象.getAttribute({String} 属性名);
HTMLElement对象.removeAttribute({String} 属性名);
HTMLElement对象.hasAttribute({String} 属性名); // IE8+才有方法,用于判断元素是否拥有该特性
注意:HTMLElement对象.removeAttribute({String} 属性名),当HTMLElement对象没有指定属性名的属性时,采用静默模式处理(就是删除成功一样返回undefined)
五、点方式——custom property的专属操作方式
var el = document.getElementById('dummy');
el.id; // 点方式
el['hello'] = 'test'; // 点方式
六、判断standard attribute的方式
我们可以通过点方式和getAttribute等方式访问standard attribute,但到底哪些是standard attribute哪些不是呢?下面介绍两种方式去判断。
①. 查阅http://msdn.microsoft.com/library/ms533029%28v=VS.85%29.aspx
②. 由司徒正美提供思路(生产环境中应该加入缓存从而提高性能)
// IE5+、Chrome、FF均有效
function isStandardAttr(node, prop){
// 由于window、document没有getAttribute等方法,这里暂时返回false好了
if (!node.getAttribute) return false; var nakedNode = document.createElement(node.nodeName);
return !(nakeNode[prop] === void 0 && nakeNode.getAttribute(prop) === null);
}
非standard attribute在未赋值时,点方式访问会返回undefine,而getAttribute方式访问会返回null。
而standard attribute在未赋值时,点方式访问会返回属性的默认值(title、id等会返回空字符串,而checked会返回false),而getAttribute方式访问会返回null。不利于判断。因此采用上一段的方式判断。
七、对于standard attribute,点方式和getAttribute方式操作的区别
首先要明确一点,通过点方式可对属性赋值任意js数据类型的属性值,通过setAttribute方式赋值则会自动对入参进行序列化后赋予给属性。因此点方式操作的任意js数据类型,而getAttribute等方法操作字符串类型的属性值。
区别1,获取的属性值不同:
点方式访问时是对属性值进行计算后的结果,getAttribute方式访问的是静态属性值。
以href属性为例,所在文件:c:\test.html,html标签:<a href="${链接1}"></a>:
| 浏览器 | 点方式 | 点方式结果 | getAttribute | getAttribute结果 |
| IE8+ | 绝对路径,符号被编码,中文不被编码 | file:///c:/$%7B链接1%7D | 原属性值 | ${链接1} |
| Chrome、FF | 绝对路径,符号被编码,中文被编码 | file:///c:/$%7B%E9%93%BE%E6%8E%A51%7D" | 原属性值 | ${链接1} |
区别2,属性名不同:
对于某些standard attribute而言,同一个属性,点方式和getAttribute方式分别使用不同的属性名来操作。
| 点方式 | getAttribute |
| className | class |
| htmlFor | for |
| style.cssText | style |
八、困惑的焦点——standard property
由于可通过点方式和getAttribute的方式操作standard property,因此相对其他两种属性而言,standard property是最复杂的。
下面我将其再细分为
①. 普通属性(如id、name等)
点方式和getAttribute方式操作一致,属性值自动转换为String类型。
②. 样式属性(style属性)
点方式的dom.style.cssText对应dom.getAttribute('style')操作,两者均是获取String类型属性值。在赋予正常的样式规则时,
各浏览器行为均一致。但复杂度就在当赋予异常的样式规则时,各浏览器行为如下:
| 赋值方式 | 点方式访问 | getAttribute方式访问 | 浏览器 |
| 点方式 | 空字符串 | null | Chrome、FF |
| setAttriubte | 空字符串 | 通过setAttribute设置的无效样式规则属性值 | |
| 点方式 | 空字符串 | null | IE9 |
| setAttribute | 空字符串 | 空字符串 | |
| 点方式 | 空字符串 | 空字符串 |
IE8,10,11 |
| setAttribute | 空字符串 | 空字符串 |
注意:IE8—11下,当通过setAttribute设置异常的样式规则时,html标签中的style属性会被删除,因此无法通过outerHTML来萃取异常样式规则的字符串值。
③. 布尔属性(如checked、disabled、selected等)
在折腾时发现同样是布尔属性,但特征却不尽相同,因此暂时给出如下分类。
3.1. 一般布尔属性(如disabled、IE5678下的checked)
| 赋值方式 | 赋予的值 | 点方式访问 | getAttribute方式访问 |
| 点方式 | true | true | 空字符串 |
| false | false | null | |
| setAttribute | 空字符串 | true | 空字符串 |
| 非disabled的任意字符串 | true |
IE9+、Chrome和FF是返回setAttribute设置的值; IE8是CHECKED |
|
| removeAttribute | false |
null |
通过setAttribute方式设置,只需出现布尔属性名称,布尔属性值即为true;
两种方式操作结果相互同步。
2014/12/08添加:
注意:FF下LINK元素的disabled属性是Attribute和Property关系分离的,两者互不影响。而样式是否应用于页面元素则由Property决定,并且当且仅当LINK元素被添加到渲染树后才能通过点方式设置disabled的值,否则设置均无效并还原为默认值false。
3.2. non-removeAttr的布尔属性(如selected)
确实不知道起什么名字好,于是只好暂时这样称呼吧。它的行为特征就是除了removeAttribute操作无法改变点方式获取的属性值内容外,
其他行为与一般布尔属性一样。具体如下:
所属SELECT元素为单选模式:
3.3. 变异布尔属性(如IE9+、Chrome和FF下checked)
变异布尔属性最大的特点是,在用户UI改动属性值和通过点方式改动属性值前,点方式和getAttribute方式是操作同一个属性。但经过用户UI或点方式改动属性值后,两者操作的就是同名的两个属性了,此时点方式操作才是与UI状态关联的属性。
具体代码如下:
IE9+、Chrome和FF下CHECKBOX和RADIO元素的checked属性属于变异布尔属性,而IE5678下的checked属性就属于双向布尔属性。
var cbx = document.createElement('input');
cbx.type = 'checkbox';
// UI或点方式改动属性前
cbx.setAttribute('checked', '');
console.log(cbx.checked); // 返回true
cbx.removeAttribute('checked');
console.log(cbx.checked); // 返回false
// 点方式改动属性后
cbx.checked = true;
console.log(cbx.checked); // 返回true
console.log(cbx.getAttribute('checked')); // 返回null
cbx.checked = false;
console.log(cbx.checked); // 返回false
cbx.setAttribute('checked', '');
console.log(cbx.checked); // 返回false
④. 事件钩子(如onclick等)
事件钩子是DOM0级的事件订阅方式,现在一般不怎么用了,但不妨碍我们去折腾。
而折腾的结果是却是让人惊奇的,因为它与之前理解的standard attribute的特征有差异,那就是点方式和getAttribute方式操作是单向影响的。
具体请看代码(IE8-11,Chrome,FF均如此):
var dom = document.createElement('DIV');
dom.setAttribute('onclick', 'console.log("bySA");');
/* 输出
* function onclick(){
* console.log("bySA");
* }
*/
console.log(dom.onclick);
/* 输出
* console.log("bySA");
*/
console.log(dom.getAttribute('onclick'));
dom.onclick = function(){
console.log('byProp');
};
/* 输出
* dom.onclick = function(){
* console.log('byProp');
* };
*/
console.log(dom.onclick);
/* 输出
* console.log("bySA");
*/
console.log(dom.getAttribute('onclick'));
// 输出byPorp
dom.click();
整体来说,就是setAttribute方式设置的事件钩子点方式可见,点方式设置的事件钩子getAttribute不可见。
⑤. 值属性(value属性)
用过JQuery都知道面对种类繁多的表单元素,一个val函数就能轻松搞定是一件多么惬意的事啊。但原生value属性到底有哪些坑呢?我们现在来踩一下。
5.1. SELECT标签
下拉框有单选(select-one)和多选(select-multiple)两种模式。而它的value属性由于是特性value和被选中项的text属性的运算结果,
因此建议使用点方式进行操作。
通过点方式获取和设置value值的运算流程如下:
| 浏览器 | 操作 | 流程 |
selectedIndex 默认值 |
| Chrome、FF | 获取 | 获取的第一被选中的option的value属性,若没有设置value属性,则返回该option标签的text属性 |
单选:0 多选:-1 |
| 设置 | 会根据属性值去匹配option标签的value属性值,若匹配成功则该option将被选中;若不成功,则匹配option的text属性值。若成功,则selectedIndex设置为-1。再次通过点方式访问value时,返回空字符串。 | ||
| IE9+ | 获取 | 获取的第一被选中的option的value属性,若没有设置value属性,则返回该option标签的text属性 |
单选:0 多选:-1 |
| 设置 | 会根据属性值去匹配option标签的value属性值,若匹配成功则该option将被选中;若不成功,则selectedIndex设置为-1。再次通过点方式访问value时,返回空字符串。 | ||
| IE5678 | 获取 | 获取的第一被选中的option的value属性,若没有设置value属性则返回空字符串。 | 单选、多选:-1 |
| 设置 | 会根据属性值去匹配option标签的value属性值,若匹配成功则该option将被选中;若不成功,则selectedIndex设置为-1。再次通过点方式访问value时,返回空字符串。 |
text属性:属性值就是选中项的innerText.trim()返回的字符串。
结论:通过SELECT元素的value属性获取选中项的值不可靠,因此mass framework在valHooks['@select:get']中是通过操作OPTION元素来获取选中项的值,
并且由于SELECT元素或OPTION元素的disabled属性值为true时,OPTION元素的selected属性依旧可能返回true,因此要对不可用的OPTION元素作过滤。
5.2. CHECKBOX和RADIO标签
value属性默认为字符串"on"
⑥. Url属性(如href、src等)
点方式获取的属性值为绝对路径且对特殊符号、中文进行编码,getAttribute方式获取的原来的值。
看到这里我想大家都有点头晕晕了吧,总结一下感觉会好些!
1. 对于普通属性,两种方式均可;
2. 对于样式、布尔和事件钩子属性建议统一采用点方式操作;
3. 对于值属性要不就使用JQuery等dom库统一操作,要不就具体元素具体操作吧,
mass framework的valHooks['@select:get']就是遍历option元素来获取select的选中值的;
4. Url属性看具体需求,若想获取绝对路径,那就用点方式吧,否则就用getAttribute吧!
九、window和document对象的属性分类
由于window和document对象均没有getAttribute函数,可知其必须没有custom attribute的。但我们还是可以将它们的属性分为固有属性和自定义属性。
固有属性:window和document对象自身携带的成员属性和方法;
特征:①. 无法通过delete操作删除固有属性,在IE5.5、6、7中还会抛异常呢!
②. 固有属性为只读属性,无法修改。
自定义属性:Jser们附加到window和document对象上的属性和方法。
特征:①. 可通过delete操作删除;
②. 自定义属性可随便改。
下面我将固有属性的判断和本文第六节中判断standard attribute的方法结合一下:
// IE5+、Chrome、FF均有效
function isStandardAttr(node, prop){
// 由于window、document没有getAttribute等方法,这里用固有属性为只读的特征来作判断
if (!node.getAttribute){
var oVal = node[prop], nVal;
nVal = node[prop] = +new Date();
node[prop] = oVal; return nVal === oVal;
} var nakedNode = document.createElement(node.nodeName);
return !(nakeNode[prop] === void && nakeNode.getAttribute(prop) === null);
}
十、IE5.5、6、7下的特性与属性
custom property和custom attribute都是IE8+的分类,在IE5.5、6、7下它俩可是一伙的。于是会发现在IE7下,dom.getAttribute('style')得到居然是个对象而不是样式规则的字符串。也许你会觉得这不碍事,反正在获取style属性时直接用点方式就好了。但下面的情况一不注意就会中bug了。
情况①:调用FORM元素的getAttribute获取action属性,居然得到其下的表单元素?
html
<form action="./add.aspx" name="frm" id="frm">
<input type="text" id="name"/>
<input type="text" name="id"/>
<select id="action">
<option value="">所有</option>
</select>
</form>
js
var fDom = document.getElementById('frm');
var action = fDom.getAttribute('action');
var name = fDom.geAttribute('name');
var id = fDom.geAttribute('id');
console.log(typeof action);// 返回object
console.log(action.id);// 返回action
console.log(typeof name);// 返回object
console.log(name.id);// 返回name
console.log(typeof id);// 返回object
console.log(id.name);// 返回id
也许大家会疑惑,这最多就是通过点方式获取FORM元素的属性值而已,为什么会获取其下id或name属性值匹配的表单元素呢?假如大家看过《JS魔法堂:那些困扰你的DOM集合》就会知道FORM元素有一个HTMLFormControllersCollection类型的elements属性,该属性可通过点方式获取FORM元素下id或name属性值匹配的表单元素。而该FORM元素直接拥有elements这一特征,因此点方式除了获取FORM元素自身的属性值外,还可以访问其下的表单元素。
解决办法采用getAttributeNode获取Attr类型对象
var actionNode = fDom.getAttributeNode('action');
var nameNode = fDom.geAttributeNode('name');
var idNode = fDom.geAttributeNode('id');
console.log(actionNode.value); // ./add.aspx
console.log(nameNode.value); // frm
console.log(idNode.value); // frm
情况②:没法通过href、src等获取绝对路径?
与操作其他属性不同,对于href、src等属性而言,点方式的行为特征被getAttribute同化了,仅能获取静态属性值。那怎么办呢?
IE对getAttribute作了增强,具体如下。
getAttrbute({String} 属性名, {Number} [0|1|2|4]):默认值0,表示使用IE默认行为;
1,属性名区分大小写;
2,获取属性的静态属性值;
4,获取绝对路径。
十一、其他:IE下tabIndex属性的非标准行为
标准浏览器(Chrome、FF等)下仅仅是表单元素(a,area)和链接元素(input,button,select,textarea,object)的tabIndex属性的默认值为0,
而其他元素的tabIndex默认值为-1。而IE下就是所有元素的tabIndex属性默认值均为0.
十二、总结
本来是打算针对IE5.5、6、7和其他浏览器的差异、IE的bugs和各类型属性的特点来修补getAttribute等方法,但发现属性系统水深啊,
为原生API打补丁成本高效益差,还不如像JQuery那样重新包装上市来得爽快。于是本篇仅仅是记录了属性系统的一些坑而已,还不够全面,也暂时没能提出有效的封装方式。
想继续深入的朋友可以读读个大框架的源码哦!
十三、感谢那些巨人
《JavaScript框架设计》
《JQuery源码分析》 http://www.cnblogs.com/aaronjs/p/3279314.html
尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/3840263.html ^_^肥仔John
JS魔法堂:属性、特性,傻傻分不清楚的更多相关文章
- JS魔法堂:那些困扰你的DOM集合类型
一.前言 大家先看看下面的js,猜猜结果会怎样吧! 可选答案: ①. 获取id属性值为id的节点元素 ②. 抛namedItem is undefined的异常 var nodes = documen ...
- JS魔法堂:LINK元素深入详解
一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...
- JS魔法堂:不完全国际化&本地化手册 之 实战篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- JS魔法堂:浏览器模式和文档模式怎么玩?
一.前言 从IE8开始引入了文档兼容模式的概念,作为开发人员的我们可以在开发人员工具中通过“浏览器模式”和“文档模式”(IE11开始改为“浏览器模式”改成更贴切的“用户代理字符串”)品味一番,它的出现 ...
- JS魔法堂:精确判断IE的文档模式by特征嗅探
一.前言 苦逼的前端攻城狮都深受浏览器兼容之苦,再完成每一项功能前都要左顾右盼,生怕浏览器不支持某个API,生怕原生API内含臭虫因此判断浏览器类型和版本号成了不可绕过的一道关卡,而特征嗅探是继浏览器 ...
- JS魔法堂:追忆那些原始的选择器
一.前言 ...
- JS魔法堂:不完全国际化&本地化手册 之 理論篇
前言 最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...
- JS魔法堂:判断节点位置关系
一.前言 在polyfill querySelectorAll 和写弹出窗时都需要判断两个节点间的位置关系,通过jQuery我们可以轻松搞定,但原生JS呢?下面我将整理各种判断方法,以供日后查阅. 二 ...
- JS魔法堂:IMG元素加载行为详解
一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...
随机推荐
- 【转】使用Cocoapods创建私有podspec
Cocoapods是非常好用的一个iOS依赖管理工具,使用它可以方便的管理和更新项目中所使用到的第三方库,以及将自己的项目中的公共组件交由它去管理.Cocoapods的介绍及优点本文就不在赘述,我开始 ...
- 删除windows系统中以前的设备(比如以前的网卡)或驱动的方法
1.在“开始”菜单单击“运行”,然后在“运行”对话框中输入“CMD”命令打开命令提示符窗口:2.在提示符窗口中输入“Set devmgr_show_nonpresent_devices=1”并回车:3 ...
- Oracle Essbase入门系列(三)
数据库计算 Essbase中单元格的数据可以是外部输入或计算而得,单元格因而分为输入单元格和计算单元格.计算单元格的计算方法可以通过大纲中维度成员的合并计算符和公式脚本定义,此称为大纲计算定义. 例1 ...
- Oracle Essbase入门系列(二)
本篇开始会一个三口之家的家庭财务数据库为例,讲述Essbase的功能和开发.为了说明EPM应用程序的管理和开发过程,会绕一些弯路,不使用EAS,而尽量用EPMA. 创建应用程序 首先登陆到Worksp ...
- python排序算法的实现-冒泡
1.算法描述: (1)共循环 n-1 次 (2)每次循环中,如果 前面的数大于后面的数,就交换 (3)设置一个标签,如果上次没有交换,就说明这个是已经好了的. 2.代码 #!/usr/bin/pyth ...
- 不停止MySQL服务增加从库的两种方式
不停止MySQL服务增加从库的两种方式 转载自:http://lizhenliang.blog.51cto.com/7876557/1669829 现在生产环境MySQL数据库是一主一从,由于业务量访 ...
- iOS 内存管理机制和循环引用处理方法
简述 ARC: 自动引用计数, Automatic Reference Counting MRC: Mannul Reference Counting ARC工作原理 1.当每次创建一个新实例时,AR ...
- git delete repository
- shell 常用命令
Terminal是Mac OS X系统中的字符控制界面,可以更灵活地控制苹果电脑以下看到 “>“ 就是打指令的地方,prompt,指令列>pwd列出路径>ls列出此档案夹里所有的东西 ...
- bower 新建.bowerrc文件
Twitter工程师团队推出了Bower,这是一个针对Web开发的包管理器.该工具主要用来帮助用户轻松安装CSS.JavaScript.图像等相关包,并管理这些包之间的依赖. 随着网页功能变得越来越复 ...