2017-04-18
FE
JavaScript
FP
活用函数作为一等公民的权利
JavaScript 里的函数既可以作为参数传递给一个函数,也可以作为函数返回值返回,其地位跟别的变量一样并无二致,也就是说
———— JavaScript 的函数是一等公民
说JS函数是一等公民的大意是说:函数在这个JS里面可以作为参数传递给另一个函数或者作为返回值返回、还可以保存在变量或者其他数据结构里(比如数组这样)
我们称具有以函数作为参数或者返回值是参数的函数为 高阶函数
在数学上,相信你也很熟悉:y = g(f(x)),这时候 y 就是我所说的高阶函数

# 回调函数

函数作为参数传递的时候,通常称为 回调函数
这种高阶函数的调用方式你一定很熟悉:
00setTimeout(function(){
01    console.log('After 200 ms');
02}, 200);
函数最平凡的意味是流程
———— 可能是一个计算过程,亦或者是一次 http 请求 等等
把函数作为参数传递,无疑是想要高阶函数在适当的时刻执行这个流程,并且,期望能给什么参数给回调函数。
异步函数总是需要一个参数作为回调函数,在适当的时候调用。
比如利用 $.ajax 封装一个 login 的 http 操作
00function login(user, cb){
01    $.ajax({
02        url: 'http://xxxx.com/api/login',
03        data: user, 
04        dataType: 'json', 
05        success: function(res){
06            cb(null, res); 
07        },
08        error: function(err){
09            cb(err); 
10        }
11    }); 
12}
13
14login({
15    id: 'eczn',
16    pwd: '0v0,i_am_pwd'
17}, function(err, res){
18    if (err) {
19        alert('err'); 
20        return 
21    }
22
23    if (res.code == 200){
24        alert('login success'); 
25        storageAuth(res.data.auth); // 存储登录凭证 用来访问其他api 
26    } else {
27        // 帐号密码错误 
28    }
29});
更一般的情况:
00function todo(data, cb){
01    // DELAY 表示延迟,这是异步操作
02    // DELAY 之后执行cb,可以认为是某个异步过程完成后回调一个函数
03    setTimeout(cb, DELAY); 
04}

# 传递上下文

上下文即 this
将函数作为参数传递给另外一个函数可以很轻易的将当前 this 传递到另一个函数里。
00var todo = function(){
01    // to do something ... 
02    // such as: 
03    return this.x + this.y; 
04}
05
06setTimeout(todo.bind(this), 300);
传递上下文尤其是在 相邻组件间通信 里面很重要,使用他可以让别的组件访问当前组件的状态。

# 访问闭包

一个函数里的变量外部无法访问,但是可以通过设置函数的方式来访问到,比如:
00function Rectangle(width, height){
01    let HowToGetArea; 
02
03    return {
04        setGetArea: function(cb){
05            HowToGetArea = cb; 
06        },
07        getArea: () => HowToGetArea(width, height)
08    }
09}
10
11let Box_A = Rectangle(4, 8); 
12
13Box_A.setGetArea((x, y) => x * y); 
14Box_A.getArea(); 
15// => 
16// 32 
17
18Box_A.setGetArea((x, y) => x + y); 
19Box_A.getArea(); 
20// => 
21// 12

# 偏函数

当函数作为函数值返回的时候,我们可以实现 偏函数
偏函数其实就是固定 (或者说预置) 参数
———— 或者说对于二元函数 f(x, y) 让 y 恒等于 k,从而得到: f(x, k) 这是数学上的偏函数
我在 This, Function 曾提到过的偏函数 logger:
00function logger(logType){
01    return console.log.bind(console, logType); 
02}
03
04var info = logger('[INFO]'); 
05var error = logger('[ERROR]'); 
06
07info('this is an info'); 
08// => 
09// [INFO] this is an info
10
11error('this is an error'); 
12// => 
13// [ERROR] this is an error
再来看看另外一个更明显的例子:
00let add = (x, y) => x + y; 
01
02add(2, 3); 
03// => 
04// 5 
05
06function addX(x){
07    return add.bind(this, x);
08}
09
10let add4 = addX(4); 
11
12add4(4); 
13// => 
14// 8
15
16add4(0);
17// =>
18// 4 
19
20add4(1);
21// =>
22// 5

# 创建闭包

当某个函数作为函数值返回的时候,无形中创建了闭包。
00function Rectangle(width, height){
01    // width, height 闭包 
02    return {
03        getArea: () => width * height, 
04        getColor: () => color,
05        setWH: (newWidth, newHeight) => {
06            width = newWidth, height = newHeight; 
07        } 
08    }
09}
10
11let box_A = Rectangle(1, 2); 
12let box_B = Rectangle(2, 3);
13
14box_A.getArea(); 
15// => 
16// 2 
17
18box_B.getArea(); 
19// => 
20// 6
21
22box_B.setWH(4, 5); 
23box_B.getArea(); 
24// => 
25// 20

# 惰性求值

00function add(x, y){
01    let cache; 
02
03    return function(){
04        // 如果事先有缓存计算结果
05        if (cache) {
06            return cache; 
07        } else {
08            // 没有缓存结果(第一次算)
09            let result = x + y;
10            cache = result; // 缓存结果 
11            return result;
12        }
13    }
14}
15
16var sum = add(1, 3); 
17
18sum(); 
19// => 
20// 4 
21
22sum(); 
23// => from cache 
24// 4
显然,示例中的 x + y 并不需要多长执行时间,因此惰性求值还有cache缓存结果显得非常多余。
但是如果某个计算操作需要很长的时间,可以先不求值,用闭包先保存好参数,需要用的时候再求值,甚至求值的结果还可以缓存下来,下次计算的时候会马上得到结果。




回到顶部