美文网首页
[underscore 源码学习] undefined 的处理

[underscore 源码学习] undefined 的处理

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

    undefined 的处理

    不可靠的 undefined

    • JavaScript 中,假设我们想判断一个变量是否是 undefined,通常会这样写:
      if(a === undefined) {}
    • JavaScript 中的 undefined 并不可靠,例如:
    function test(a) {
      var undefined = 1;
      console.log(undefined); // => 1
      if (a === undefined) { ... }
    }
    

    undefined 被轻易修改为 1,使之后对 undefined 的理解引起歧义。

    所以在 JavaScript 中,把 undefined 直接解释为 “未定义” 是有风险的,因为这个标识符可能被篡改。

    最常见的用法是通过以下运算来获得 undefined,表达式为 0 时的运算开销最小。
    void 0; || void(0);

    iteratee

    一个迭代至少由两部分构成:

    • 被迭代集合
    • 当前迭代过程

    underscore 中,当前迭代过程是一个函数,称为 iteratee(迭代器),它将对当前迭代元素进行处理。

    _.map = function(obj, iteratee, context) {
      iteratee = cb(iteratee, context);
      var keys = !isArrayLike(obj) && _.keys(obj),
            length = (keys || obj).length,
            results = Array(length); // 定义初始化数组
      for (var index = 0; index < length; index++) {
        var currentKey = keys ? keys[index] : index;
        results[index] = iteratee(obj[currentKey], currentKey, obj);
      }
      return results;
    };
    

    cb (优化迭代器)

    我们传递给 _.map 的第二个参数就是一个 iteratee,它可能是函数、对象、甚至字符串,underscore 会将其统一处理为一个函数。

    这个处理由 underscore 的内置函数 cb 完成。

    cb 将根据不同情况,为我们的迭代创建一个迭代过程 iteratee,服务于每轮迭代。

    // 源码
    var cb = function(value, context, argCount) {
      // 是否用自定义的 iteratee
      if (_.iteratee !== builtinIteratee) {
        return _.iteratee(value, context);
      }
      // 针对不同的情况
      if (value === null) {
        return _.identity;
      }
      ...
    };
    

    optimizeCb

    当传入的 value 是一个函数时,value 还要经过内置函数 optimizeCb 才能获得最终的 iteratee

    optimizeCb
    • 含义:是一个对最终返回的 iteratee 进行优化的过程。
    • 源码:
    var optimizeCb = function(func, context, argCount) {
      if (context === void 0) return func;
      ...
    };
    
    • 总体思路:可选传入待优化的回调函数 func,以及迭代回调需要的参数个数 argCount,根据参数个数分情况进行优化。

    下面开始源码学习:

    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;
        };
    
        _.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);
                    };
            }
        };
    
        _.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>
            const a = {
                'a': 1
            };
            console.log(_.map([1,2,3,4,5], function(value) {
                console.log(this);
                return value * 3;
            }, a));
        </script>
    </body>
    </html>
    

    显示结果:


    这里 this 结果指向设置的上下文 {a:1},之所以打印 5 次,原因在于该刚发被调用 5 次。

    相关文章

      网友评论

          本文标题:[underscore 源码学习] undefined 的处理

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