2017-04-08
FP
This, Function

# 函数调用 Function Invoke

00function sayThis(){
01    console.log(this); 
02}
我们先创建一个函数 sayThis 以期打印 函数调用 的时候 this 的指向
00sayThis(); 
01// => 
02// window or global
这时候 this 指向 全局对象(在浏览器上是 window, node上是 global),这点没有异议。 当函数被调用的时候, this 的取值将会计算出来,当用 ( ) 调用函数的时候,this 指向这个函数所属的对象,换句话说就是:
this 指的是,调用函数的那个对象
---- 阮一峰 - Javascript的this用法
因此如下代码能很好的理解:
00var sayA = function(){
01    console.log(this.A); 
02}
03
04A = 'i am A in the global'; 
05var obj = {
06    A: 'i am A in the obj',
07    __sayThisA: sayA,
08    __question: function(a_function){
09        console.log('in __question');
10        console.log(this.A); 
11        a_function(); 
12    }
13}
14
15sayA === obj.__sayThisA;
16// => true
17
18sayA(); 
19// => 
20// i am A in the global
21
22obj.__sayThisA(); 
23// => 
24// i am A in the obj
25
26obj.__question(obj.__sayThisA); 
27// => 
28// in __question
29// i am A in the obj
30// i am A in the global 
31// 在 __question 里调用 作为参数传递的 a_function 其 this 指向 global, 是 纯粹的函数调用
以上就是简单的函数调用
  1. 普通的执行 sayA() 指向 global
  2. 作为对象的方法执行 obj.__sayThisA() 指向 obj
  3. 还有一种是 new 的时候 var o = new O(); 这时候就指向 o

# Function.prototype

每个函数都从 Function.prototype 上继承了属性方法 比如 call, apply 和 bind, 每个函数都可以使用这三种方法,他们用来指定 如何执行函数 (钦定 this 的指向和参数表)
为了说明方便,定义一个新的函数 sayA_V2
00var sayA_V2 = function(i, j, k){
01    console.log(this.A); 
02    console.log(i + j + k); 
03}

# Function.prototype.apply

00sayA_V2();
01// => 
02// i am A in the global 
03// NaN  ( undefined + undefined + undefined )
04
05sayA_V2.apply(obj); 
06// => 
07// i am A in the obj
08// NaN  ( undefined + undefined + undefined )
09
10sayA_V2.apply(obj, [1, 2, 3]); 
11// => 
12// i am A in the obj
13// 6 (1 + 2 + 3)
某函数 func 执行 .apply 的时候 会把 call 的第一个参数作为 this 的指向,第二个参数是数组,按顺序传递给函数执行。
需要注意,如果这段代码运行在非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。

# Function.prototype.call

00// 保持前文的代码 继续写: 
01sayA_V2();
02// => 
03// i am A in the global 
04// NaN  ( undefined + undefined + undefined )
05
06sayA_V2.call(obj); 
07// => 
08// i am A in the obj
09// NaN  ( undefined + undefined + undefined )
10
11sayA_V2.call(obj, 1, 2, 3); 
12// => 
13// i am A in the obj
14// 6 (1 + 2 + 3)
注意:该函数的语法与 apply() 方法的语法几乎完全相同,唯一的区别在于,call()方法接受的是一个参数列表,而 apply()方法接受的是一个包含多个参数的数组(或类数组对象)。
---- MDN - Function.prototype.call()
注意 apply 方法接受的是一个包含多个参数的数组(或类数组对象)
apply也可以接受类数组对象

# Function.prototype.bind

  1. bind(newThis, a, b, c, …) 方法会返回一个新函数
  2. 这个新函数的 this 由 newThis 给出
  3. 参数表由后面的 a, b, c, … 给出
  4. 它很像 .call, 不同在于他返回一个新函数 该函数不论如何执行,其 this 都被锁死
  5. 可以利用 bind 预置函数需要的参数
00// 依据之前的代码 继续写: 
01var bind001 = sayA_V2.bind(obj); 
02bind001(); 
03// => 
04// i am A in the obj
05// NaN ( undefined + undefined + undefined )
06
07bind001(1,2,3); 
08// => 
09// i am A in the obj
10// 6 ( 1 + 2 + 3 )
11
12bind001.call(window, 1, 2, 3); 
13// => 
14// i am A in the obj  ** this is NOT window **
15// 6 ( 1 + 2 + 3 ) 
16
17var bindPre = sayA_V2.bind(obj, 1, 2); 
18bindPre(3); 
19// => 
20// i am A in the obj
21// 6 ( 1 + 2 + 3 ) 
22// 与 sayA_V2.call(obj, 1, 2, 3) 结果一致
23
24bindPre(4); 
25// => 
26// i am A in the obj
27// 7 ( 1 + 2 + 4 )
28// 与 sayA_V2.call(obj, 1, 2, 4) 结果一致
29
30// 三个参数 a, b, c 中 a, b 被钦点为 1, 2 了

