underScore专题-源码分析mixin和链式调用

开头

源码的开头部分是一个立即执行函数, 第一个参数是上下文对象,第二个参数是一个函数,主要代码在这个函数里面。立即执行函数主要是支持多种模式导出模块。

(function(global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
        typeof define === 'function' && define.amd ? define('underscore', factory) :
            (function() {
                var current = global._;
                var exports = factory();
                global._ = exports;
                exports.noConflict = function() { global._ = current; return exports; };
            })();
}(this, (function() {...}))

typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :''

typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :''

这里判断是不是node环境,如果exports是一个对象并且module存在,CommonJs的导出模块方法:module.exports = factory()。暴露出了一个函数的调用,可以通过这样的方式使用_(obj),factory返回的也是一个函数。可以const _ = require(''underscore);这样来引入,这是通过npm install underscore安装后,node环境可以这样来引入。

typeof define === 'function' && define.amd ? define('underscore', factory) : ''

 RequireJS定义了一个define函数,用来定义模块

define([id], [dependencies], factory)

参数

  • id:可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
  • dependencies:可选,字符串数组,AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执行具体的factory方法前解决
  • factory:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值

如果使用的是RequireJS规范,define是一个函数并且define.amd存在,就需要用define函数来定义模块,导出的模块就是factory函数返回的内容。可以在require的回调函数中访问underscore。

require(['underscore'], function(underscore){})

 如果即不是CommonJs规范,又不是RequireJS规范,这里是一个立即执行函数,这里主要是把underscore挂在window上,并解决命名冲突。如果页面中已经定义了_这个变量,这里会重写,将_这个变量释放给underscore使用。并且给underscore上面扩展了一个方法,调用这个方法就可以重新给underscore起一个名字,将window上原来的_变量,还给window。

(function() {
                var current = global._;
                var exports = factory();
                global._ = exports;
                exports.noConflict = function() { global._ = current; return exports; };
            })();

举例:注意引入顺序

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>

<body>

</body>
<script>
    var _ = function() {
        console.log(1)
    }
</script>
<script src="underScore.js"></script>
<script>
    const newUnderScore = _.noConflict()
    _(); // 1
</script>

</html>

如果不调用 _.noConflict(),_()将不会打印1。

主要内容

主要内容都在factory中,这个函数中最上面的是下面内容:

var root = typeof self == 'object' && self.self === self && self ||
        typeof global == 'object' && global.global === global && global ||
        Function('return this')() ||
        {};

这里主要目的是将_挂在全局对象上,浏览器环境的全局对象是window,node环境的全局对象是global,但是这里并没有出现window。浏览器内打印一下self,可以看到self指向window。打印一下window.self。

可以看到两者都指向window,self.self === self 也就是说window.self === self。那为什么使用self不是window呢?

是因为Web Worker,在 Web Worker 标准中,定义了解决客户端 JavaScript 无法多线程的问题。其中定义的 “worker” 是指执行代码的并行过程。不过,Web Worker 处在一个自包含的执行环境中,无法访问 Window 对象和 Document 对象,和主线程之间的通信业只能通过异步消息传递机制来实现。虽然在 Web Worker 中不能访问到 Window 对象,但是我们却能通过 self 访问到 Worker 环境中的全局对象。所以完全可以挂到 self 中。

 Function('return this')() 

上面这句代码又是什么意思呢?浏览器中返回:

上面的代码相当于是这样的,一个立即执行函数,return this。this就是全局对象。那为什么不直接使用下面的代码呢?

(function(){return this})()

 来测试一下

console.log( window === function(){
  return (function(){return this})();
}() ); // true

console.log( window === function(){
  "use strict";
  return (function(){return this})();
}() ); // false

console.log( window === function(){
  return Function('return this')();
}() ); // true

console.log( window === function(){
  "use strict";
  return Function('return this')();
}() ); // true

区别就在这里,用下面这种方式,无论是严格模式还是非严格模式,拿到的都是全局对象。

Function('return this')();

微信小程序中,即不能访问window, 也没有global和self,又强制严格模式:

console.log(this) // undefined
console.log(Function('return this')()) // {}

为了防止有些环境中没有全局对象导致root为undefined的报错,最后给root一个{}。

函数式编程

接下来会看到很多个这样的函数,这是第一个函数,也是underscore的主函数,_(obj)调用的就是这个函数,这个函数返回的是一个实例,为什么要返回一个实例呢?

 function _(obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
    }

 看一下unique方法的使用

var arr = [2, 4, 3, 4, 2, 3, 4]
console.log(_(arr).unique()) // [2, 4, 3]
console.log(_.unique(arr)) // [2, 4, 3]

一个是函数式调用,一个是面向对象调用,结果都是一样的,怎么实现的呢?


_()返回的是一个实例,_的原型上有 unique方法,所以每个实例都可以使用,_.unique()是_的静态方法,说明这两个方法是同一个方法,不只有这个这一个,很多方法都可以这样来使用,也就是说underscore原型上方法,与函数静态方法共享。_()调用返回实例也就可以理解了。

实现方法:

先省略中间定义的方法和属性如:

    var VERSION = _.VERSION = '1.10.2';
    .
    .
    .
   // sparse array-likes as if they were dense.
    function each(obj, iteratee, context) {
        iteratee = optimizeCb(iteratee, context);
        var i, length;
        if (isArrayLike(obj)) {
            for (i = 0, length = obj.length; i < length; i++) {
                iteratee(obj[i], i, obj);
            }
        } else {
            var _keys = keys(obj);
            for (i = 0, length = _keys.length; i < length; i++) {
                iteratee(obj[_keys[i]], _keys[i], obj);
            }
        }
        return obj;
    }
    .
    .
    .

直接到最后看下factory返回了什么?

 var allExports = ({
        'default': _,
        VERSION: VERSION,
        iteratee: iteratee,
        restArguments: restArguments,
        each: each,
        forEach: each,
        map: map,
        collect: map,
        reduce: reduce,
        foldl: reduce,
        inject: reduce,
        reduceRight: reduceRight,
        foldr: reduceRight,
        find: find,
        detect: find,
        filter: filter,
        select: filter,
        reject: reject,
        every: every,
        all: every,
        some: some,
        any: some,
        contains: contains,
        includes: contains,
        include: contains,
        invoke: invoke,
        pluck: pluck,
        where: where,
        findWhere: findWhere,
        max: max,
        min: min,
        shuffle: shuffle,
        sample: sample,
        sortBy: sortBy,
        groupBy: groupBy,
        indexBy: indexBy,
        countBy: countBy,
        toArray: toArray,
        size: size,
        partition: partition,
        first: first,
        head: first,
        take: first,
        initial: initial,
        last: last,
        rest: rest,
        tail: rest,
        drop: rest,
        compact: compact,
        flatten: flatten,
        without: without,
        uniq: uniq,
        unique: uniq,
        union: union,
        intersection: intersection,
        difference: difference,
        unzip: unzip,
        zip: zip,
        object: object,
        findIndex: findIndex,
        findLastIndex: findLastIndex,
        sortedIndex: sortedIndex,
        indexOf: indexOf,
        lastIndexOf: lastIndexOf,
        range: range,
        chunk: chunk,
        bind: bind,
        partial: partial,
        bindAll: bindAll,
        memoize: memoize,
        delay: delay,
        defer: defer,
        throttle: throttle,
        debounce: debounce,
        wrap: wrap,
        negate: negate,
        compose: compose,
        after: after,
        before: before,
        once: once,
        keys: keys,
        allKeys: allKeys,
        values: values,
        mapObject: mapObject,
        pairs: pairs,
        invert: invert,
        functions: functions,
        methods: functions,
        extend: extend,
        extendOwn: extendOwn,
        assign: extendOwn,
        findKey: findKey,
        pick: pick,
        omit: omit,
        defaults: defaults,
        create: create,
        clone: clone,
        tap: tap,
        isMatch: isMatch,
        isEqual: isEqual,
        isEmpty: isEmpty,
        isElement: isElement,
        isArray: isArray,
        isObject: isObject,
        isArguments: isArguments,
        isFunction: isFunction,
        isString: isString,
        isNumber: isNumber,
        isDate: isDate,
        isRegExp: isRegExp,
        isError: isError,
        isSymbol: isSymbol,
        isMap: isMap,
        isWeakMap: isWeakMap,
        isSet: isSet,
        isWeakSet: isWeakSet,
        isFinite: isFinite,
        isNaN: isNaN,
        isBoolean: isBoolean,
        isNull: isNull,
        isUndefined: isUndefined,
        has: has,
        identity: identity,
        constant: constant,
        noop: noop,
        property: property,
        propertyOf: propertyOf,
        matcher: matcher,
        matches: matcher,
        times: times,
        random: random,
        now: now,
        escape: escape,
        unescape: unescape,
        result: result,
        uniqueId: uniqueId,
        templateSettings: templateSettings,
        template: template,
        chain: chain,
        mixin: mixin
    });

    // Add all of the Underscore functions to the wrapper object.
    var _$1 = mixin(allExports);
    // Legacy Node.js API
    _$1._ = _$1;

    return _$1;

_$1就是它暴露出来的方法,allExports上所有的属性和方法,都被扩展到了_$1的自身和原型上,而扩展的方法就是mixin,所以核心方法就是这个mixin。

mixin

源码中mixin方法返回了_,这个_已经把obj传递进来的属性和方法全部挂在了原型和自身上。

// Add your own custom functions to the Underscore object.
    function mixin(obj) {
        each(functions(obj), function(name) {
            var func = _[name] = obj[name];
            _.prototype[name] = function() {
                var args = [this._wrapped];
                push.apply(args, arguments);
                return chainResult(this, func.apply(_, args));
            };
        });
        return _;
    }

这里调用了each方法,functions方法和chainResult方法,先来看前两个

each

这里是一个简化的each方法,接受一个数组,和一个迭代器,迭代器接受数组成员和index。那么调用functions(obj)返回的应该就是一个数组。

 _.each = function(target, callbacks) {
        var key , i = 0;
        if(_.isArray(target)) {
            for(; i < target.length; i++) {
                callbacks.call(target, target[i], i);
            }
        } else {
            for(key in target) {
                callbacks.call(target, key, target[key])
            }
        }
    }

functions 

函数接受一个对象,返回一个数组,数组是对象的key,value是函数的key才会被加进来,所以原型和自身上扩展的是方法,过滤掉了属性。

// Return a sorted list of the function names available on the object.
    function functions(obj) {
        var names = [];
        for (var key in obj) {
            if (isFunction(obj[key])) names.push(key);
        }
        return names.sort();
    }

方法添加到自身: 

var func = _[name] = obj[name];

方法添加到了原型,这里为什么不直接像上面那样简单的写呢?

   _.prototype[name] = function() {
                var args = [this._wrapped];
                push.apply(args, arguments);
                return chainResult(this, func.apply(_, args));
            };

还记得上面的面向对象吗?这样调用直接把参数arr传递给了_,并没有给到unique()。而unique()内部却可以访问这个参数。

console.log(_(arr).unique()) // [2, 4, 3]

 _函数把参数存在了this._wrapped上面,可以通过这个拿到,但是有时又可以这样调用_.unique(arr)。所以我们还需要获取到arguments。

function _(obj) {
        if (obj instanceof _) return obj;
        if (!(this instanceof _)) return new _(obj);
        this._wrapped = obj;
    }

push.apply(args, arguments);在underscore中就是Array.prototype.push(args, arguments );

拿到参数并合并为数组。

function test() {
    console.log(arguments)
    var args = [undefined]
    Array.prototype.push.apply(args, arguments)
    console.log(args) // [ undefined, { a: 9 }, { b: 3 } ]
}

test({ a: 9 }, { b: 3 })

传递参数 

 return chainResult(this, func.apply(_, args));

 这里返回了一个函数调用的结果,使用的是链式调用

链式调用

 var arr = [2, 4, 3, 4, 2, 3, 4]
    var a = _.unique(arr);
    console.log(a) // [2,4,3]
  var a = _(arr).each(function(item, index) {
        console.log(item) // 2, 4, 3, 4, 2, 3, 4
    });

 链式操作:

var arr = [2, 4, 3, 4, 2, 3, 4]
    var a = _(arr).unique().each(function(item) {
        console.log(item)
    })
    console.log(a) 

报错了,说明不能直接使用链式掉用,需要用一个chain开启链式调用: 

var arr = [2, 4, 3, 4, 2, 3, 4]
    var a = _(arr).chain().unique().each(function(item) {
        console.log(item) // 2,4,3
    })
    console.log(a)

这里是a的打印结果: 返回的是一个实例

对比直接调用underScore返回的也是一个实例,但是没有_chain属性:

 var arr = [2, 4, 3, 4, 2, 3, 4]
    var a = _(arr)
    console.log(a)

如果这样来使用:可以看到返回的不再 是一个实例,而是一个数组,这种情况下肯定是不能进行链式调用的。

var arr = [2, 4, 3, 4, 2, 3, 4]
    var a = _.unique(arr);
    console.log(a) // [2,4,3]

由上面分析得出,之所以可以进行链式调用,关键就在chain函数,源码中chain函数很简单:它只是返回一个实例,并且在实例上增加一个属性_chain = true。这样好像也并不能实现链式调用。

  // Add a "chain" function. Start chaining a wrapped Underscore object.
    function chain(obj) {
        var instance = _(obj);
        instance._chain = true;
        return instance;
    }

 上面的mixin方法中,原型上扩展方法的最后,这里返回的是调用了 chainResult函数返回的结果,

 return chainResult(this, func.apply(_, args));

看下 chainResult函数返回了什么?instance是当前实例,obj就是实例上函数返回的结果,也就是调用unique函数的返回值,[2,4,3] 

function chainResult(instance, obj) {
        return instance._chain ? _(obj).chain() : obj;
    }

如果_chain是true,说明开启了链式调用,又去调用了_([2,4,3]),返回了一个实例,实例可以接着调用原型上的方法。如果没有开启链式调用,直接将[2,4,3]返回。

也就是说,如果开启了链式调用,返回结果一直是实例,如果没有开启,返回结果直接是处理参数后的返回值。

平时简单的实现链式调用,

  var jQuery = {
        css: function() {
            console.log(1) // 1
            return this
        },
        style: function() {
            console.log(2) // 2
            return this

        }
    }

    jQuery.css().style()

每一个方法都返回this,就可以直接进行链式调用,但是underScore原型上所有的方法都不是return this。而是同过chain 和chainResult方法,动态的返回实例或者参数处理后的结果。

 

    // obj要处理的数据源,可以是对象,可以是数组,iteratee迭代器函数,context
    _.map = function(obj, iteratee, context) {
        // 初始化迭代器,生成不同功能的迭代器
        var iteratee = cb(iteratee, context);
        // 如果是对象,我们需要找出属性的个数
        var keys = !_.isArray(obj) && Object.keys(obj);
        var length = (keys || obj).length;
        var result = Array(length);

        for(var index = 0; index < length; index++) {
            var currentKey = keys ? keys[index] : index;
            result[index] = iteratee(obj[currentKey], index, obj)
        }
        return result;
    }

 判断是不是数组的函数

   _.isArray = function(obj) {
        return {}.toString.call(obj) === '[object Array]'
    }

如果给map函数传递了迭代器,就返回该迭代器函数,如果没有传递,就返回一个初始化的迭代器 

   var cb = function(iteratee, context, count) {
        if(iteratee == null) {
            return _.identify;
        }
        if(_.isFunction(iteratee)) {
            return optimizeCb(iteratee, context, count)
        }
    }

初始化迭代器 

// 这是一个默认迭代器 没有对参数做任何的处理 
    _.identify = function(value) {
        return value
    }

 这个 optimizeCb方法是写在underScore内部使用的,不对外暴露接口,根据传递的参数不通,返回不通的迭代器。如果传递了context参数,也就是要给迭代器制定上下文this,所以就需要用call来调用改变this指向。switch中的内容,是为了改变this指向的。如果没有传递,就默认返回该函数。这里面count为4的情况是为后面的 reduce函数提供服务的,这里先不用关注。count代表迭代器函数能接受到的参数的个数。一般默认是3个,value,index,和数据源本身。

    var optimizeCb = function (func, context, count) {
        if(context == void 0) {
            return func;
        }
        switch (count == null ? 3 : count) {
            case 1: 
                return function(value) {
                    return func.call(context, value)
                };
            case 3:
                return function(value, index ,obj) {
                    return func.call(conrtext,value, index, obj)
                };
            case 4:
                return function(memo, value, index, obj) {
                    func.call(context, memo, value, index, obj)
                }
        }
    }

 5 reduce方法

用法: 可以看到memo就是每一次迭代的返回值,重新传递给下一个迭代。就实现了累加的功能。

     console.log(_.reduce([3,4,5,4,3,2], function(memo,value) {
        return memo + value
    }, 0)) // 21

reduce是基于内部的一个createReduce函数来实现的。这个方法是在内部调用的,默认传递了1,就是说从0开始,-1是从最后一位开始。 

_.reduce = creatReducer(1);

    // Create a reducing function iterating left or right.
    var createReduce = function(dir) {
      // Wrap code that reassigns argument variables in a separate function than
      // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
      var reducer = function(obj, iteratee, memo, initial) {
        var keys = !isArrayLike(obj) && _.keys(obj),
            length = (keys || obj).length,
            index = dir > 0 ? 0 : length - 1;
        if (!initial) {
          memo = obj[keys ? keys[index] : index];
          index += dir;
        }
        for (; index >= 0 && index < length; index += dir) {
          var currentKey = keys ? keys[index] : index;
          memo = iteratee(memo, obj[currentKey], currentKey, obj);
        }
        return memo;
      };
  
      return function(obj, iteratee, memo, context) {
        var initial = arguments.length >= 3;
        return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
      };
    };

4 rest参数

通过_.restArguments方法传递一个函数,

es6用...变量名的方式获取函数的参数,并以数组的形式存储,我们想要达到的效果,掉用_.restArguments方法,返回一个函数,使得该函数支持rest参数,并以数组的形式传递

    
       var test = function(a, rest) {
           console.log(rest) // [3, 4, 4, 5, 4]
       }
       var rest = _.restArguments(test);
       rest(1,3,4,4,5,4)
    _.restArguments = function(func) {
        // func.length是函数行参的个数, startIndex除rest以外的参数个数
        var startIndex = func.length - 1;
        return function() {
            // 计算rest数组的长度
            var length = arguments.length - startIndex,
                rest = Array(length),
                index = 0;
            for(; index < length; index++) {
                rest[index] = arguments[index+startIndex]
            }
            // func函数的参数个数
            var args = Array(startIndex+1);
            for(index = 0; index < startIndex; index++) {
                args[index] = arguments[index]
            }
            args[startIndex] = rest
            return func.apply(this, args)
        }
    }

 

偏函数(partial)反映了新函数是原函数的一部分,underScore的_.partial方法 返回的就是一个偏函数。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值