jQuery源码解析之addClassremoveClasstoggleClass和hasClass

4次阅读

共计 4844 个字符,预计需要花费 13 分钟才能阅读完成。

一、$().addClass()
作用:
向目标元素添加一个或多个类名

源码:

    // 向目标元素添加一个或多个类名
    // 源码 8401 行
    addClass: function(value) {
      var classes, elem, cur, curValue, clazz, j, finalValue,
        i = 0;
      // 如果 addClass(value)的 value 是一个 function
      // 那么就通过 call 让目标元素 this 调用该 function
      if (isFunction( value) ) {return this.each( function( j) {// function(index,currentclass)
          // index 对应 j,作用是获取多个目标元素的下标;// currentClass 对应 getClass(this),作用是获取当前元素的类名,方便加空格
          jQuery(this).addClass(value.call( this, j, getClass( this) ) );
        } );
      }
      // 将(多个)类名转为数组形式
      classes = classesToArray(value);
      if (classes.length) {
        // 多个目标元素
        while (( elem = this[ i++] ) ) {
          // 获取当前值
          curValue = getClass(elem);
          // 如果目标元素是元素节点并且用空格左右包起来 ""+value+" "cur = elem.nodeType === 1 && (" "+ stripAndCollapse( curValue) +" " );

          if (cur) {
            j = 0;
            // 一个个类名
            while (( clazz = classes[ j++] ) ) {
              // 当前元素没有和要添加的类名重复的话就添加
              if (cur.indexOf( "" + clazz +" ") < 0 ) {cur += clazz + " ";}
            }
            // 最后,确认经过处理后的类名集合是否和处理前的类名集合相同
            // 如果相同,就 setAttribute,重新渲染,否则不重新渲染(性能优化)// Only assign if different to avoid unneeded rendering.
            finalValue = stripAndCollapse(cur);
            if (curValue !== finalValue) {
              // 最后通过 setAttribute,添加类名
              elem.setAttribute("class", finalValue);
            }
          }
        }
      }
      return this;
    },

解析:
(1)getClass()
作用:
获取目标元素的类名

源码:

  // 源码 8377 行
  function getClass(elem) {return elem.getAttribute && elem.getAttribute( "class") || "";
  }

(2)classesToArray
作用:
将(多个)类名转为数组形式

源码:

  // 源码 8382 行
  function classesToArray(value) {
    // 元素的 className 如果有多个类名的话,是以数组形式保存的,那就直接返回
    if (Array.isArray( value) ) {return value;}
    // 如果元素类名是 string 类型的话
    if (typeof value === "string") {return value.match( rnothtmlwhite) || [];}
    return [];}

(3)stripAndCollapse
作用:
将 vaues 以空格分开,再以空格拼接

源码:

  // 源码 8370 行
  // Strip and collapse whitespace according to HTML spec
  // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
  function stripAndCollapse(value) {var tokens = value.match( rnothtmlwhite) || [];
    return tokens.join(" ");
  }

综上:
可以看到 addClass() 的思路是:
① 获取元素当前类名集合 a
② 如果要添加的类名 b 不重复,则将 b 添加进 a 里
③ 最后使用elem.setAttribute("class",a) 完成

二、$().removeClass
作用:
移除类,如果没有参数,则移除目标元素所有类名

源码:

    // 源码 8449 行
    removeClass: function(value) {
      var classes, elem, cur, curValue, clazz, j, finalValue,
        i = 0;
      // 作用同上
      if (isFunction( value) ) {return this.each( function( j) {jQuery( this).removeClass(value.call( this, j, getClass( this) ) );
        } );
      }
      // 如果没有规定参数,则删除所有类
      if (!arguments.length) {return this.attr( "class", "");
      }
      // 将(多个)类名转为数组形式
      classes = classesToArray(value);

      if (classes.length) {while ( ( elem = this[ i++] ) ) {curValue = getClass( elem);

          // This expression is here for better compressibility (see addClass)
          cur = elem.nodeType === 1 && ("" + stripAndCollapse( curValue) +" " );

          if (cur) {
            j = 0;
            while (( clazz = classes[ j++] ) ) {
              // 如果当前元素的类名里有要移除的类,// 就将该类替换成 " "
              // Remove *all* instances
              while (cur.indexOf( "" + clazz +" ") > -1 ) {cur = cur.replace( "" + clazz +" "," ");
              }
            }
            // 同上
            // Only assign if different to avoid unneeded rendering.
            finalValue = stripAndCollapse(cur);
            if (curValue !== finalValue) {elem.setAttribute( "class", finalValue);
            }
          }
        }
      }
      return this;
    },

