underscore源码阅读之一

算法破浪使
• 阅读 2151

此次源码分析为 1.8.3 version
以前曾读过一次,可是没有做下笔记。此次重新阅读特制此笔记

Baseline setup

underscore是包裹在一个闭包内部的防止污染全局变量

(function(){ 
}())

如果有人不知道的话我可以说一下
这样匿名函数立即调用
因为函数划分作用域
在创建作用域之后没有引用,则不污染全局

  var root = typeof self == 'object' && self.self === self && self ||
            typeof global == 'object' && global.global === global && global ||
            this ||
            {};

显然判断的是运行的环境。因为可能是node环境可能是浏览器环境等。

var previousUnderscore = root._;

如果之前存在_的话,则将其进行保存而不是简单的替换

  var ArrayProto = Array.prototype, ObjProto = Object.prototype;
  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

保存常见原型的引用

  var push = ArrayProto.push,
      slice = ArrayProto.slice,
      toString = ObjProto.toString,
      hasOwnProperty = ObjProto.hasOwnProperty;

保存常见方法的引用。因为频繁的查找。会损耗性能(高性能javascript)

  var nativeIsArray = Array.isArray,
      nativeKeys = Object.keys,
      nativeCreate = Object.create;

保存es5方法引用

var Ctor = function(){};

Constructor的缩写,在之后用于对象创建

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

创建一个underscore的对象引用,保证不重复引用
且当用_做函数调用时候,会自动返回_对象
当_调用时候则不会新生_对象

    if (typeof exports != 'undefined' && !exports.nodeType) {
        if (typeof module != 'undefined' && !module.nodeType && module.exports) {
            exports = module.exports = _;
        }
        exports._ = _;
    } else {
        root._ = _;
    }

将_underscore对象挂载到合适的位置。
类似umd的兼容写法

_.VERSION = '1.8.3';

记录版本号

  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      // The 2-parameter case has been omitted only because no current consumers
      // made use of it.
      case null:
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

优化回调(特指函数中传入的回调)
void 0是一个真正的undefined因为undefined是可以被赋值的
防止undefined值被篡改
接下来就是保证回调函数的执行上下文。
如果回调函数参数只有1个,那么我们在迭代过程中我们只需要值
两个回调函数参数的时候基本不存在这里省略
3个的情况类似forEach那么传入回调函数值(值,索引,被迭代集合对象)
4个的情况则是累加器,类似reduce(累加器,值,索引,被迭代集合对象)
argCount不存在则直接返回一个绑定上下文的回调函数

var builtinIteratee;

一个默认的迭代器

  var cb = function (value, context, argCount) {
        if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
        if (value == null) return _.identity;
        if (_.isFunction(value)) return optimizeCb(value, context, argCount);
        if (_.isObject(value)) return _.matcher(value);
        return _.property(value);
    };

为迭代过程中的元素生产一个回调函数, 该回调函数能够应用到集合中的每个元素
第一个是否使用默认的迭代器
第二个 如果value为null 则回调只是一个返回自身的函数
第三个 如果value是一个回调函数,则需要optimizeCb回炉改造回调进行优化
第四个 如果value是对象,则返回一个matcher进行对象匹配
最后 如果value只是一个字面量 则将value看做属性名称,默认返回一个获取对象属性的函数
简单的可以认为cb函数是对一个大的范围进行处理 optimizeCb 则是对一个更具体的回调函数进行优化

    _.iteratee = builtinIteratee = function (value, context) {
        return cb(value, context, Infinity);
    };

