jQuery-1.9.1源码分析系列(八) 属性操作

  jQuery的属性操作主要包括

  jQuery.fn.val

  jQuery.fn.attr

  jQuery.fn.removeAttr

  jQuery.fn.prop

  jQuery.fn.removeProp

  jQuery.fn.addClass

  jQuery.fn.removeClass

  jQuery.fn.toggleClass

  

  接下来一一分析jQuery对他们的处理

a. jQuery.fn.val



  jQuery.fn.val用来获取jQuery对象的第一个元素的val值或者给jQuery对象的每一个元素设置其val值。参数个数为0表示获取get,否则表示设置set。

  处理过程比较简单:

  判断参数个数,没有参数表示获取当前匹配元素中的第一个元素的value值,此时如果能使用valHooks则使用之,否则使用elem.value获取值(null/undefined需要转成空字符"");

  如果有参数,表示要为当前所有的匹配元素设置值,如果参数是函数,调用函数的返回值作为值val,否则使用传递的参数做为值val。能使用则用之,否则使用elem.value = val。

  源码:

val: function( value ) {
    var ret, hooks, isFunction,
    elem = this[0];//获取jQuery对象的第一个元素
    //获取值
    if ( !arguments.length ) {
        if ( elem ) {
       hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
            //通过hooks如果能取到值则返回
            if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
                return ret;
            }
            //否则正常取值
            ret = elem.value;
            return typeof ret === "string" ?
                    // 换行符转换成空字符
                    ret.replace(rreturn, "") :
                    //处理null/undef 或数字
                    ret == null ? "" : ret;
        }
        return;
    }

    isFunction = jQuery.isFunction( value );
    //对jQuery对象的每一个元素设置val
    return this.each(function( i ) {
        var val,
        self = jQuery(this);
        //元素不为DOM节点则返回
        if ( this.nodeType !== 1 ) {
            return;
        }

        if ( isFunction ) {
            val = value.call( this, i, self.val() );
        } else {
            val = value;
        }

        //用空字符替换null/undefined;数字转化成字符串
        if ( val == null ) {
            val = "";
        } else if ( typeof val === "number" ) {
            val += "";
        } else if ( jQuery.isArray( val ) ) {
            val = jQuery.map(val, function ( value ) {
                return value == null ? "" : value + "";
            });
        }

        hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

        //如果hooks的set返回为undefined,则使用正常设置
        if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
            this.value = val;
        }
    });
}

b. jQuery.fn.attr  



  设置或返回当前jQuery对象所匹配的元素节点的属性值。

        attr: function( name, value ) {
            return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
        }

  当前匹配的元素挨个执行jQuery.attr,并返回执行结果集。

  $(...).attr的最基础api函数jQuery.attr。关键代码如下,其他省略。深度理解钩子机制

notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
// All attributes are lowercase
// Grab necessary hook if one is defined
if ( notxml ) {
  name = name.toLowerCase();
  //查找hook
  hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
}
if ( value !== undefined ) {
  if ( value === null ) {
    jQuery.removeAttr( elem, name );
  //如果有hooks,就使用hooks来处理
  } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
    return ret;
  } else {…}
//如果有hooks,就使用hooks来处理
} else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
  return ret;
} else {…}

c. jQuery.fn.removeAttr



  用于移除在当前jQuery对象所匹配的每一个元素节点上指定的属性  

    removeAttr: function( name ) {
        return this.each(function() {
            jQuery.removeAttr( this, name );
        });
    }

  内部使用低级API jQuery.removeAttr实现

  底层使用elem.removeAttribute来实现。不过需要注意的是因为可能根本就没有要删除的属性,所以在删除之前都设置了该属性

                // Boolean attributes get special treatment (#10870)
                if ( rboolean.test( name ) ) {
                    // 如果是bool类型的属性(attribute)设置相应的特性(property)
                    //同时清除defaultChecked/defaultSelected (if appropriate) for IE<8
                    if ( !getSetAttribute && ruseDefault.test( name ) ) {
                        elem[ jQuery.camelCase( "default-" + name ) ] =
                        elem[ propName ] = false;
                    } else {
                        elem[ propName ] = false;
                    }

                // See #9699 for explanation of this approach (setting first, then removal)
                } else {
                    jQuery.attr( elem, name, "" );
                }

  完整的jQuery.removeAttr源码如下

removeAttr: function( elem, value ) {
  var name, propName,
  i = 0,
  attrNames = value && value.match( core_rnotwhite );
  if ( attrNames && elem.nodeType === 1 ) {
    while ( (name = attrNames[i++]) ) {
      propName = jQuery.propFix[ name ] || name;

      // Boolean attributes get special treatment (#10870)
      if ( rboolean.test( name ) ) {
        // 如果是bool类型的属性(attribute)设置相应的特性(property)
        //同时清除defaultChecked/defaultSelected (if appropriate) for IE<8
        if ( !getSetAttribute && ruseDefault.test( name ) ) {
          elem[ jQuery.camelCase( "default-" + name ) ] = elem[ propName ] = false;
        } else {
           elem[ propName ] = false;
        }

      // See #9699 for explanation of this approach (setting first, then removal)
      } else {
        jQuery.attr( elem, name, "" );
      }

      elem.removeAttribute( getSetAttribute ? name : propName );
    }
  }
}

  

