第十章:属性模块

通常我们把对象的非函数成员叫属性

对于元素节点来说,其属性大体分为两类,固有属性与自定义属性(特性)。固有属性一般遵循驼峰命名风格,拥有默认值,并且无法删除。

自定义属性是用户随意添加的属性值对,由于元素节点也是一个普通的javascript对象,没有什么严格的访问操作,因此命名风格林林总总,值的类型也是乱七八糟。但是随意添加属性显然不够安全,比如引起循环引用等,因此,浏览器提供了一组API来供人们操作自定义属性

即:setAtttribute, getAttribute, removeAttribute。当然还有其他API。不过这是标准套装。只有在IE67等糟糕的环境下,我们才会求助其他的API、一般情况下此三个属性足也

我们通常称其为DOM属性系统。DOM属性系统对属性名会进行小写化处理,属性值会统一转字符串

    var el = document.createElement("div");
    el.setAttribute("xxx","1");
    el.setAttribute("XxX","2");
    el.setAttribute("XXx","3");

    console.log(el.getAttribute("xxx"))
    console.log(el.getAttribute("XxX"))

IE6,IE7会返回1,其他浏览器返回3,在前端的世界里,我们走到哪都能碰到兼容性的问题。
IE67 在处理固有属性时要求进行名字映射,比如class变成className,for变成htmlFor。

对于布尔属性(一些返回布尔的属性),浏览器的差异更大。

<input type="radio" id="aaa">
<input type="radio" checked  id="bbb">
<input type="radio" checked="checked"  id="ccc">
<input type="radio" checked="true" id="ddd">
<input type="radio" checked="xxx" id="eee">
<script type="text/javascript">
"aaa,bbb,ccc,ddd,eee".replace(/\w+/g,function (id) {
    var elem = document.getElementById(id)
    console.log(elem.getAttribute("checked"))
})
</script>

IE9 FF15 chrome23以上分别为  null "" checked true xxx ,在ie 7 ie8差别太多,请自行尝试。

因此框架很有必要提供一些API来屏蔽这些差异性。但IE6时代,这个需求并不明显。因此,ie67不区分固有属性与自定义属性。 setAttribute和getAttribute在当时人们看来就是一个语法糖。用el.setAttribute("innerHTML","xxxx")与用el.innerHTML = "xxxx"效果一样,而且后者使用更方便。

即使早期使用广泛的prototype.js也提供的api也非常贫乏。

只有identify,readAttribute,writeAttribute,hasAttribute,className,hasClassName,addClassName,removeClassName toggleClassName与$F方法。

后来prototype.js也意识到固有属性和自定义属性之间的差异。在它的内部,搞了个Element._attributeTranslations 。

然而,在其内部还是优先el[name]方式来操作属性,而不是set/getAttribute。

jQuery早期的attr方法,其行为与prototype一模一样。只 不过jQuery1.6之前,是使用attr方法实现了读写删这三种操作。从易用性来说。不区分固有属性和自定义属性,由框架内部处理应该比attr,prop分家更容易接受。那么是什么致使jQuery这样做呢答案是选择器引擎

 

jQuery是最早以选择器为导向的类库。它最开始的选择器引擎是Xpath式,后来换成sizzle.以css表达式风格来选取元素,。在css.2.1引入了属性选择器[aaa=bbb],ie7也开始残缺支持。Sizzle毫不含糊地实现了这语法。属性选择器是最早突破类名与ID的限制求元素的。

css3时代,属性还能以[name^=value],[name*=value],[name$=value]等更精致的方式来甄选元素,而这一切都是建立在获取用户的预设值的基础上。因此jQuery下了很大的决心,把prop从attr切割出来。虽然为了满足用户的向前 兼容需求。又让attr做了prop的事。


浏览器经过了这么多年发展,谁也说不清元素节点有多少个属性。for.in循环也不行,因为它对不可遍历的属性无能为力。显式属性就是被显式设置的属性,分两种情况,一种是写在标签内的HTML属性,一种是通过setAttribute动态设置的属性。还是以el[xxx]=yyy的定义属性,还是没有定义的属性。可惜到ie8与其他浏览器中,你只看到寥寥可数特性节点。称为显式属性

