jQuery源码 - 扩展一些工具方法

概述

// 工具方法不仅可以给jQuery对象来用,也可以给原生js来用
// 是jQuery底层最基本的架构
// 调用了之前写的jQuery.extend() 扩展jQuery 静态方法
// jQuery.extend({a: 'XXX', b: 'XXX'});
expando: 生成唯一JQ字符串(内部)
noConflict(): 防止冲突
isReady: DOM是否加载完(内部)
readyWait: 等待多少文件的计数器(内部)
holdReady(): 推迟DOM触发
ready(): 准备DOM触发
isFunction(): 是否为函数
isArray(): 是否为数组
isWindow(): 是否为window
isNumeric(): 是否为数字
type(): 判断数据类型
isPlainObject(): 是否为对象自变量
isEmptyObject(): 是否为空的对象
error(): 抛出异常
parseHTML(): 解析节点
parseJSON(): 解析JSON
parseXML(): 解析XML
noop(): 空函数
globalEval(): 全局解析JS
camelCase(): 转驼峰
nodeName(): 是否为指定节点名(内部)
each(): 遍历集合
trim(): 去前后空格
makeArray(): 类数组转真数组
inArray(): 数组版indexOf
merge(): 合并数组
grep(): 过滤新数组
map(): 映射新数组
guid: 唯一标识符(内部)
proxy():this指向
access(): 多功能值操作(内部)
now(): 当前时间
swap(): CSS交换(内部)

