美文网首页
[underscore 源码学习] reduce & 真值检测函

[underscore 源码学习] reduce & 真值检测函

作者: 小黄人get徐先生 | 来源:发表于2020-01-28 14:02 被阅读0次

    map - reduce

    JavaScript Array.prototype 提供的 mapreduce 函数不仅是存在于 JavaScript 的两个 API,更是函数式编程语言的重要组成部分,是一种对列表的操作思路。

    map(映射)
    • 一个映射过程就是将各个元素,按照一定的规则,逐个映射为新的元素。这是一个一一对应的过程。


    reduce(规约)
    • 一个规约过程仍然需要迭代指定列表的每个元素,然后仍然按照一定规则,合并这些元素到一个目标对象上。这是一个由多至一的过程,或者说是一个逐步累积的过程。


    underscore reduce

    // 源码
    var createReduce = function(dir) {
        var reducer = function(obj, iteratee, memo, initial) {
          ...
        };
    
        return function(obj, iteratee, memo, context) {
          return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
        };
    };
    

    underscore 通过内部函数 createReducer 来创建 reduce 函数:

    • 区分 reduce 的方向 dir,是从序列开端开始做规约过程,还是从序列末端开始做规约过程。
    • 判断用户在使用 _.reduce 或者 _.reduceRight 时,是否传入第三个参数,即是否传入了规约起点。

    真值检测函数

    概述

    • underscore 中,除了 _.each_.map_.reduce 等函数操作集合,还提供了 _.filter_.reject_.every_.some 这几个基于逻辑判断的集合操作函数。
    • 无一例外的是,这些函数都依赖于用户提供的 真值检测函数 用来判断当前迭代元素是否满足条件。underscore 将真值检测函数参数命名为 predicate

    下面我们开始源码学习:

    reduce

    test.js

    (function(root) {
    
        const toString = Object.prototype.toString;
        const push = Array.prototype.push;
    
        const _ = function(obj) {
    
            if (obj instanceof _) {
                return obj;
            }
    
            if (!(this instanceof _)) {
                return new _(obj);
            }
            this._wrapped = obj;
        };
    
        // (direction)dir 为 1,从左到右累加;dir 为 -1,从右到左累加。
        const createReduce = function(dir) {
    
            const reducer = function(obj, iteratee, memo, initial) {
                const keys = !_.isArray(obj) && Object.keys(obj);
                const length = (keys || obj).length;
                let index = dir > 0? 0 : length - 1;
    
                // 如果不包含初始值,则使用 第一个或最后一个值 作为初始化值,并相应移动 index dir 步。
                if (!initial) {
                    memo = obj[keys? keys[index] : index];
                    index += dir;
                }
                
                for (index; index >= 0 && index < length; index += dir) {
                    const currentKey = keys? keys[index] : index;
                    memo = iteratee(memo, obj[currentKey], currentKey, obj);
                }
                return memo;
            };
    
    
            return function(obj, iteratee, memo, context) {
                // 如果值的个数大于等于 3,说明存在初始化值
                const initial = arguments.length >= 3;
                return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
            };
        };
    
        _.reduce = createReduce(1);
    
        _.reduceRight = createReduce(-1);
    
        // rest 参数
        _.restArguments = function(func) {
            // rest 参数位置
            const startIndex = func.length - 1;
            return function() {
                const length = arguments.length - startIndex;
                const rest = Array(length);
                // rest 数组中的成员
                for (let index = 0; index < length; index++) {
                    rest[index] = arguments[index + startIndex];
                }
                // 非 rest 参数成员的值一一对应
                const args = Array(startIndex + 1);
                for (let index = 0; index < startIndex; index++) {
                    args[index] = arguments[index];
                }
    
                args[startIndex] = rest;
                return func.apply(this, args);
            };
        };
    
    
        _.isFunction = function(obj) {
            return typeof obj === 'function';
        };
    
        const cb = function(iteratee, context, count) {
            if (iteratee === null) {
                return _.identity;
            }
    
            if (_.isFunction(iteratee)) {
                return optimizeCb(iteratee, context, count);
            }
        };
    
        const 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(context, value, index, obj);
                    };
                case 4:
                    return function(memo, value, index, obj) {
                        return func.call(context, memo, value, index, obj);
                    }
            }
        };
    
        _.identity = function(value) {
            return value;
        };
    
        _.map = function(obj, iteratee, context) {
            // 生成不同功能迭代器
            const cbIteratee = cb(iteratee, context);
            const keys = !_.isArray(obj) && Object.keys(obj);
            const length = (keys || obj).length;
            const result = Array(length);
    
            for (let index = 0; index < length; index++) {
                const currentKey = keys? keys[index] : index;
                result[index] = cbIteratee(obj[currentKey], index, obj);
            }
    
            return result;
        };
    
        _.unique = function(obj, callback) {
            const res = [];
            for (let i = 0; i < obj.length; i++) {
                const val = callback? callback(obj[i]) : obj[i];
                if (res.indexOf(val) === -1) {
                    res.push(val);
                }
            }
            return res;
        };
    
        _.isArray = function(obj) {
            return toString.call(obj) === "[object Array]";
        };
    
        _.functions = function(obj) {
            const res = [];
            for (let key in obj) {
                res.push(key);
            }
            return res;
        };
    
        _.each = function(obj, callback) {
            if (_.isArray(obj)) {
                for (let i = 0;i < obj.length; i++) {
                    callback.call(obj, obj[i], i);
                }
            } else {
                for (let key in obj) {
                    callback.call(obj, key, obj[key]);
                }
            }
        };
    
        _.chain = function(obj) {
            const instance = _(obj);
            instance._chain = true;
            return instance;
        };
    
        const result = function(instance, obj) {
            return instance._chain? _(obj).chain() : obj;
        };
    
        _.prototype.value = function() {
            return this._wrapped;
        };
    
        _.mixin = function(obj) {
            _.each(_.functions(obj), (name) => {
                const func = obj[name];
    
                _.prototype[name] = function() {
                    let args = [this._wrapped];
                    push.apply(args, arguments);
                    return result(this, func.apply(this, args));
                };
            });
        };
    
        _.mixin(_);
    
        root._ = _;
    })(this);
    
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>underscore</title>
    </head>
    <body>
        <script src="./test.js"></script>
        <script>
            console.log(_.reduce([1,2,3,4,5], function(acc, val, idx, obj) {
                return acc + val;
            }, 0));
            console.log(_.reduce({a: 1, b: 2, c: 2}, function(acc, val, idx, obj) {
                return acc + val;
            }, 2));
        </script>
    </body>
    </html>
    

    显示结果如下:


    filter

    test.js

    (function(root) {
    
        const toString = Object.prototype.toString;
        const push = Array.prototype.push;
    
        const _ = function(obj) {
    
            if (obj instanceof _) {
                return obj;
            }
    
            if (!(this instanceof _)) {
                return new _(obj);
            }
            this._wrapped = obj;
        };
    
        _.filter = function(obj, predicate, context) {
            predicate = cb(predicate, context);
    
            const results = [];
    
            _.each(obj, function(value, index, list) {
                if (predicate(value, index, list)) {
                    results.push(value);
                }
            });
    
            return results;
        };
    
        // (direction)dir 为 1,从左到右累加;dir 为 -1,从右到左累加。
        const createReduce = function(dir) {
    
            const reducer = function(obj, iteratee, memo, initial) {
                const keys = !_.isArray(obj) && Object.keys(obj);
                const length = (keys || obj).length;
                let index = dir > 0? 0 : length - 1;
    
                // 如果不包含初始值,则使用 第一个或最后一个值 作为初始化值,并相应移动 index dir 步。
                if (!initial) {
                    memo = obj[keys? keys[index] : index];
                    index += dir;
                }
    
                for (index; index >= 0 && index < length; index += dir) {
                    const currentKey = keys? keys[index] : index;
                    memo = iteratee(memo, obj[currentKey], currentKey, obj);
                }
                return memo;
            };
    
    
            return function(obj, iteratee, memo, context) {
                // 如果值的个数大于等于 3,说明存在初始化值
                const initial = arguments.length >= 3;
                return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
            };
        };
    
        _.reduce = createReduce(1);
    
        _.reduceRight = createReduce(-1);
    
        // rest 参数
        _.restArguments = function(func) {
            // rest 参数位置
            const startIndex = func.length - 1;
            return function() {
                const length = arguments.length - startIndex;
                const rest = Array(length);
                // rest 数组中的成员
                for (let index = 0; index < length; index++) {
                    rest[index] = arguments[index + startIndex];
                }
                // 非 rest 参数成员的值一一对应
                const args = Array(startIndex + 1);
                for (let index = 0; index < startIndex; index++) {
                    args[index] = arguments[index];
                }
    
                args[startIndex] = rest;
                return func.apply(this, args);
            };
        };
    
    
        _.isFunction = function(obj) {
            return typeof obj === 'function';
        };
    
        const cb = function(iteratee, context, count) {
            if (iteratee === null) {
                return _.identity;
            }
    
            if (_.isFunction(iteratee)) {
                return optimizeCb(iteratee, context, count);
            }
        };
    
        const 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(context, value, index, obj);
                    };
                case 4:
                    return function(memo, value, index, obj) {
                        return func.call(context, memo, value, index, obj);
                    }
            }
        };
    
        _.identity = function(value) {
            return value;
        };
    
        _.map = function(obj, iteratee, context) {
            // 生成不同功能迭代器
            const cbIteratee = cb(iteratee, context);
            const keys = !_.isArray(obj) && Object.keys(obj);
            const length = (keys || obj).length;
            const result = Array(length);
    
            for (let index = 0; index < length; index++) {
                const currentKey = keys? keys[index] : index;
                result[index] = cbIteratee(obj[currentKey], index, obj);
            }
    
            return result;
        };
    
        _.unique = function(obj, callback) {
            const res = [];
            for (let i = 0; i < obj.length; i++) {
                const val = callback? callback(obj[i]) : obj[i];
                if (res.indexOf(val) === -1) {
                    res.push(val);
                }
            }
            return res;
        };
    
        _.isArray = function(obj) {
            return toString.call(obj) === "[object Array]";
        };
    
        _.functions = function(obj) {
            const res = [];
            for (let key in obj) {
                res.push(key);
            }
            return res;
        };
    
        _.each = _.forEach = function(obj, iteratee, context) {
            iteratee = optimizeCb(iteratee, context);
            if (_.isArray(obj)) {
                for (let i = 0;i < obj.length; i++) {
                    iteratee(obj[i], i, obj);
                }
            } else {
                for (let key in obj) {
                   iteratee(obj[key], key, obj);
                }
            }
            return obj;
        };
    
        _.chain = function(obj) {
            const instance = _(obj);
            instance._chain = true;
            return instance;
        };
    
        const result = function(instance, obj) {
            return instance._chain? _(obj).chain() : obj;
        };
    
        _.prototype.value = function() {
            return this._wrapped;
        };
    
        _.mixin = function(obj) {
            _.each(_.functions(obj), (name) => {
                const func = obj[name];
    
                _.prototype[name] = function() {
                    let args = [this._wrapped];
                    push.apply(args, arguments);
                    return result(this, func.apply(this, args));
                };
            });
        };
    
        _.mixin(_);
    
        root._ = _;
    })(this);
    

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>underscore</title>
    </head>
    <body>
        <script src="./test.js"></script>
        <script>
            console.log(_.filter([12,50,2,4,6,13,12], function(elem) {
                return elem < 10;
            }));
        </script>
    </body>
    </html>
    

    显示结果:


    相关文章

      网友评论

          本文标题:[underscore 源码学习] reduce & 真值检测函

          本文链接:https://www.haomeiwen.com/subject/kampthtx.html