# bind函数

bind函数是非常常见的绑定函数作用域的方法,那bind都有哪些特性,又是如何实现的呢?下面我们就来探索一下。

# bind函数的特性

测试函数

function myTest () {
  console.log(this.a);
}
myTest.prototype.walk = function () {
  console.log('walk');
}
1
2
3
4
5
6

# 为函数绑定作用域

let obj = {
  a: 30
}
let c = myTest.bind(obj);
c(); // 30
1
2
3
4
5

这里,我们为函数myTest绑定作用域为obj,所以输出的this.aobj.a为30;

# 函数柯里化

修改源函数为接受一个参数的函数,并执行打印。

function myTest (arg) {
  console.log(this.a);
  console.log(arg);
}
1
2
3
4
let obj = {
  a: 30
}
let c = myTest.bind(obj);
c(10); // 30 10

let d = myTest.bind(obj, 10)(); // 30 10
1
2
3
4
5
6
7

bind绑定之后的函数,具备了函数柯里化的特性。

# 对new无效

let obj = {
  a: 30
}
let c = myTest.bind(obj);

c(); // 30

let cc = new c(); // undefined
cc.walk(); // walk

let d = new myTest(); // undefined
d.walk(); // walk
1
2
3
4
5
6
7
8
9
10
11
12

上面的代码结果,我们发现,new操作符修饰后的绑定函数和未绑定函数的表现是一样的,由此可以得出 bind函数对new操作是无效的。

# 实现bind函数

  • bind方法是函数方法特有的属性,所以bind是定义在Function.prototype上的;
  • bind方法返回的是一个函数;
  • bind方法的函数参数是可传递的;
Function.prototype.bind = function () {
  // 数组方法
  let slice = Array.prototype.slice;
  
  let fn = this;

  let args = arguments;
  // 绑定的作用域
  let context = args[0];
  // 剩余参数
  let restArgs = slice.call(args, 1);

  let fun = function () {
    // 组合bing参数
    let allArgs = restArgs.concat(slice.call(arguments));
    // 返回原函数在context作用域下的执行结果
    return fn.apply(context, allArgs);
  }

  return fun;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

执行上述的函数测试,我们可以发现,我们实现的bind函数对new操作符是有效的,但是无法访问原函数 原型链上的属性和方法。

let obj = {
  a: 30
}
let c = myTest.bind(obj);

let cc = new c(); // 30
cc.walk(); // 报错
1
2
3
4
5
6
7

bind 所以,我们需要对new操作符做特殊处理:

  • 1、在调用函数执行时,检查是否是new操作符创造的实例,如果是,则将作用域设置为this
  • 2、修正返回函数的原型为原函数逇原型
















 


 




Function.prototype.bind = function () {

  let slice = Array.prototype.slice;

  let fn = this;

  let args = arguments;

  let context = args[0];

  let restArgs = slice.call(args, 1);

  let fun = function () {

    let allArgs = restArgs.concat(slice.call(arguments));

    return fn.apply(this instanceof fun ? this : context, allArgs);
  }

  fun.prototype = Object.create(this.prototype);

  return fun;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

测试:

let obj = {
  a: 30
}
let c = myTest.bind(obj);

c();

let cc = new c();
cc.walk();

let d = new myTest();
d.walk();
1
2
3
4
5
6
7
8
9
10
11
12

bind

# 官方bind函数实现

'use strict';

/* eslint no-invalid-this: 1 */

var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible ';
var slice = Array.prototype.slice;
var toStr = Object.prototype.toString;
var funcType = '[object Function]';

module.exports = function bind(that) {
    var target = this;
    if (typeof target !== 'function' || toStr.call(target) !== funcType) {
        throw new TypeError(ERROR_MESSAGE + target);
    }
    var args = slice.call(arguments, 1);

    var bound;
    var binder = function () {
        if (this instanceof bound) {
            var result = target.apply(
                this,
                args.concat(slice.call(arguments))
            );
            if (Object(result) === result) {
                return result;
            }
            return this;
        } else {
            return target.apply(
                that,
                args.concat(slice.call(arguments))
            );
        }
    };

    var boundLength = Math.max(0, target.length - args.length);
    var boundArgs = [];
    for (var i = 0; i < boundLength; i++) {
        boundArgs.push('$' + i);
    }

    bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder);

    if (target.prototype) {
        var Empty = function Empty() {};
        Empty.prototype = target.prototype;
        bound.prototype = new Empty();
        Empty.prototype = null;
    }

    return bound;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
上次更新: 5/25/2020, 5:50:59 PM