虽然Underscore并没有在API手册中提及到restArgs函数,我们仍然可以通过_.restArgs接口使用restArgs函数。如果不去阅读源码,我们很难发现Underscore中还有这样的一个函数,对于这样的一个“没有存在感”的函数,我们为什么要使用并学习它呢?

这个函数虽然比较“低调”,但是它在Underscore中的存在感却一点也不低。在Underscore源码中,restArgs函数作为工具函数,参与多个公开API的实现,可谓劳苦功高。从其多次参与实现公开API可以看出,这是一个十分重要的函数,为了方便讲解后面的公开API,这里专门写一篇文章介绍restArgs工具函数。

为什么我们需要restArgs?

在现实中,我们可能有碰到过一些特殊情况,比如我们所写的函数不确定有多少个要传递的参数,这样在函数内部实现参数处理时就会比较棘手。

比如现在我们需要构建一个函数,这个函数接受至少两个参数,第一个是一个数组对象,第二个之后是一些值,我们的函数就需要把这些值添加到第一个参数的尾部。

代码实现:

function appendToArray(arr) {
if(arguments.length < 2)
throw new Error('funciton a require at least 2 parameters!');
return arr.concat(Array.prototype.slice.call(arguments, 1));
}

  

现在我们需要实现另一个函数,该函数也是把参数附加到数组中,不过实现了过滤器功能,比如只把大于1的参数附加到数组中。

代码实现:

function appendToArrayPlus(arr, filter) {
if(arguments.length < 3)
throw new Error('function a require at least 3 parameters!');
var params = Array.prototype.slice.call(arguments, 2);
for(var i = 0; i < params.length; i++) {
if(filter(params[i])) {
arr.push(params[i]);
}
}
return arr;
}

  

可以看出来,我们在开发这两个函数时,做了重复的工作,那就是根据多余参数的开始序号来截断arguments对象。

这样的做法,在写一两个函数时没有什么问题,但是在开发框架时,需要写大量的参数个数不确定的函数,这就会使得冗余代码大量增加,并且多次直接操作arguments对象的做法并不十分优雅。

所以我们需要一个restArgs这样的工具函数,给它传递一个函数以及一个多余参数开始索引(startIndex)作为参数,它会返回一个函数,我们在调用返回的函数时,开始索引之后的多余参数会被放入到数组中,然后一并传递给restArgs的第一个参数函数调用(作为最后一个参数)。

有人会说,ES6中已经实现了rest params的功能,参考阮老师教程,但是我们知道一个框架的开发,必须考虑到兼容问题,很多低端浏览器并未实现ES6语法。所以在Underscore中,暂时还未使用ES6语法。

Underscore的实现

Underscore实现的源码(附注释):

// Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
// This accumulates the arguments passed into an array, after a given index. //restArgs用于把func的位于startIndex之后的参数归类为一个数组,
//然后返回一个函数把这个数组结合startIndex之前的参数传递给func调用。
var restArgs = function (func, startIndex) {
//function.length表示function定义时,形式参数的个数。
//注意此处是func.length,即传入的方法参数的形参个数而不是当前函数的参数个数,需要结合具体传入的参数来看。
//当startIndex参数未传递时,默认func函数的最后一个参数开始为多余参数,会被整合到数组中。
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function () {
//length表示构造的多余参数数组的长度,是实际的多余参数或者0。
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
//新建了一个rest数组,把位于startIndex索引之后的所有参数放入该数组。
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
//将多余参数放入rest数组之后,直接用Function.prototype.call执行函数。
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);
}
//如果startIndex > 2,那么使用apply传递数组作为参数的方式执行func。
//虽然调用方法发生了变化,但是还是会把rest数组放在传入的参数数组的最后。
//这样做其实与之前的方法无异(switch部分可以删除),但是call的效率高于apply。 //args数组用于盛放startIndex之前的非多余参数。
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};

  

可以注意到两个重点:

  • 1 startIndex默认为func函数的形参个数减1,那么代表的含义就是当我们调用restArgs函数不传递第二个参数时,默认从最后一个形参开始即为多余参数。

    比如:

      _.invoke = restArgs(function (obj, path, args) {...});
    _.invoke(obj, path, 1, 2, 3);

      

    以上代码中,如果我们给_.invoke传递这些参数,那么实际上执行的函数会是:

    function(obj, path, [1, 2, 3]) {
    // ...
    }

      

    这样我们就可以在写函数_.invoke时,很方便的预处理args数组。

  • 2 switch中的内容只是后面func.apply(this, args)的一个特例,不写switch也完全可以实现功能,但是之所以要写这个switch,是因为Function.prototype.call的效率要高于Function.prototype.apply(具体请参考:Why is call so much faster than apply?)。

结语