d. jQuery.fn.prop



  在分析这个函数之前先说一点必备知识。

  attribute和property的区别

  1)      property是对象的属性值(有的时候文章中也称为特征值,他们是相同的),通过elem[ name ]来取值/赋值; 而attribute是直接写在标签上的属性,通过elem.getAttribute /elem.setAttribute。观察一张图很直观的理解(引用Aaron的图例

 attributes是一个类数组的容器,说得准确点就是NameNodeMap,总之就是一个类似数组但又和数组不太一样的容器。attributes的每个数字索引以名值对(name=”value”)的形式存放了一个attribute节点。attributes是会随着添加或删除attribute节点动态更新的。property就是一个属性,如果把DOM元素看成是一个普通的Object对象,那么property就是一个以名值对(name=”value”)的形式存放在Object中的属性。要添加和删除property也简单多了,和普通的对象没啥分别。之所以attribute和property容易混倄在一起的原因是,很多attribute节点还有一个相对应的property属性

  2)      boolen类型的attr值更改是通过prop(elem[ propName ] = false;)方式来处理的,因为properties就是浏览器用来记录当前值的东西,boolean properties保持最新。但相应的boolean attributes是不一样的,它们仅被浏览器用来保存初始值。

  jQuery.fn.prop内部使用低级API jQuery.prop实现

prop: function( name, value ) {
        return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
    },

  jQuery.prop的核心代码如下,其他代码省略

if ( notxml ) {
  // Fix name and attach hooks
  name = jQuery.propFix[ name ] || name;
  //查找hook
  hooks = jQuery.propHooks[ name ];
}
if ( value !== undefined ) {
  //如果有hooks,就使用hooks来处理
  if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
    return ret;
  } else {
        return ( elem[ name ] = value );
  }
} else {
  //如果有hooks,就使用hooks来处理
  if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
    return ret;
  } else {
    return elem[ name ];
  }
}

e. jQuery.fn.removeProp



  jQuery.fn.removeProp比较简单,就如同普通的js对象一样删除某个属性直接使用delete即可

propFix: {
  tabindex: "tabIndex",
  readonly: "readOnly",
  "for": "htmlFor",
  "class": "className",
  maxlength: "maxLength",
  cellspacing: "cellSpacing",
  cellpadding: "cellPadding",
  rowspan: "rowSpan",
  colspan: "colSpan",
  usemap: "useMap",
  frameborder: "frameBorder",
  contenteditable: "contentEditable"
}
removeProp: function( name ) {
        name = jQuery.propFix[ name ] || name;
        return this.each(function() {
            //try/catch handles cases where IE balks (such as removing a property on window)
            try {
                //删除prop处理
                this[ name ] = undefined;
                delete this[ name ];
            } catch( e ) {}
        });
    }

f. jQuery.fn.addClass



  这个函数处理比较简单,将参数字符串使用空格分隔(多个class)为classes,将原来的ClassName获取出来为cur,将分classes添加到cur中即可(在此过程中需要保证classes[i]在cur中不存在即可)

  重点源码

               //使用空格符分隔参数          classes = ( value || "" ).match( core_rnotwhite ) || [];

                for ( ; i < len; i++ ) {
                    elem = this[ i ];            //获取原来的class 名称
                    cur = elem.nodeType === 1 && ( elem.className ?
                        ( " " + elem.className + " " ).replace( rclass, " " ) :
                        " "
                        );

                    if ( cur ) {
                        j = 0;              //class名称组合
                        while ( (clazz = classes[j++]) ) {                //保证class名称的唯一性
                            if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
                                cur += clazz + " ";
                            }
                        }
                        elem.className = jQuery.trim( cur );

                    }
                }

g. jQuery.fn.removeClass



  这个函数也比较简单了,这里不分析了

h. jQuery.fn.toggleClass



  这个函数只需要在调用.addClass和.removeClass之间切换即可。toggleClass因为可能需要来回切换的原因,需要保存原来的class,以便下次调用的时候恢复回来。不过需要注意的是当没有传递参数时,被认为是整个Class的切换,需要保存原来的class,以便下次调用的时候恢复回来,关键代码如下

// Toggle whole class name
else if ( type === core_strundefined || type === "boolean" ) {
    if ( this.className ) {
    jQuery._data( this, "__className__", this.className );
    }

this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
}

  完整源码  

        toggleClass: function( value, stateVal ) {
            var type = typeof value,
            isBool = typeof stateVal === "boolean";

            if ( jQuery.isFunction( value ) ) {
                return this.each(function( i ) {
                    jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
                });
            }

            return this.each(function() {
                if ( type === "string" ) {
                    // toggle individual class names
                    var className,
                    i = 0,
                    self = jQuery( this ),
                    state = stateVal,
                    classNames = value.match( core_rnotwhite ) || [];

                    while ( (className = classNames[ i++ ]) ) {
                        // check each className given, space separated list
                        state = isBool ? state : !self.hasClass( className );
                        self[ state ? "addClass" : "removeClass" ]( className );
                    }

                // Toggle whole class name
                } else if ( type === core_strundefined || type === "boolean" ) {
                    if ( this.className ) {
                            // store className if set
                            jQuery._data( this, "__className__", this.className );
                    }

                    // If the element has a class name or if we‘re passed "false",
                    // then remove the whole classname (if there was one, the above saved it).
                    // Otherwise bring back whatever was previously saved (if anything),
                    // falling back to the empty string if nothing was stored.
                    this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
                }
            });
        },

  如果觉得本文不错,请点击右下方【推荐】!

