call,apply,bind的作用

都是Function的原型方法

Function.prototype.bind()

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

Function.prototype.apply()

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

Function.prototype.call()

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

call() 方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

bind() 方法和call方法的不同之处在于,bind() 方法返回一个新的函数,而call方法会直接调用此函数。

call 的模拟实现

Function.prototype.call()

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

我们所要做的和注意的有以下几点:

  1. call 函数是 Function 的一个原型方法
  2. call 改变了原函数 this 的指向,this 变成了给定的第一个参数
  3. call 函数的第一个参数应该是对象或者能转换成对象
  4. 该函数执行了,并返回了结果
  5. 给出多个参数时,从第二个参数到最后一个参数作为函数的实参进行调用
  6. call 的第一个参数可以传 null/undefined,当为 null/undefined 的时候,视为指向全局对象

实际实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.call = function(context) {
var context = Object(context) || window; // 1 当context为 字符串、数字或布尔值时进行转换 2 传null/undefined时指向window
var id = 0;
while (context['_fn' + id]) {
id++;
}
context['_fn' + id] = this; // 为 context 添加新方法,避免context已有同名方法需要找到不重复的函数名

var args = [];
for (var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
} // 拼接从第二个参数到最后一个参数

var result = eval('context._fn' + id + '(' + args + ')'); // 调用eval执行函数,args 会自动调用 toString()转换为字符串
delete context['_fn' + id]; // 删除添加的属性
return result; // 返回函数执行结果
}

apply 的模拟实现

Function.prototype.apply()

apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。

apply的模拟实现和call类似,需要注意的是apply的第二个参数是一个数组(或类似数组对象)。

实际实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Function.prototype.apply = function(context, arr) {
var context = Object(context) || window;
var id = 0;
while (context['_fn' + id]) {
id++;
}
context['_fn' + id] = this;

var result;
if (!arr) {
result = context['_fn' + id]();
} else if (typeof arr !== 'object') {
throw TypeError('CreateListFromArrayLike called on non-object');
} else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context._fn' + id + '(' + args + ')');
}

delete context['_fn' + id];
return result;
}

bind 的模拟实现

Function.prototype.bind()

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

我们所要做的和注意的有以下几点:

  1. bind 函数是 Function 的一个原型方法
  2. 直接调用时 bind 改变了 this 的指向,this 变成了给定的第一个参数
  3. bind 函数的第一个参数应该是对象或者能转换成对象
  4. bind 函数返回的是一个函数而不是函数执行的结果
  5. 给出多个参数时,从第二个参数到最后一个参数作为函数的实参进行调用
  6. bind 返回的函数在调用时,可以继续添加参数
  7. 当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。

实际实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Function.prototype.bind = Function.prototype.bind || function (context) {

if (typeof this !== "function") { //
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}
var self = this;
var slice = Array.prototype.slice;
var args = slice.call(arguments, 1); // 获取bind函数从第二个参数到最后一个参数

var fNOP = function () {};

var fBound = function () {
var bindArgs = slice.call(arguments); // 这个时候的 arguments 是指 fBound 函数传入的参数
return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs)); // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
}

fNOP.prototype = this.prototype;
fBound.prototype = new fNOP(); // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值
return fBound;
}

es5-shim

完整的模拟实现参考 es5-shim