概述

很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以就了结研究underscore源码这一心愿吧。

underscore.js源码研究(1)

underscore.js源码研究(2)

underscore.js源码研究(3)

underscore.js源码研究(4)

underscore.js源码研究(5)

underscore.js源码研究(6)

underscore.js源码研究(7)

underscore.js源码研究(8)

参考资料:underscore.js官方注释undersercore 源码分析undersercore 源码分析 segmentfault

函数式编程

之前一直没搞懂为什么要用map而不用for循环,原来是因为map是函数式编程中的迭代式思维

对于一个迭代来说,他至少由两个部分构成:被迭代集合当前迭代过程。在underscore中,当前迭代过程就是一个函数,叫做iteratee,它会对被迭代集合进行处理。示例如下:

_.map = _.collect = 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;
};

可以看到,iteratee函数会先经过cb方法进行处理,而cb方法是通过optimizeCb来处理一个函数的,原代码如下:

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) && !_.isArray(value)) return _.matcher(value);
return _.property(value);
};

optimizeCb的代码很长:

var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount == null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
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);
};
};

虽然代码很长,但是optimizeCb方法只起了1个作用:把context这个上下文绑定在func函数上面。那些长长的case只是原样返回func不同参数的情形:func只带一个参数,比如times的迭代函数;func带三个参数,比如map,some等的迭代函数;func带四个参数,比如reduce等的迭代函数。

剩余参数的实现

我们常常有这样一个需求,就是想要使一个函数接受多个参数,但是在调用的时候可以不必输入全部参数,我们可以这样实现:

function add(x, y, z) {
z = z == null ? 0 : z;
return x + y + z;
} add(1,2,0); //输出3
add(1,2); //输出3

但是如果我们想要使add能够接受无限多个参数呢?(这在函数式编程里面是非常正常的)我们可以把剩余的参数装在一个rest参数里面:

function add(x, rest) {
return _.reduce(rest,function(accum, current){
return accum+current;
},x);
} add(1, [2, 3]); //输出6
add(1, [2, 3, 4]); //输出10
add(1, [2, 3, 4, 5]); //输出15

所以问题变成了怎么把add进行封装使得add(1,2,3,4)会变成add(1, [2,3,4])的形式。

对此underscore利用了restArguments方法,源码和注释如下:

var restArguments = function(func, startIndex) {
//不输入startIndex则自动取最后一个为rest
startIndex = startIndex == null ? func.length - 1 : +startIndex;
//接受一个函数为参数,返回一个包装后的函数,参数用arguments获取
return function() {
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
switch (startIndex) {
//原函数只接受一个rest参数
case 0: return func.call(this, rest);
//原函数接受1个参数 + rest参数
case 1: return func.call(this, arguments[0], rest);
//原函数接受2个参数 + rest参数
case 2: return func.call(this, arguments[0], arguments[1], rest);
}
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
//原函数接受2个以上参数 + rest参数
return func.apply(this, args);
};
};

使用起来是这样的:

var addWithRest = _.restArguments(add, 1);
addWithRest(1,2,3,4); //10

值得一提的是,es6发明了一个语法,叫做Rest parameters。这种语法比上面的方法更加人性化,示例如下:

//...theArgs这种写法就是Rest parameters的写法
function sum(...theArgs) {
return theArgs.reduce((previous, current) => {
return previous + current;
});
} console.log(sum(1, 2, 3));
// expected output: 6 console.log(sum(1, 2, 3, 4));
// expected output: 10

注意:restArguments方法是最新加入的,cdn上的1.8.3 版本的underscore里面没有restArguments方法。

underscore.js源码研究(3)的更多相关文章

  1. underscore.js源码研究(8)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  2. underscore.js源码研究(7)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  3. underscore.js源码研究(6)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  4. underscore.js源码研究(5)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  5. underscore.js源码研究(4)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  6. underscore.js源码研究(2)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  7. underscore.js源码研究(1)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  8. underscore.js源码解析(五)—— 完结篇

    最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...

  9. underscore.js 源码

    underscore.js 源码 underscore]JavaScript 中如何判断两个元素是否 "相同" Why underscore 最近开始看 underscore.js ...

随机推荐

  1. Java第1章笔记

    第一章 计算机程序:计算机为完成某些功能产生的一系列有序指令集合. Java技术包括:JavaSE(标准版)  JavaEE(企业版) ---JavaME(移动版) 开发Java程序步骤:1.编写 2 ...

  2. dblink(转)

    oracle在进行跨库访问时,可以通过创建dblink实现,今天就简单的介绍下如果创建dblink,以及通过dblink完成插入.修改.删除等操作 首先了解下环境:在tnsnames.ora中配置两个 ...

  3. 697. Degree of an Array

    static int wing=[]() { std::ios::sync_with_stdio(false); cin.tie(NULL); ; }(); class Solution { publ ...

  4. 2019.01.04 洛谷P4719 【模板】动态dp(链分治+ddp)

    传送门 ddpddpddp模板题. 题意简述:给你一棵树,支持修改一个点,维护整棵树的最大带权独立集. 思路: 我们考虑如果没有修改怎么做. 貌似就是一个sbsbsb树形dpdpdp,fi,0f_{i ...

  5. Win7 qt-windows-x86-msvc2015-5.6.0 DLL依赖库打包

    今天开始系统的学习QT,第一个测试的问题就是在纯净的系统中如何正常运行,也就是找出QT生成的exe的依赖库问题 网上搜了下可以简单粗暴的用 D:\Qt\Qt5.6.0\5.6\msvc2015\bin ...

  6. spoj COT2(树上莫队)

    模板.树上莫队的分块就是按dfn分,然后区间之间转移时注意一下就好.有个图方便理解http://blog.csdn.net/thy_asdf/article/details/47377709: #in ...

  7. Beta阶段第六篇Scrum冲刺博客-Day5

    1.站立式会议 提供当天站立式会议照片一张 2.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中: 昨天已完成的工作. 张晨晨:完善收藏功能 郭琪容:收藏功能的实现 吴玲:完 ...

  8. protobuf和protostuff的区别

    在我们的开发过程中,序列化是经常需要处理的问题,比如在做分布式访问数据时,或者是在做redis缓存存储数据时,如果我们涉及的知识面不够广的话,可能会简单的使用JDK的序列化,也即在需要序列化的类上im ...

  9. Android FragmentActivity 嵌套 Fragment 调用startActivityForResult返回的requestCode错误

    Android FragmentActivity 嵌套 Fragment 调用startActivityForResult返回的requestCode错误 此时,要在调用startActivityFo ...

  10. hdu 5092 线裁剪(纵向连线最小和+输出路径)

    http://acm.hdu.edu.cn/showproblem.php?pid=5092 给一个m*n的矩阵,找到一个纵向的"线"使得线上的和最小并输出这条线,线能向8个方向延 ...