# 玩法

虽然知道 call apply bind 三个函数的用法和意义…
但是基本没用过,也不知道 有什么实际的地方
最近在鼓捣 函数式编程 ,因此用到了不少 call applybind

# 高阶函数的 this 指向

00var obj = {
01    copyDist: '/path/for/copy/destination',
02    readFinish: function(err, data){
03        if (err) { throw err }
04        else {
05            this.copyTo(this.copyDist, data); 
06        }
07    },
08    copyTo: function(path, data){
09        // .... 
10    },
11    copy: function(filePath){
12        fs.readFile(filePath, this.readFinish.bind(this));
13    }
14}
15obj.copy('File.dat');
高阶函数指的是参数或者返回值为函数的函数.
上面的代码 copy 中的 this.readFinish 执行的时候 this 应该指向 obj , 因此用了 bind 才会让 readFinish 能正常工作

# 偏函数

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
bind 方法可以用来用来制造 偏函数,用于预置参数。
有时候我们需要打印东西,还希望给打印什么东西加点前缀,比如这是条信息,这是条错误,因此就有上面的做法。

# 变长参数的处理

00function sumFor(){
01    let argu = Array.prototype.slice.apply(arguments); 
02    return argu.reduce(function(prev, curr, idx, its){
03        return prev+curr; 
04    }, 0);  
05}
06
07sumFor(1); 
08// => 
09// 1
10
11sumFor(1,2,3);
12// =>
13// 6 
14
15// 不用 bind 实现 logger
16function loggerWithOutBind(namespace){ 
17    // namespace 闭包 
18    return function(){
19        var argu = Array.prototype.slice.call(arguments);
20        argu.unshift(namespace); 
21        console.log(argu.join(' '));
22    }
23}
有时候不得不处理变长参数… 这时候需要调用 .apply 或者 .call类数组对象arguments 格式化为数组

# 函数监视 Function Spy

00function Spy(target, method){
01    let pre = target[method]; 
02    var spy = { 
03        count: 0
04    }; 
05
06    target[method] = function(){
07        let argu = Array.prototype.slice.call(arguments);
08        spy.count++; 
09        
10        return pre.apply(target, argu); 
11    }
12
13    return spy; // 闭包 
14}
15
16var spy4consoleLog = Spy(console, 'log'); 
17
18spy4consoleLog.count; 
19// => 
20// 0 
21
22console.log('WoW~ this is 1'); 
23spy4consoleLog.count; 
24// => 
25// 1 
26
27console.log('WoW~ this is 2'); 
28spy4consoleLog.count; 
29// => 
30// 2
这很厉害,用于调试,可以监视很多东西

# 函数柯里化

00function curry3(fun){
01    return function(one){
02        return function(two){
03            return function (three){
04                return fun(one, two, three)
05            }
06        }
07    }
08}
09
10function add3(a, b, c){
11    return a + b + c; 
12}
13
14var add_0 = curry3(add3); 
15var add_1 = add_0(1); 
16var add_2 = add_1(2); 
17
18add_2(3);  
19// =>
20// 6 
21// just like add3(1, 2, 3)
22
23add_2(10); 
24// => 
25// 13
26// just like add3(1, 2, 10)
27
28var add_another = add_1(4); 
29add_another(5); 
30// =>
31// 10 
32// just like add3(1, 4, 5)
33
34curry3(add3)('This is ')('Function ')('Currying');  
35// => 
36// This is Function Currying
函数柯里化就好像是慢慢填充函数的参数一样 最后填满的时候就可以得出答案,没有状态,只有 第几阶段的函数
第 n 阶可以生成无数个第 n+1 阶函数,而且相互没有关系
就像代码里的 add_0, 反复执行 add_0(0) 返回的结果都是与众不同的

# Links

  1. 大量参阅了MDN, 尤其是 Function: MDN -Function
  2. 本文许多代码是受 nodeschool 上的教程启发的 NODESCHOOL - 教你 Web 开发技能的开源课程,自学或者参加一个附近的教学活动
  3. 受此函数式编程教程影响 Functional JavaScript




回到顶部