bind、call、apply的实现

bindcallapply是JavaScript中Function.prototype非常重要的三个方法,他们的作用是改变this的指向。三者的区别是:

bind返回一个函数,该函数改变了this的指向。
call直接调用函数,也可以传递参数用逗号隔开。
apply直接调用函数,也可以传递参数使用数组传递给第二个参数。

我们现在详细解读一下各个函数的实现方式。

bind的实现

bind的基本用法:

  1. 函数调用bind,返回一个新的函数。
  2. bind方法的第一个参数是宿主对象,也就是执行的this
  3. bind返回函数执行时候的参数是bind方法第二个至多个参数与调用时参数的合集。

bind简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.bind = function (context) {
var self = this;// 这个this其实是真正的函数
// 获取第二至多个参数
var args = Array.prototype.slice.call(arguments, 1);
// 返回一个函数 该函数是真正执行时的函数
return function () {
// 获取真正执行时传进来的函数
var bindArgs = Array.prototype.slice.call(arguments);
// 合并参数 并且调用函数
return self.apply(context, args.concat(bindArgs));
}
}

这个简单的bind已经解决了上面的三个基本用法了,其实bind还有2个附加的特性:

  1. 如果上面self不是函数(防止非函数的原型指向Function.prototype),那么会报错。
  2. 从改变this的指向来看,new的优先级大于bind

功能更强大的bind实现:

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.bind = function (context) {

var self = this;
if (typeof self !== "function") {// 如果self不是函数则报错
throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
}

var args = Array.prototype.slice.call(arguments, 1);

// 定义一个中间函数 让返回的函数继承它
var FN = function () {};

var returnFn = function () {
var bindArgs = Array.prototype.slice.call(arguments);
// 重点 如果返回函数的this是FN 说明是new出来的 this就是new的对象 否则是context
return self.apply(this instanceof FN ? this : context, args.concat(bindArgs));
}

// FN的原型指向函数的原型
FN.prototype = self.prototype;
// 返回的函数继承FN
returnFn.prototype = new FN();
return returnFn;
}

call的实现

call的基本用法:

  1. 函数调用call,函数会执行,并且this指向了第一个函数。
  2. call方法的第一个参数是null或者undefined的时候this会绑定在全局对象上。
  3. call方法第二个至多个参数会传给执行的方法。

call方法简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.call = function (context) {
// 如果为空 则绑定在全局函数
var context = context || window;
// 使用context.fn() 来调用函数 来模拟fn绑定在context上
context.fn = this;

var args = [];
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}

// 这里比较尴尬 因为context.fn调用的时候 无法把不定长的arguments 一个一个地传进去
// 所以就使用eval方法了 当然可以用apply但是稍后我们也要实现它 所以就不能用了
var result = eval('context.fn(' + args +')');

// 删除添加的方法(毁尸灭迹)
delete context.fn;
return result;
}

apply的实现

apply的基本用法:

  1. 函数调用apply,函数会执行,并且this指向了第一个函数。
  2. apply方法的第一个参数是null或者undefined的时候this会绑定在全局对象上。
  3. apply方法第二个参数是一个数组,相当于函数执行时的参数。

apply方法简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.apply = function (context, arr) {
var context = 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;
}

-------------本文结束 感谢您的阅读-------------
0%