zoukankan      html  css  js  c++  java
  • 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: est.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元素为单选模式:

            通过点方式操作selected属性时,true表示选中,false表示不选中;通过setAttribute时,表示选中,且点方式访问selected时会返回true;
          但通过removeAttribute移除selected属性后,并不会改变选中项,因为selectedIndex没有被改变。
            推断:option标签设置selected显式属性后,会改变selectedIndex的值,从而改变选中项;而removeAttribute时仅仅去除该属性,
             而没有改变selectedIndex值,因此不会改变选中项。而点方式是根据selectedIndex去获取项目是否被选中。
     
          所属SELECT元素为多选模式:
              通过点方式操作selected属性时,true表示选中,false表示不选中;通过setAttribute时,表示选中,且点方式访问selected时会返回true;
          但通过removeAttribute移除selected属性后,并不会改变选中项。

        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 0 && 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="0">所有</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

  • 相关阅读:
    apache安全—用户访问控制
    hdu 3232 Crossing Rivers 过河(数学期望)
    HDU 5418 Victor and World (可重复走的TSP问题,状压dp)
    UVA 11020 Efficient Solutions (BST,Splay树)
    UVA 11922 Permutation Transformer (Splay树)
    HYSBZ 1208 宠物收养所 (Splay树)
    HYSBZ 1503 郁闷的出纳员 (Splay树)
    HDU 5416 CRB and Tree (技巧)
    HDU 5414 CRB and String (字符串,模拟)
    HDU 5410 CRB and His Birthday (01背包,完全背包,混合)
  • 原文地址:https://www.cnblogs.com/fsjohnhuang/p/3840263.html
Copyright © 2011-2022 走看看