可以看到 addClass() 的思路是:
① 获取元素当前类名集合 a
② 如果要移除的类名 b 不重复,则将 a 里面的 b 替换成空格 ” “
③ 最后使用 elem.setAttribute("class",a) 完成移除类名

三、$().toggleClass
作用:
切换类

源码:

    //stateVal 为 true,则添加类,false 则移除类
    // 源码 8497 行
    toggleClass: function(value, stateVal) {
      var type = typeof value,
        // 如果 value 是 string 类型或者是数组类型的话,为 true,反之为 false
        isValidValue = type === "string" || Array.isArray(value);
      //true 添加类,false 移除类
      if (typeof stateVal === "boolean" && isValidValue) {return stateVal ? this.addClass( value) : this.removeClass(value);
      }
      // 同上
      if (isFunction( value) ) {return this.each( function( i) {jQuery( this).toggleClass(value.call( this, i, getClass( this), stateVal ),
            stateVal
          );
        } );
      }

      return this.each(function() {
        var className, i, self, classNames;

        if (isValidValue) {

          // Toggle individual class names
          i = 0;
          self = jQuery(this);
          classNames = classesToArray(value);

          while (( className = classNames[ i++] ) ) {
            // 如果目标元素已经有要 toggle 的 className,那么就移除它
            // Check each className given, space separated list
            if (self.hasClass( className) ) {self.removeClass( className);
            } else {
              // 否则就添加类
              self.addClass(className);
            }
          }

          // Toggle whole class name
        }
        // 如果 $.toggleClass()没有值或者该值为布尔值
        else if (value === undefined || type === "boolean") {className = getClass( this);
          // 如果目标元素有类的话,就先用__className__属性保存类名
          if (className) {

            // Store className if set
            dataPriv.set(this, "__className__", 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.
          // 如果目标元素存在 setAttribute 的方法话
          if (this.setAttribute) {
            // 如果已有类名 /value=false,则移除所有类名
            // 如果没有类名并且 value=true,// 则从 dataPriv 中重新获取之前保存过的__className__当做目标元素的类名
            this.setAttribute( "class",
              className || value === false ?
                "" :
                dataPriv.get(this, "__className__") || ""
            );
          }
        }
      } );
    },

解析:
主要是两个 if
(1)if (typeof stateVal === “boolean” && isValidValue)
这个就是$().toggleClass(value,truefalse) 的第二个参数的作用了,
true 即 addClass(),false 即 removeClass()

(2)if(isValidValue)
这个是只有一个参数的情况
利用 hasClass 判断是否 add/removeClass

(3)如果 $.toggleClass()没有值或者第一个值为 true 的话
如果目标元素有类名的话,就使用 dataPriv 来保存类名,
如果目标元素有 setAttribute 的话,则将 className 设置为 dataPriv 里保存的值。

四、$().hasClass
作用:
检查目标元素是否包含指定的类

源码:

    // 源码 8568 行
    hasClass: function(selector) {
      var className, elem,
        i = 0;

      className = "" + selector +" ";
      while (( elem = this[ i++] ) ) {
        if ( elem.nodeType === 1 &&
          // 关键代码
          ("" + stripAndCollapse( getClass( elem) ) +" " ).indexOf(className) > -1 ) {return true;}
      }

      return false;
    }

这个代码还挺简单的,就不解析了。


(完)

正文完
 0