学习完这个内部函数之后,再学习其他API源码时,就会好理解许多,我们需要重点注意的一点就是当restArgs只接受一个函数作为参数时,表示默认从接受的最后一个参数开始(包括最后一个参数)即为多余参数。

其余Underscore源码解读文章:GitHub

理解Underscore中的restArgs函数的更多相关文章

  1. 理解Underscore中的节流函数

    上一篇中讲解了Underscore中的去抖函数(_.debounced),这一篇就来介绍节流函数(_.throttled). 经过上一篇文章,我相信很多人都已经了解了去抖和节流的概念.去抖,在一段连续 ...

  2. 理解Underscore中的flatten函数

    最近是在所在实习公司的第一个sprint,有个朋友又请假了,所以任务比较重,一直这么久都没怎么更新了,这个周末赖了个床,纠结了一会儿决定还是继续写这个系列,虽然比较乏味,但是学到的东西还是很多的. 之 ...

  3. 理解Underscore中的uniq函数

    uniq函数,是Underscore中的一个数组去重函数,给它传递一个数组,它将会返回该数组的去重副本. 1 ES6版本去重 在ES6版本中,引入了一个新的数据结构——set,这是一种类似数组的数据结 ...

  4. 理解Underscore中的_.bind函数

    最近一直忙于实习以及毕业设计的事情,所以上周阅读源码之后本周就一直没有进展.今天在写完开题报告之后又抽空看了一眼Underscore源码,发现上次没有看明白的一个函数忽然就豁然开朗了,于是赶紧写下了这 ...

  5. 简单理解ECMAScript2015中的箭头函数新特性

    箭头函数(Arrow functions),是ECMAScript2015中新加的特性,它的产生,主要有以下两个原因:一是使得函数表达式(匿名函数)有更简洁的语法,二是它拥有词法作用域的this值,也 ...

  6. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  7. 理解Underscore中的_.template函数

    Underscore中提供了_.template函数实现模板引擎功能,它可以将JSON数据源中的数据对应的填充到提供的字符串中去,类似于服务端渲染的模板引擎.接下来看一下Underscore是如何实现 ...

  8. 理解javascript中的回调函数(callback)

    以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs ...

  9. 再次理解js中的call函数

    a.call(b); 网上说明的版本比较多.有的说,是指针替换.有说,将a对象的方法加在b对象执行.官方说:什么对象替换什么对象.反正看了几个版本,尽管有具体的实例,看了我三次都没看懂它的具体含义.看 ...

随机推荐

  1. java中的各种命令参数

    java中有很多命令参数,这些命令参数有些是控制jvm行为的,有的则是供应用程序使用.我所了解的参数主要有三种,现在说一说这三种类型的参数. (1)命令行参数. 命令行参数就是类似与c语言的命令行参数 ...

  2. Go在windows10 64位上安装

    一.安装 1.下载安装包http://www.golangtc.com/download,这是国内的地址,也直接去官网下载. 2.选择适合自己的版本,这里我选择安64位装版go1.9.2.window ...

  3. 点击checkbox 向input 里面传值显示

    文本框显示 <input type="text" id="textareashow" name="" class="marg ...

  4. 006.ASP.NET MVC ActionResults说明

    原文:http://rachelappel.com/asp.net-mvc-actiohttp://i.cnblogs.com/EditPosts.aspx?postid=3857238&up ...

  5. The Internet Communications Engine (Ice) 跨平台异构通讯方案 第二弹-Hello world!

    如果不知道ICE是什么的同学,请看上一篇的ICE简介:http://www.cnblogs.com/winds/p/3864677.html 好了,HelloWorld,从中间语言讲起. 首先,我们新 ...

  6. 配置Spring

    搭建Springmvc的时候,出现异常: IOException parsing XML document from ServletContext resource [/WEB-INF/applica ...

  7. linux 忘记密码

    密码保存在/etc/shadow文件中 1. root 密码忘记了 1.1 重启进入单人维护模式后, 系统会主动给予root权限的bash接口, 此时再以passwd修改密码即可: 1.2 以Live ...

  8. ef使用dbfirst方式连接mysql

    1.安装 mysql connector net 6.9.9  https://dev.mysql.com/downloads/file/?id=463758 和mysql for visual st ...

  9. vue.js与angular.js的区别(个人)

    刚进入实训 讲师就要发一些什么比较高大上的东西,本人才疏学浅  浅浅的分享一下angularjs 和vue.js的区别.只是简单的理解一下 大神勿喷. 生实训之前学习的angular.js 只是理解了 ...

  10. C语言实现整数数组的逆置算法

    读入100个整数到一个数组中,写出实现该数组进行逆置的算法. 方法一: 假设100个整数读入到数组a中,算法f1的思想是分别从数组两端依次将对应数进行交换,即a[i]与a[100 - i - 1]进行 ...