在IE或其它浏览器,我们想判定一个属性是否为显式属性,直接用hasAttribute API判定

    var isSpecified = !"1"[0] ? function(el, attr) {
        return el.hasAttribute(attr)
    } : function(el, attr) {
        var val = el.attributes[attr]
        return !!val && val.specified
    }

二.className的操作

我们操作一个属性通常只有三个选择:设置,读取,删除。但className有点特殊。它的值可以用空格隔开,分为多个类名。因此对类名的操作变成读取,添加,删除。在上代王者prototype.js,就已经把人们想要的类名操作总结出来。

我们操作一个属性通常只有三个选择:设置,读取,删除。但className有点特殊。它的值可以用空格隔开,分为多个类名。因此对类名的操作变成读取,添加,删除。在上代王者prototype.js,就已经把人们想要的类名操作总结出来。

    //prototype1.7
    ClassName: function(element) {
        return new Element.ClassNames(element);
    },

    hasClassName: function(element, className) {
        if ((element = $(element))) return;
        var elementClassName = element.className;
        return (elementClassName.length >0 && (elementClassName == className  new RegExp("(^\\s)" + className + "(s$)").test(elementClassName)));
    },

    addClassName:function(element,className) {
        if (!(element = $(element))) return;
        if (!Element.hasClassName(element, className))
            element.className += (element.className ? ' ' : '') + className;
        return element;
    },

    removeClassName: function(element,className) {
        if (!(element = $(element))) return;
        element.className = element.className.replace(
            new RegExp("(^\\s+)" + className + "(\\s+$)"), ' ').strip();
        return element;
    },

    toggleClassName : function(element, className) {
        if (!(element = $(element))) return;
        return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'] (element,className)
    },

另外,除了这些外,还提供了一个Element.ClassNames类,用于直接操作类名,这不禁把html5提供的classList联想起来

prototype.js对className的操作已被认可,我们亦可以吧prototype.js精简一下,变成一些工具函数。在不引入类库时使用。

    var getClass = function(ele) {
        return ele.className.replace(/\s+/," ").split(" ");
    }

    var hasClassName = function(ele,cls){
        return -1 < (" "+ele.className+ " ").indexOf(" "+cls+" ");
    }

    var addClass = function(ele,cls) {
        if (!this.hasClass(ele,cls)) {
            ele.className += " "+ cls;
        }
    }

    var removeClass = function(ele,cls) {
        if (hasClass(ele,cls)){
            var reg = new RegExp ('(\\s^)'+cls+'(\\s$)');
            ele.className = ele.className.replace(reg," ");
        }
    }

    var clearClass = function(ele, cls) {
        ele.className = ""
    }

 

三.jQuery的属性系统

希望分析早期的jQuery,带来一点启迪,jQuery系统经年累月,量变引发质变的结果。

    attr: function(elem, name, value) {
        var fix = {
            "for": "htmlFor",
            "class": "className",
            "float": "cssFloat",
            innerHTML: "innerHTML",
            className: "className",
            value: "value",
            disabled : "disabled"
        };
        if (fix[name]) {
            if (value != undefined) elem[fix[name]] = value;
            return elem[fix[name]];
        } else if ( elem.getAttribute ) {
            if (value != undefined ) elem.setAttribute( name, value);
            return elem.getAttribute(name, 2);
        } else {
            name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});
            if (value != undefined ) elem[name] = value;
            return elem[name]
        }
    },

这个方法不断膨胀,加入prototype.js发掘出来的特殊属性处理,以及社区补丁。jquery1.5.2,这个attr方法已经接近100行。
在1.6时,把1.5版本css模块想出的好方法,cssHooks适配器移植过来,解决了扩展的难题。

jquery1.6中存在4个适配器,从单词直接移过来,就是formHooks,attrHooks,propHooks,valHooks。formHook是在attr方法对付旧版本的IE的form元素用到的。到jQuery1.61.新增一个boolHook对付布尔属性值。

jquery1.6.3,人们发现ie大多数情况使用getAttributeNode,就能取到正确值,因此对formHook重构下。更名为nodeHooks,便形成今天的jQuery的属性系统。如图

