bind、call、apply

JS中的函数也是一种对象,但是并不是所有的对象都是函数。函数中拥有 call、apply、bind三个原型方法。call()apply()可以看作为某个对象的方法,通过调用方法的形式来间接调用函数。

如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是此次调用的上下文, 也就是该函数的this的值。

每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称作“原型对象”。 每一个函数都包含不同的原型对象。当将函数用作构造函数的时候,新创建的对象会从原型对象上继承属性。

bind

将函数绑定到某个对象,将返回一个新的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};

// 此时 this 的指向是 module
module.getX(); // 返回 81

// 在这种情况下,"this"指向全局作用域
var retrieveX = module.getX;
retrieveX(); // 返回 9,

// 创建一个新函数,将"this"绑定到module对象
// 新手可能会被全局的x变量和module里的属性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

再来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Foo = {
count: 1,

getCount: function() {
return this.count;
}
};

console.log(Foo.getCount()); // 1

// 此时 func 指向全局变量
var func = Foo.getCount;
console.log(func()); // undefined

那么进行多次 bind() 呢?

1
2
3
4
5
6
7
8
9
10
11
var bar = function() {
console.log(this.x);
}
var foo = { x:3 }
var sed = { x:4 }
var fiv = { x:5 }

var func = bar.bind(foo).bind(sed);
func(); //?
var func = bar.bind(foo).bind(sed).bind(fiv);
func(); //?

以上输出什么?答案是,两次都仍将输出 3 ,而非期待中的 4 和 5 。原因是,在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

其实真的没什么大不了的,看看 bind 的实现就明白了:

1
2
3
4
5
6
Function.prototype.bind = Function.prototype.bind || function(context){
var self = this;
return function(){
return self.apply(context, arguments);
};
}

call 与 apply

在 JavaScript 中 callapply 都是为了改变函数运行时的上下文而存在的,也就是说为了改变函数体内部 this 的指向。其语法:fun.call(thisArg[, arg1[, arg2[, ...]]])。可以让 call() 中的对象调用当前对象所拥有的function,可以用这种方法来实现继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Foo() {}
Foo.prototype = {
color: 'red',
say: function() {
console.log('this color is: ' + this.color);
}
}

var foo = new Foo;
foo.say();

// 此方法也想调用 say(),又不想从新定义 say 方法。可以 通过 call、apply 来实现。
bar = {
color: 'cyan',
}

// 可以看出,call、apply 是为了动态改变 this 而出现的。
foo.say.call(bar);
foo.say.apply(bar);

callapply 的功能是一样的,唯一的不同是接收参数方式不一样:

1
2
3
var func = function() {};
func.call(this, arg1, arg2);
func.apply(this, [arg1, arg2]);

func函数运行时指定 this。该 this 并不是真正的 this 值,在严格模式下为全局对象(在浏览器中为window)。非严格模式下为 undefined

三者区别

apply、call、bind比较:

1
2
3
4
5
6
7
8
9
10
11
var obj = { x: 10 };

var foo = {
getX: function() {
return this.x;
}
}

console.log(foo.getX.bind(obj)()); // 10
console.log(foo.getX.call(obj)); // 10
console.log(foo.getX.apply(obj)); // 10

三个输出的都是10,但是注意看使用 bind() 方法的,他后面多了对括号。

也就是说,区别是,当你希望改变上下文环境之后并非立即执行,而是回调执行的时候,使用 bind() 方法。而 apply/call 则会立即执行函数。

再总结一下:

apply 、 call 、bind 三者都是用来改变函数的this对象的指向的;
apply 、 call 、bind 三者第一个参数都是this要指向的对象,也就是想指定的上下文;
apply 、 call 、bind 三者都可以利用后续参数传参;
bind 是返回对应函数,便于稍后调用;apply 、call 则是立即调用 。


参考

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call
https://segmentfault.com/a/1190000000375138?page=1
http://web.jobbole.com/83642/
http://yanhaijing.com/basejs/index.html#sect_var_scope_closures