call,apply,bind

前言

javascript是动态编程语言,存在创建时上下文,运行时上下文,并且这些上下文都是可以动态改变的。而call,apply和bind的作用就是动态的改变上下文。

call、apply和bind的作用都差不多,只是使用上有点小区别。

call和apply

call,apply都属于Function.prototype的一个方法,他是javascript引擎内在实现的,因为属于Function.prototype,所以每个Function对象实例,都有call,apply属性。其实他们的作用是一样的,只是传递的参数不一样而已。

  • apply:接受2个参数,第一个参数指定了函数体内this对象的指向,第二个参数为数组或者一个类数组。apply传入的是一个参数数组,也就是将多个参数组合成为一个数组传入。
  • call:接受2个参数,第一个参数指定了函数体内this对象的指向,后边的参数是函数调用时的参数按顺序传递。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
let obj1 = {
name: "husbin",
getName: function(){
return this.name;
}
}
let obj2 = {
name: 'wzb'
}

console.log(obj1.getName()); // "husbin"
console.log(obj1.getName.call(obj2)); // "wzb"
console.log(obj1.getName.apply(obj2)); // "wzb"
1
2
3
4
5
6
function showArgs(a, b, c){
console.log(a,b,c);
}

showArgs.call(this, 3,4,5);
showArgs.apply(this, [5,6,7]);

面试题:定义一个 log 方法,让它可以代理 console.log 方法:

去面试的时候,面试官问我怎么使用传进去的参数,我说直接使用,他说不对,是通过一个arguments数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 常规做法
function log(msg){
console.log(msg);
}
log(12); //12
log(1,2); //1

//上面能够基本解决打印输出问题,但是下面多参数的时候就gg了。换个更好的方式吧。
//去面试的时候,面试官问我怎么使用传进去的参数,我说直接使用,他说不对,是通过一个arguments数组
function log(){
console.log.apply(console, arguments);
}
log(12); //12
log(1,2); //1,2

//接下来是要在每一条打印信息前面都要加上一个特定字符串'Error'又怎么说呢?
function log(){
let args = Array.prototype.slice.call(arguments);
args.unshift('Error');
console.log.apply(console, args);
}
log(12);
log(1,2);

bind

bind方法与apply和call很相似,也是可以改变函数体内this的指向。

MDN的解释是:bind()方法会创建一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以创建它时传入 bind()方法的第一个参数作为 this,传入 bind() 方法的第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
var func = function(){
console.log(this.x);
console.log(arguments);
}


func(); // undefined, {}
var obj = {
x: 2
}
var bar = func.bind(obj,1);

bar(); // 2 , {'0':1}
  • 在Javascript中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的。

这三个方法的异同

举个例子:

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

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

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

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

总结

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

实现apply、call(简化版)

  • 将函数设为对象的属性
  • 执行该函数
  • 删除该函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//call
Function.prototype.call2 = function (context,...args) {
// 首先要获取调用call的函数,用this可以获取
context.fn = this;
context.fn(...args);
delete context.fn;
}
// 测试一下
var foo = {
value: 1
};
function bar(name,age) {
console.log(name);
console.log(age);
console.log(this.value);
}

bar.call2(foo,'wzb','21'); // 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//apply
Function.prototype.apply = function (context, arr) {
var context = Object(context) || window;
context.fn = this;

var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}

delete context.fn
return result;
}

实现bind(简化版)

bind函数有两个特点:

  • 返回一个函数
  • 可以传入参数

返回函数模拟实现

修改this指向,可以使用call或者apply实现

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

传参模拟实现

1
2
3
4
5
6
7
8
9
10
Function.prototype.bind2 = function (context) {
var self = this;
// 获取bind2函数从第二个参数到最后一个参数,这里的参数是bind的时候传入的
var args = Array.prototype.slice.call(arguments, 1);
return function () {
// 这个时候的arguments是指执行bind返回的函数传入的参数
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(context, args.concat(bindArgs));
}
}

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
console.log(name);
console.log(age);
}
var bindFoo = bar.bind2(foo, 'daisy');
bindFoo('18');
// 1
// daisy
// 18

Reference – 前端基础:call,apply,bind的的理解

Reference – JavaScript深入之call和apply的模拟实现

Reference – JavaScript深入之bind的模拟实现