第十章:属性模块

 

上图为jQuery的属性系统概述图,新生的prop方法异常简单,复杂些都移动到钩子。
古老的attr方法则无比复杂,兼任读,写,删三职,由于IE的情况不得不动用到3个钩子(钩子只处理有问题的属性),不处理一般情况。

    //jQuery1.83
    prop:function(elem, name, value) {
        var ret, hooks, notxml, nType = elem.nodetype;

        if ( !elem  nType === 3  nType === 8  nType === 2) {
            return;//跳过注释节点,文本节点,特性节点
        }

        notxml = nType !== 1  !jQuery.isXMLDoc(elem);
        if (notxml) { //如果是HTML文档的元素节点
            name = jQuery.propFix[name]  name;
            hooks = jQuery.propHooks[name];
        }

        if (value !== undefined) { //写方法
            if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
                return ret; //处理特殊情况
            } else { //处理通用情况
                return (elem[name] = value ); 
            }

        } else { //读方法
            if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            return ret;//处理特殊情况    
            } else { //处理通用情况
                return elem[name];
            } 
        }
    } ,
    attr: function (elem, name, value) {
        var ret, hooks, notxml, nType = elem.nodeType;
        if (!elem  nType === 3  nType === 8  nType === 2) {
            return;// 跳过注释节点,文本节点,特性节点
        }

        if ( typeof elem.getAttribute === "undefined") {
            return jQuery.prop(elem, name, value); //文档与window.只能使用prop
        }

        notxml = nType !== 1  !jQuery.isXMLDoc(elem);
        if (notxml) { //如果是HTML文档元素节点
            name = name.toLowerCase();//决定用哪一个钩子
            hooks = jQuery.attrHooks[name]  (rboolean.test( name ) ? boolHook : nodeHook);
        }

        if (value !== undefined) {
            if (value === null) { //模仿prototypejs移除属性
                jQuery.removeAttr(elem, name);
            } else if (hooks && notxml && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
                return ret;// 处理特殊情况
            } else { //处理通用
                elem.setAttribute(name, value + "");
                return value;
            }
        } else if (hooks && notxml && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            return ret;//处理特殊情况
        } else { //处理通用情况
            ret = elem.getAttribute( name );
            return ret === null ? undefined : ret;
        }
    },

 

四:value的操作

之所以将它独立出来,是因为它非常重要,涉及与后端的交互。而value值,一般而言,只有表单元素的value才对我们有用。下面简单来说每个表单元素的情况

select元素,它的value值就是被选中的option孩子的value值。不过,select有两种形态,一种type为select-one,令一种为select-multiple,就是当用户设置了multiple属性。在多选的形态下。我们可以使用ctrl键进行多选。

option元素,它的value值可以是value属性的值,也可以是其中间的文本,换言之就是innrtText,当用户没有显式的设置value属性时,它就取innertext,不过这个innerText要使用text属性来取。就像script标签一样。有人会问,为什么不使用innerHTML呢?因为这个option元素的text属性比innerHTML多一个trim操作,去掉两边的空白(旧版本的IE的innerHTML会做trim操作,标准浏览器不会)。

button元素,它的取值与option元素有点类似又不尽相同,在ie6 7中,它取元素的innerText,在IE8时,它才与其它浏览器保持一致,取value属性的值。

checkbox,radio在设置value时,应该考虑对checked属性的修改。
因此在val方法对应的适配器内,应该有六组方法,对select,option的特殊处理,对button的兼容处理,对checkbox,radio的check值处理,最后是默认值。