underscore内置的迭代回调
Infinity是一个全局的数值,代表无穷大
这又得牵扯到javascript的安全数值了

   var restArgs = function (func, startIndex) {
        startIndex = startIndex == null ? func.length - 1 : +startIndex;
        //rest判断有没有,没有的话默认是参数的最后。有的话则是有的情况。(+隐式转换数字)
        return function () {
            var length = Math.max(arguments.length - startIndex, 0);
            //防止出现负数
            var rest = Array(length);
            //开辟数组来存储rest
            for (var index = 0; index < length; index++) {
                rest[index] = arguments[index + startIndex];
            }
            //例如 func(a,b,...args)
            //实际上是在结构一下 func(1,2,3,4,5,6,7)变成func(1,2,[3,4,5,6,7])
            switch (startIndex) {
                case 0:
                    return func.call(this, rest);
                case 1:
                    return func.call(this, arguments[0], rest);
                case 2:
                    return func.call(this, arguments[0], arguments[1], rest);
            }
            //根据rest的参数不同决定不同的回调方法
            var args = Array(startIndex + 1);
            for (index = 0; index < startIndex; index++) {
                args[index] = arguments[index];
            }
            //这是一种默认的传参调用
            args[startIndex] = rest;
            //拼接剩余参数。传入函数回调
            return func.apply(this, args);
        };
    };
    

类似ES6的...实现
传入参数 func 需要rest参数的数组 startIndex从哪里开始标识rest参数, 如果不传递, 默认最后一个参数为rest参数
最后是返回一个具备了args(rest)参数的函数

  var createAssigner = function(keysFunc, undefinedOnly) {
    return function(obj) {
      var length = arguments.length;
      //获得参数长度
      //如果小于2或者传入obj为null 则直接返回
      if (length < 2 || obj == null) return obj;
      //枚举后面的参数对象
      for (var index = 1; index < length; index++) {
      //一个迭代
      //传入的keysFunc实际上也是一个函数
      //此时的source是对象参数
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
            //遍历对象键
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          //如果没有传入undefinedOnly
          //则一定会执行键值对赋值。键值相同会直接进行覆盖
          //如果有undefinedOnly值,则对象赋值则不会进行覆盖
          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
        }
      }
      //返回处理后的obj对象
      return obj;
    };
  };
    var baseCreate = function (prototype) {
        if (!_.isObject(prototype)) return {};
        if (nativeCreate) return nativeCreate(prototype);
        Ctor.prototype = prototype;
        var result = new Ctor;
        Ctor.prototype = null;
        return result;
    };

'类'的继承。(即Object.create)并且保证挂载在原型链的属性不会影响所继承的prototype
判断是否为对象,不是就直接返回了。
如果有Object.create则使用Object.create
没有的话下面相当于是一个Object.create的polyfill

  var property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

利用函数式编程思想
这是明显的函数向右柯里化
先传入key返回一个匿名函数为obj
当调用Property(length)的时候。里面则是为obj == null ? void 0 : obj[length];

  var deepGet = function(obj, path) {
    var length = path.length;
    for (var i = 0; i < length; i++) {
      if (obj == null) return void 0;
      obj = obj[path[i]];
    }
    return length ? obj : void 0;
  };

获得对象的深层属性

var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

最大安全'值'

var getLength = property('length');

像上面说的

  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

判断是不是类数组也就是简单判断是不是有length存在并且大于等于0且小于或等于最大安全整数


参考资料

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Stella981 Stella981
3年前
Go 源码阅读之 flag 包
Go源码阅读系列是我的源码阅读笔记。因为本人的电脑上Go的版本是1.13.4,所以就选择了该版本作为学习的版本。为此我在Github上Fork了Go的源码,并创建了study1.13.4分支,来记录对于源码的个人理解或者说中文注释也行。每当阅读完一个包后都会进行一下小结,就像这篇是对flag包的总结整理。当然在整理的过程中发现Go夜读
Wesley13 Wesley13
3年前
MySQL 的慢 SQL 怎么优化?
!(https://oscimg.oschina.net/oscnet/7b00ec583b5e42cc80e8c56c6556c082.jpg)Java技术栈www.javastack.cn关注阅读更多优质文章(https://www.oschina.net/action/GoToLink?urlhttp
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(