方法详解

  • expando: 生成唯一字符串(内部)
    // expando
    // "jQuery" + 版本号 + 随机小数 通过replace替换为空
    expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" )
    
  • noConflict: 防止冲突
    noConflict: function( deep ) {
      if ( window.$ === jQuery ) {
        window.$ = _$;
      }
      // 如果可以调用这个得写成
      // var miaov = $.noConflict(true);
      // 如果不写为true,会出错
      if ( deep && window.jQuery === jQuery ) {
        window.jQuery = _jQuery;
      }
      // 要返回jQuery对象,要不jQuery对象就无法调用了
      return jQuery;
    }
    
    /*
    	如果在外边定义过$变量,将jquery库引入的时候,因为是立即执行函数,会立即执行里面的代码,当执行到最后的时候,会更改$变量,所以在最开始,先用_jQuery和_$变量将之前的$或jQuery保存下来,如果需要之前的变量,直接调用这个函数就可以返回去,同时这个函数返回的是jQuery对象
    */
    var $ = 123;
    <script src="jquery-2.0.3.js"></script>
    _jQuery = window.jQuery,
    _$ = window.$,  //123
    
    <script>var $ = 123;</script>
    <script src="jquery-2.0.3.js"></script>
    <script>
    var miaov = $.noConflict();
    miaov(function(){
      alert($);  // 这里的输出也是123,这里就用到了if判断
    });
    </script>
    
  • jQuery中有关DOM加载的代码
  • isFunction判断是否为函数
    // 在低版本浏览器中,typeof 和 下面的 type方法会返回Object 而不是function
    isFunction: function( obj ) {
      return jQuery.type(obj) === "function";
    }
    
    // 适应各种情况的isFunction
    function isFunction(fn){
      if(!fn)
        return false;
      var s = "toString",
          v = "valueOf",
          t = typeof fn[s] === "function" && fn[s],
          o = typeof fn[v] === "function" && fn[v],
          r;
      if(t)
          delete fn[s];
      if(o)
          delete fn[v];
      r = typeof fn !== "string" && !(fn instanceof String) && !fn.nodeName && fn.constructor != Array && /^[\s[]?function/.test(fn + "");
      if(t)
        fn[s] = t;
      if(o)
        fn[v] = o;
      return r;
    }
    
  • isArray是否是数组
    // 在ECMA5中已经有这个方法了
    isArray: Array.isArray
    
  • isWindow是否是窗口
    isWindow: function( obj ) {
      // 只有window对象具有window属性
      return obj != null && obj === obj.window;
    }
    
    alert($.isWindow(window))
    
    // 除了null和undefined之外任何跟null比较都会返回false
    alert(null == null) // true
    alert(undefined == null) //true
    
    // window可以充当全局的对象
    var a = 10;
    window.a = 10;
    //window可以当浏览器窗口
    window.open();
    
  • isNumeric 是否是数字值
    // isNumeric 判断是不是数字类型
    // parseFloat(obj) 只要可以转换的,肯定不是NaN
    //                 转换不了的,肯定都是NaN
    // 这个数字是否在计算机的计算能力范围之内 isFinite( obj )
    isNumeric: function( obj ) {
      return !isNaN( parseFloat(obj) ) && isFinite( obj );
    }
    
  • type判断数据类型
    type: function( obj ) {
      if ( obj == null ) {
      	// null undefined
        return String( obj );
      }
      // 如果是在火狐5.1以下用typeof来判断正则,会返回function ;高版本会返回object
      // Support: Safari <= 5.1 (functionish RegExp)
      // class2type = {}
      // core_toString = class2type.toString;
      return typeof obj === "object" || typeof obj === "function" ?
        class2type[ core_toString.call(obj) ] || "object" :
        typeof obj;
    }
    
    var a = 'hello';
    alert($.type(a));
    
    alert( {}.toString.call([]) )   // [object Array]
    alert( {}.toString.call(new Date) )   // [object Data]
    
    jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
      class2type[ "[object " + name + "]" ] = name.toLowerCase();
    });
    
  • isPlainObject判断是否为对象自变量
    // isPlainObject 判断是否为对象自变量
    isPlainObject: function( obj ) {
      // nodeType是结点的类型,因为如果把一个DOM结点放到type类型中,会返回object;window放到type当中,也会返回object
      if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
        return false;
      }
    
      try {
        // class2type = {}
        // hasOwnProperty 是查看这个属性是否在这个对象身上
        // core_hasOwn = class2type.hasOwnProperty
        // isPrototypeOf 是object对象所特有的,检查这个属性就可以看出是否是object对象
        // window.location 如果频繁调用的话,在Firefox < 20 以下的版本会出现内存泄漏的问题,所以需要用try..catch
        if ( obj.constructor &&
            !core_hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
          return false;
        }
      } catch ( e ) {
        return false;
      }
    
      return true;
    }
    
  • isEmptyObject 是否是空对象
    isEmptyObject: function( obj ) {
      var name;
    
      for ( name in obj ) {
        return false;
      }
      return true;
    }
    
    function Aaa(){}
    var obj = []        // true
    var obj = {}        // true
    var ob = new Aaa(); // true
    
  • error抛出异常
    error: function( msg ) {
      throw new Error( msg );
    }
    
    $.error('这是错误');
    
  • parseHTML 解析节点
    // parseHTML 解析结点
    // 解析data,将data添加到document中,但没有建立关系,返回节点数组,可以自己建立关系
    parseHTML: function( data, context, keepScripts ) {
      if ( !data || typeof data !== "string" ) {
        return null;
      }
      if ( typeof context === "boolean" ) {
        keepScripts = context;
        context = false;
      }
      context = context || document;
    
      // //如果不是单个标签那么parsed就是null,所谓的单个标签就是<div/>或者<div></div>但是<div>hello</div>不满足!
      var parsed = rsingleTag.exec( data ),
        // 判断是否要复制script标签
        // 如果keepScripts是false,将script标签全部复制到script数组中,最后从DOM中移除
        // 如果keepScripts是true,script是false,不需要将script复制到数组中
        scripts = !keepScripts && [];
    
      // Single tag 单标签的创建
      if ( parsed ) {
        //如果是单个标签就调用相应的createElement方法,默认上下文是document!
        return [ context.createElement( parsed[1] ) ];
      }
    
      // 多标签的创建
      // 如果需要创建script标签 scripts为[]  不需要false
      parsed = jQuery.buildFragment( [ data ], context, scripts );
    
      // 到这里scripts里面会有script标签
      if ( scripts ) {
        // 将DOM中的script数组里的东西移除
        jQuery( scripts ).remove();
      }
    
      // 最后得到的是DOM结点,把它转成数组
      return jQuery.merge( [], parsed.childNodes );
    },
    
    var str = '<li></li><li></li>';
    console.log($.parseHTML(str)); // [li, li]
    // document 指定的根节点
    // boolean 是否会储存script标签 str = '<li></li><li></li><script></script>';
    console.log($.parseHTML(str, document, true)); // [li, li, script]
    
  • parseXML解析XML
    parseXML: function( data ) {
      var xml, tmp;
      if ( !data || typeof data !== "string" ) {
        return null;
      }
    
      // Support: IE9
      // 用DOMParse去解析XML文档的时候,前提是data必须是完整的XML,也不能去解析HTML,所以需要try..catch(在IE9下才会报错)
      try {
        // 在IE6和IE7中没有这个方法,应该怎么办呢?
        // ActiveXObject也可以做低版本的XML解析
        tmp = new DOMParser();
        xml = tmp.parseFromString( data , "text/xml" );
      } catch ( e ) {
        xml = undefined;
      }
    
      // 如果其他浏览器,上面parseFromString不会报错,但是会创建一个parserror的标签
      if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
        jQuery.error( "Invalid XML: " + data );
      }
      return xml;
    }
    
    parseXML: function(data, xml, tmp) {
    
        if (window.DOMParser) { //标准 IE9可以
            tmp = new DOMParser();
            xml = tmp.parseFromString(data, "text/xml");
        } else { // IE 
            xml = new ActiveXObject("Microsoft.XMLDOM");
            xml.async = "false";
            xml.loadXML(data);
        }
    
        tmp = xml.documentElement;
    
        if (!tmp || !tmp.nodeName || tmp.nodeName === "parsererror") {
            jQuery.error("Invalid XML: " + data);
        }
    
        return xml;
    }
    
  • noop返回一个空函数
    // noop 返回一个空函数
    // 在做插件或做组件开发的时候,经常会写一些默认参数,默认参数有可能就是一个函数
    noop: function() {}
    
  • globalEval全局解析JS
    globalEval: function( code ) {
      var script,
          // 先把eval存成indirect
          indirect = eval;
      // 用trim可以把前后空格去一下
      code = jQuery.trim( code );
    
      if ( code ) {
        // 在严格模式下,是不支持eval解析的
        if ( code.indexOf("use strict") === 1 ) {
          script = document.createElement("script");
          script.text = code;
          // 大概就是先通过创建script标签,将变量添加到全局,之后再删除这个标签
          document.head.appendChild( script ).parentNode.removeChild( script );
        } else {
          // 正常情况下,就通过eval解析就可以了
          indirect( code );
        }
      }
    }
    
    function test() {
      jQuery.globalEval("var newVar = true");
    }
    test();
    
    function test() {
      eval('var a = 1'); // 通过eval可以解析里面的内容
      alert(a);    // 在这里可以进行输出
    } 
    test();
    alert(a); // 在这里看不到里面的内容 如果是window.eval('var a = 1'); 就可以正常输出了
    
    function test() {
      // 这样写也是可以的
      var val = eval;
      val('var a = 1'); 
      alert(a);    
    } 
    test();
    
  • camelCase转驼峰的方法
    // camelCase 是转驼峰的方法
    // 把margin-top -> marginTop
    camelCase: function( string ) {
      // rmsPrefix = /^-ms-/
      // 将-ms-transform -> ms-Transform
      // replace 是将(-字符)这个组合转化成大写字符
      // rdashAlpha = /-([\da-z])/gi
      /*
        fcamelCase = function( all, letter ) {
          return letter.toUpperCase();
        }
      */
      return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
    }
    
    fcamelCase = function( all, letter ) {
      return letter.toUpperCase();
    }
    
    // 在IE浏览器下的方法
    -ms-transform -> msTransform
    // 在其它浏览器下得大写
    -moz-transform -> MozTransform
    
  • nodeName是否为指定节点名(内部)
    nodeName: function( elem, name ) {
      return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
    }
    
    $.nodeName(document.documentElement, 'html'); // true
    $.nodeName(document.body, 'html'); // false
    $.nodeName(document.body, 'body'); // body
    
  • each 遍历数组
    each: function( obj, callback, args ) {
      var value,
        i = 0,
        length = obj.length,
        isArray = isArraylike( obj );
    
      // 判断是不是内部使用的
      if ( args ) {
        // 如果是数组
        if ( isArray ) {
          for ( ; i < length; i++ ) {
            // 参数数量不固定,使用apply
            value = callback.apply( obj[ i ], args );
    
            if ( value === false ) {
              break;
            }
          }
        } else {
          // 如果是JSON
          for ( i in obj ) {
            value = callback.apply( obj[ i ], args );
    
            if ( value === false ) {
              break;
            }
          }
        }
    
      // 如果是外部使用的
      } else {
        if ( isArray ) {
          for ( ; i < length; i++ ) {
            // 参数数量固定,使用call
            // 第一个参数是谁调用callback,后面的是传入callback中的参数
            value = callback.call( obj[ i ], i, obj[ i ] );
    
            if ( value === false ) {
              break;
            }
          }
        } else {
          for ( i in obj ) {
            value = callback.call( obj[ i ], i, obj[ i ] );
    
            if ( value === false ) {
              break;
            }
          }
        }
      }
    
      return obj;
    }
    
  • isArraylike是否是数组或是类数组
    /**
     * 三种情况下返回true:
     *  1. length == 0
     *  2. jQuery.type(obj) == 'Array';
     *  3. length === "number" && length > 0 && ( length - 1 ) in obj 
     * 
     * document.getElementById("xx")返回的nodeType是1,但是没有length属性
     * document.getElementsByTagName("div")的集合有length属性,但是没有nodeType,返回的是htmlCollection集合
     * 
     * 什么时候nodeType是1,同时也具有length属性呢?
     *  isArraylike($("#content")) //打印true
     */
    function isArraylike( obj ) {
      // length得为0或number类型
      var length = obj.length,
        type = jQuery.type( obj );
    
      if ( jQuery.isWindow( obj ) ) {
        return false;
      }
    
      //
      if ( obj.nodeType === 1 && length ) {
        return true;
      }
    
      return (type === "array") || (type !== "function" &&
        // 特殊的JSON或者arguments 类数组
        ( length === 0 ||
        // 如果当length = 0的时候,-1 in obj 是错误的语法格式,所以得先判断lenght === 0
        typeof length === "number" && length > 0 && ( length - 1 ) in obj ));
    }
    

    这里之所以要判断(length-1) in obj是引入了稀疏数组的概念,稀疏数组在用forEach或者map迭代的时候会跳过空隙(这两个方法第一个参数是元素,第二个参数是下标,第三个是调用数组),也就是要确保数组的最后一个元素是存在的!如果要创建密集数组可以通过Array.apply(arr,Array(3))的方式,于是forEach和map就不会跳过其中的元素了

  • trim去掉前后空格
    // trim 去掉前后空格
    trim: function( text ) {
      // core_version = "2.0.3"
      // core_trim = core_version.trim
      return text == null ? "" : core_trim.call( text );
    }
    
  • makeArray类数组转化成数组
    // makeArray() 类数组转化为真数组
    // core_deletedIds = []
    // core_push = core_deletedIds.push
    makeArray: function( arr, results ) {
      // 首先看有没有特殊的JSON
      var ret = results || [];
    
      // 判断要转化为数组的东西是否为空
      if ( arr != null ) {
        // isArraylike 只能针对的是对象,如果是123之类,得先转换成Object,数字调用的就是else了
        // 如果是字符串,object(xxx),转为JSON,走if
        // 字符串,数组,类数组都走这里
        if ( isArraylike( Object(arr) ) ) {
          // 内部转换,使用下面这个
          // 通过merge进行合并
          jQuery.merge( ret,
            typeof arr === "string" ?
            [ arr ] : arr
          );
        } else {
          // 普通使用,直接使用数组的push方法
          // 将数字(111)push到数组中
          core_push.call( ret, arr );
        }
      }
    
      return ret;
    }
    
    // $.makeArray("xxx",[1,2])  [1,2,xxx]
    /**
     * var obj={
        0:"xx",
        1:"female",
        length:2
      }
      var arr=[1,2,3];
      $.merge(arr,obj);   [1, 2, 3, xx, female]
    */
    /**
     * var obj = {0:"xxx", 1:"yy", length:2};
       var result = $.makeArray("sex", obj);    [xxx, yy, sex]
    */
    
  • inArray 数组版的indexOf
     inArray: function( elem, arr, i ) {
      return arr == null ? -1 : core_indexOf.call( arr, elem, i );
    }
    
    var arr = ['a', 'b', 'c', 'd'];
    $.inArray('b', arr);  // 1
    $.inArray('e', arr);  // -1
    
  • merge合并数组
    // merge 合并数组
    // 对外就是只针对数组
    // 对内就是转换成特殊形式的JSON
    merge: function( first, second ) {
      var l = second.length,
        i = first.length,
        j = 0;
    
      // $.merge(['a', 'b'], ['c', 'd']);
      if ( typeof l === "number" ) { 
        for ( ; j < l; j++ ) {
          first[ i++ ] = second[ j ];
        }
      // $.merge(['a', 'b'], {0: 'c', 1: 'd'});
      // $.merge({0: 'c', 1: 'd', length: 2}, {0: 'c', 1: 'd'});
      } else {
        while ( second[j] !== undefined ) {
          first[ i++ ] = second[ j++ ];
        }
      }
    
      first.length = i;
    
      return first;
    }
    
  • grep 过滤新数组
    // grep 过滤新数组
    grep: function( elems, callback, inv ) {
      var retVal,
        ret = [],
        i = 0,
        length = elems.length;
      // 如果不写这个参数(inv),得到的结果是undefined,可以转化成false
      inv = !!inv;
    
      for ( ; i < length; i++ ) {
        retVal = !!callback( elems[ i ], i );
        if ( inv !== retVal ) {
          ret.push( elems[ i ] );
        }
      }
    
      return ret;
    }
    
    var arr = [1, 2, 3, 4];
    arr = $.grep(arr, function(n, i){
      return n > 2;
    })
    console.log(arr); // [3, 4]
    
    // 过滤到相反的方式
    var arr = [1, 2, 3, 4];
    arr = $.grep(arr, function(n, i){
      return n > 2;
    }, true)
    console.log(arr); // [1, 2]
    
  • map 映射新数组
    // map 映射新数组
    // 最后一个参数 arg 是内部使用的
    map: function( elems, callback, arg ) {
      var value,
        i = 0,
        length = elems.length,
        isArray = isArraylike( elems ),
        ret = [];
    
      // 数组,类数组
      if ( isArray ) {
        for ( ; i < length; i++ ) {
          value = callback( elems[ i ], i, arg );
    
          if ( value != null ) {
            ret[ ret.length ] = value;
          }
        }
    
      // JSON
      } else {
        for ( i in elems ) {
          value = callback( elems[ i ], i, arg );
    
          if ( value != null ) {
            ret[ ret.length ] = value;
          }
        }
      }
    
      // 不想得到复合数组,只想得到单一的数组
      // core_deletedIds = []
      // core_concat = core_deletedIds.concat
      /*
      var arr = [1, 2, 3, 4];
      arr = $.map(arr, function(n){
        return [n + 1];   // [2, 3, 4, 5]
      });
      如果不使用下面这句话 [[2], [3], [4], [5]]
      */
      return core_concat.apply( [], ret );
    }
    
    var arr = [1, 2, 3, 4];
    arr = $.map(arr, function(n){
      return n + 1;
    });
    
  • guid: 1唯一标识符(内部使用)
    // guid: 1 唯一标识符(内部使用)
    // 通过标识将函数和标识建立一个关系,所以可以取消绑定
    function show(){
      alert(this);
    }
    //下面这两种情况都可以取消绑定
    //$('input:eq(0)').click(show, window); 这样的话,show就不是一个时间函数了,而是一个普通函数,绑定到了window身上,同时弹出 [object window]
    $('input:eq(0)').click(show); // [object HTMLInputElement]
    $('input:eq(1)').click(function(){
      $('input:eq(0)').off();
    });
    
    // <input type="button" value="点击">
    // <input type="button" value="取消绑定">
    
  • proxy改变this指向
    // proxy 改变this指向
    
    function show(){
      alert(this);
    }
    
    // 第一个是你想要调用的函数,第二个参数是你想让this指向谁
    $.proxy(show, document)();
    
    function show(n1, n2){
      alert(this);
    }
    // 在外面传参可以在触发函数的时候传参
    $.proxy(show, document)(3, 4);
    // 在里面传参可以在不触发函数的时候传参
    $.proxy(show, document, 3, 4)();
    $.proxy(show, document, 3)(4);
    
    proxy: function( fn, context ) {
      var tmp, args, proxy;
    
      // 如果指向是一个字符串
      /*
      var obj = {
        show: function(){
          alert(this);
        }
      };
    
      $(document).click($.proxy(obj.show, obj));
      //简写方式
      $(document).click($.proxy(obj, 'show'));
      */
      if ( typeof context === "string" ) {
        // tmp = obj[show]
        tmp = fn[ context ];
        // context = obj
        context = fn;
        // fn = obj[show]
        fn = tmp; 
      }
    
      if ( !jQuery.isFunction( fn ) ) {
        return undefined;
      }
    
      // $.proxy(show, document, 3)(4);
      // arguments: show, document, 3
      // 截取2之后的 3
      args = core_slice.call( arguments, 2 );
      proxy = function() {
        // arguments是一种类数组,不是真正的数组,所以得通过 core_slice.call( arguments ) 转化成真正的数组 4
        return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
      };
    
      // 设置唯一的标识,在后期有用
      proxy.guid = fn.guid = fn.guid || jQuery.guid++;
    
      return proxy;
    }
    
    
  • now: Date.now
    Date.now 的性能比 (new Date()).getTime() 要好
    
  • swapcss交换
    // swap css交换(内部)
    
    // jQuery方法,可以获取到隐藏元素的宽
    // 如何获取的呢?
    // 首先储存 display: none
    // 再把 display: none 换成 display: block; visibility: hidden; position: absolute
    // 获取到值
    // 再把 display: block; visibility: hidden; position: absolute 换成 display: none
    // <div id="div1" style="display: block; visibility: hidden; position: absolute">aaaa</div>
    $('#div1').width()
    
    // 原生方法,获取不到隐藏元素的宽
    $('#div1').get(0).offsetWidth
    
    <div id="div1" style="width:100px; height:100px; background:red; display: none">aaaa</div>
    
    
    swap: function( elem, options, callback, args ) {
       var ret, name,
         old = {};
    
       // Remember the old values, and insert the new ones
       for ( name in options ) {
         old[ name ] = elem.style[ name ];
         elem.style[ name ] = options[ name ];
       }
    
       ret = callback.apply( elem, args || [] );
    
       // Revert the old values
       for ( name in options ) {
         elem.style[ name ] = old[ name ];
       }
    
       return ret;
    }
    
  • access多功能值操作
    $(function(){
      // 获取值
      var a = $('#div1').css('width');
      // 设置值
      $('#div1').css('background', 'yellow');
      $('#div1').css({ background: 'yellow', width: '300px' });
    });
    <div id="div1" style="width:100px; height:100px; background:red">aaaa</div>
    
    
    // elems  是操作的元素
    // fn 回调函数 有区别的地方,就在回调函数当中做进一步处理
    // key 就是background之类
    // value 就是red之类
    // chainable 为false是获取 为true是设置
    
    access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
      var i = 0,
        length = elems.length,
        // 如果key的值为空,bulk为true
        bulk = key == null;
    
      // 设置多组值
      // $('#div1').css({ background: 'yellow', width: '300px' });
      if ( jQuery.type( key ) === "object" ) {
        chainable = true;
        for ( i in key ) {
          // 递归设置
          jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
        }
    
      // 设置一组值的情况下
      // $('#div1').css('background', 'yellow');
      } else if ( value !== undefined ) {
        chainable = true;
    
        if ( !jQuery.isFunction( value ) ) {
          raw = true;
        }
    
        // 如果key的值为空
        if ( bulk ) {
          // 如果value的值是字符串
          if ( raw ) {
            fn.call( elems, value );
            fn = null;
    
          // 如果value的值是函数
          } else {
            bulk = fn;
            fn = function( elem, key, value ) {
              return bulk.call( jQuery( elem ), value );
            };
          }
        }
    
        if ( fn ) {
          for ( ; i < length; i++ ) {
            fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
          }
        }
      }
    
      return chainable ?
        elems :
    
        // Gets
        bulk ?
          fn.call( elems ) :
          length ? fn( elems[0], key ) : emptyGet;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值