redux之compose源码分析

4月的上海,居然有30°的高温,果然是一个没有春天的城市。这样的高温下怎么能静得下心撸码呢,刚看过的内容转头就忘了,那就让我们一起来复习下redux中的compose到底是怎么工作的!

话不多说,上源码!下面是compose源码的es5写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
var last = funcs[funcs.length - 1];
var rest = funcs.slice(0, -1);
return function () {
return rest.reduceRight(function (composed, f) {
return f(composed);
}, last.apply(undefined, arguments));
};
}

es6写法

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
const last = funcs[funcs.length - 1]
const rest = funcs.slice(0, -1)
return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

es5和es6的写法区别倒是没有,要说唯一区别,就是装X!如果撸码不是为了装X,那将毫无意义(捂脸笑)!那我们就拿es5的写法来说:

1
2
3
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}

1.方法的开头准备了一个新的数组funs,不过可以看到这里他声明数组并没有使用new关键字,

The Array constructor is the %Array% intrinsic object and the initial value of the Array property of the global object. When called as a constructor it creates and initializes a new exotic Array object. When Array is called as a function rather than as a constructor, it also creates and initializes a new Array object. Thus the function call Array(…) is equivalent to the object creation expression new Array(…) with the same arguments.

但是根据上述ECMA规定,数组对象使不使用new关键字是一样的,不过一般的构造函数,不使用new实例化对象,其实拿到的是个undefined,因为函数默认返回个undefinednew关键字帮我们创建了一个新对象,把构造函数的this对象赋给这个新对象,最终在return这个新对象。具体请看js new都干了些啥。
扯远了,不过针对和我差不多的小白来说,看到和平时不一样的写法,脑子里也会三个问号。接着绕回来说,准备了一个新的数组funcs,遍历下arguments,赋给空数组funcs,形成一个函数数组(数组的每一项都是一个函数)。

1
2
3
4
5
6
7
8
9
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}

2.这一段其实就是做异常处理,compose的作用就是为了调用多个函数时,写法简单清晰。例如:

1
2
3
4
var join = function(x,y) { return x+y; };
var exclaim = function(x) { return x + '!'; };
非compose写法:exclaim(join('hello','world')) //helloworld!
compose写法:var composed = compose(join, exclaim); composed('hello', 'world') //helloworld!

如果你是看代码的人,你也应该不想看到第一种写法吧,所以一般使用compose,都会有多个参数,源码中这一段也是做个判断,如果你传的参数是0个或者1个,那么就直接返回一个匿名函数或者是你传进来的那个函数。

1
2
3
4
5
6
7
var last = funcs[funcs.length - 1];
var rest = funcs.slice(0, -1);
return function () {
return rest.reduceRight(function (composed, f) {
return f(composed);
}, last.apply(undefined, arguments));
};

3.剩最后一段,也是精华所在,取之前funcs数组的最后一个为last函数,取其他剩余的作为rest数组。compose函数的返回值是一个函数,在刚才的例子var composed = compose(join, exclaim); composed('hello', 'world')也能看的出来,先来说说reduceRight,es5数组中新增的方法,方法接收两个参数,第一个是个函数,这个函数接收四个参数,第一个是prev,第二个是current,第三个是index,第四个是array本身。函数的返回值作为下一次的prev,第二个是个初始值,可能这么说,有点晕。写个例子就清楚了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[1,2,3].reduceRight(function(prev, current){
console.log(prev, current);
return current;
}, 0)
// 0,3
// 3,2
// 2,1
[1,2,3].reduceRight(function(prev, current){
console.log(prev, current);
return current;
})
// 3,2
// 2,1

如果有第二个参数初始值,那么第一次的prev值就是初始值。反之,第一次的prev为数组的最后一个元素,直到遍历结束,最后一个current为空。
这么看来,就清楚多了。funcs的最后一个函数,也就是last函数,调用last函数作为初始值,compose的返回值函数的参数,其实就是last函数的参数,由于不知道函数的参数,刚好arguments是个伪数组,可以利用apply的特性实现last的调用,有疑问的请看关于apply的用法,那么初始值就是这个last函数的返回值,作为rest数组遍历时下一个函数的参数,
依次遍历,其实就是从右到左一次执行函数,把结果作为下一个函数的参数,最终返回第一个函数的结果。以上就是reduxcompose的源码分析。
其实redux中用compose多的地方是在mapDispatchToProps中,因为store.dispatch(action)的原因,一般可以写成compose(dispatch, action)

菜鸟学习笔记,如有不对,还希望高手指点。如有造成误解,还希望多多谅解。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。