时间: 2024-11-03 20:46:41

jQuery-1.9.1源码分析系列(八) 属性操作的相关文章

jQuery源码分析系列(38) : 队列操作

Queue队列,如同data数据缓存与Deferred异步模型一样,都是jQuery库的内部实现的基础设施 Queue队列是animate动画依赖的基础设施,整个jQuery中队列仅供给动画使用 Queue队列 队列是一种特殊的线性表,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).队列的特点是先进先出(FIFO-first in first out),即最先插入的元素最先被删除. 为什么要引入队列? 我们知道代码的执行流有异步与同步之分,例如 var a

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(

jquery2源码分析系列目录

学习jquery的源码对于提高前端的能力很有帮助,下面的系列是我在网上看到的对jquery2的源码的分析.等有时间了好好研究下.我们知道jquery2开始就不支持IE6-8了,从jquery2的源码中可以学到很多w3c新的标准( 如html5,css3,ECMAScript).原文地址是:http://www.cnblogs.com/aaronjs/p/3279314.html 关于1.x.x版的jquery源码分析系列,本博客也转载了一个地址http://www.cnblogs.com/jav

jQuery源码分析系列(33) : AJAX中的前置过滤器和请求分发器

jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求,分别是: 1.前置过滤器 jQuery. ajaxPrefilter 2.请求分发器 jQuery. ajaxTransport, 3.类型转换器 ajaxConvert 源码结构: jQuery.extend({ /** * 前置过滤器 * @type {[type]} */ ajaxPrefilter: addToPrefiltersOrTransports(prefilters), /** * 请求分发器 *

jQuery源码分析系列(36) : Ajax - 类型转化器

什么是类型转化器? jQuery支持不同格式的数据返回形式,比如dataType为 xml, json,jsonp,script, or html 但是浏览器的XMLHttpRequest对象对数据的响应只有 responseText与responseXML 二种 所以现在我要定义dataType为jsonp,那么所得的最终数据是一个json的键值对,所以jQuery内部就会默认帮你完成这个转化工作 jQuery为了处理这种执行后数据的转化,就引入了类型转化器,如果没有指定类型就依据响应头Con

jQuery源码分析系列(34) : Ajax - 预处理jsonp

上一章大概讲了前置过滤器和请求分发器的作用,这一章主要是具体分析每种对应的处理方式 $.ajax()调用不同类型的响应,被传递到成功处理函数之前,会经过不同种类的预处理(prefilters). 预处理的类型取决于由更加接近默认的Content-Type响应,但可以明确使用dataType选项进行设置.如果提供了dataType选项, 响应的Content-Type头信息将被忽略. 有效的数据类型是text, html, xml, json,jsonp,和 script. dataType:预期

jQuery源码分析系列(39) : 动画队列

data函数在jQuery中只有短短的300行代码,非常不起点 ,剖析源码的时候你会发现jQuery只要在有需要保存数据的地方无时无刻不依赖这个基础设施 动画会调用队列,队列会调用data数据接口还保存队列里面的的动画数据 所以我们在自习回顾下关于数据缓存 //These may be used throughout the jQuery core codebase //存数据的 //用户使用 data_user = new Data(); //存储对象 //jQuery内部私有 //用来存事件

jQuery源码分析系列(35) : Ajax - jsonp的实现与原理

ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加<script>标签来调用服务器提供的js脚本 json核心就是:允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了. jquery ext dojo这类库的实现手段其实大同小异 在同源策略下,在某个服务器下的页面是无法获取到该服务器以外的数据的,但img.iframe.s

jQuery源码分析系列(37) : Ajax 总结

综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery. ajaxTransport 类型转换器 ajaxConvert 为了整体性与扩展性考虑,把整个结构通过Deferred实现异步链式模型,Promise对象可以轻易的绑定成功.失败.进行中三种状态的回调函数,然后通过在状态码在来回调不同的函数就行了 出于同源策略考虑,存在跨域问题,所以ajax内部

jQuery源码分析系列(31) : Ajax deferred实现 - Aaron.

AJAX的底层实现都是浏览器提供的,所以任何基于api上面的框架或者库,都只是说对于功能的灵活与兼容维护性做出最优的扩展 ajax请求的流程: 1.通过 new XMLHttpRequest 或其它的形式(指IE)生成ajax的对象xhr. 2.通过xhr.open(type, url, async, username, password)的形式建立一个连接. 3.通过setRequestHeader设定xhr的请求头部(request header). 4.通过send(data)请求服务器端