关于valhooks请关注业内更多的处理方法。此处仅做备注。

 (本章结束

上一章: 第九章:样式模块  下一章:事件系统(onXXX等缺陷)

更多相关文章
  • 最近在向Linux内核提交一些驱动程序,在提交的过程中,发现自己的代码离Linux内核的coding style要求还是差很多.当初自己对内核文档里的CodingStyle一文只是粗略的浏览,真正写代码的时候在很多细节上会照顾不周.不过, 在不遵守规则的程序员队 伍里,我并不是孤独的.如果去看dri ...
  • JEECG(J2EE Code Generation)是一款基于代码生成器的免费开源的快速开发平台.使用JEECG可以简单快速地开发出企业级的Web应用系统. 暴露的admin密码重置接口 代码在https://github.com/zhangdaiscott/jeecg/blob/02d82286 ...
  • 新建两个用户普通用户:test1管理员:test2 执行个添加管理的操作,test1的sw_login_id为105274ViewPage.ssi?_type=user&_id=2&_action=new&_hiden=whichgroup&_show=mini 查看 ...
  • 1.引言  Markus Wulftange在7月30日报告了赛门铁克端点保护Symantec Endpoint Protection (SEP)  12.1的数个高危漏洞.只有升级到12.1 RU6 MP1的版本不被影响. 简单介绍一下,SEP 是一个企业版的杀毒产品,分为管理端和客户端.管理端是 ...
  • 在上一篇从struts2的action中看ActionContext的存储结构中已经看到ActionContext.getActionContext.getContextMap()得到的map的大体结构类型. 这篇将从源代码层面进行分析. 一.回顾 在上一篇中看到ActionContext中存放的是 ...
  • 前段时间,公司制造的机器里应用装有不良广告,严重影响了儿童客户使用者的思想健康,导致被人投诉.于是乎,就有了想研发一款类似于360广告屏蔽的应用的念头.嗯,事情就是这样,现在切入主题. 目前市场上有很多安全软件,它们拦截第三方应用广告的方式都不一样,比如说有 以so 注入方式来拦截弹出广告. 现在我 ...
一周排行
  • 云计算服务除了提供计算服务外,还必然提供了存储服务.但是云计算服务当前垄断在私人机构(企业)手中,而他们仅仅能够提供商业信用.对于政府机构.商业机构(特别像银行这样持有敏感数据的商业机构)对于选择云计算服务应保持足够 ...
  • 工作了这么久,封装过一部分Helper,也写过一些控件,但也没写过属于自己的框架,这次写的这个我觉得是一个组件而已,是一个定时组件.是一个定时器组件,有别于.NET Framework里面提供的几个Timer.首先说 ...
  • 金慧瑜 王思琪新三板注册制和做市制度的落实.战略新兴板等政策利好,再加之一些国内对标企业享受的超高估值,让不少中概股心生归念.但面对当下A股市场的变脸.IPO暂缓,一些正要拆VIE结构的公司开始纠结何去何从.&quo ...
  • nocows解题报告[题目] 给你N个点,生成一棵K层的树,有s种情况.请输出s%9901的值. 要求很简单,每个点只能有0个或2个孩子.[数据范围] 3<=N<200 1<K<100[输入格 ...
  • 在用Android Studio开发的过程中,一遇到废弃.不被推荐的方法和类,我就想做点什么去掉上面的横线.然后,被一个不是问题的问题困扰了很久. 之前我们在创建固定Tabs的时候,类似于这样的功能, 使用的是Act ...
  • 本来在centos里不好装的软件,往往ubuntu里会很好装,但sipp恰恰相反,ubuntu里能装死你. 做VOIP测试的话,有时候为了模拟通话中更好的抓包,在环境简陋,又不想使用集线器引起广播风暴的前提下,sip ...
  • wifi通话对于经常在外出差的小伙伴们是一个非常棒的功能,那么iphone6 plus支持wifi通话吗,iphone6 plus有wifi通话功能吗 iphone6 plus支持wifi通话吗?wifi通话对于经常 ...
  • /* * Copyright (c) 2014, 烟台大学计算机学院 * All rights reserved. * 文件名称:sum123.cpp * 作 者:张恒宇 * 完成日期:2014年 11 月 17日 ...
  • 大家好:    今天终于小试牛刀,终于写了linux两个命令,很是开心啊,之前一直想学,没人教,可是做项目又用到,需要看服务器后台打印的消息!当时真的是纠结了一阵,今天又有空来整下linux命令,并且成功了,真是开心 ...
  • 工作任务描述 HT公司临时配置了一台独立的服务器Filesvr,公司的员工想从自己的计算机访问服务器上的一个共享文件夹share.并实现以下功能: 实验任务和目标: 1. 创建"工单二"中的用户和 ...