手撕代码之call、apply和bind

三兄弟的作用

call、apply和bind三个方法都是为了改变函数运行时上下文(即this指向)而存在的,来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const language = {
name: "java",
getName: function() {
console.log(`${this.name} is the best language in the world!`);
}
};
const weber = {
name: "javascript"
};
//谁是世界上最好的语言?
language.getName();
language.getName.call(weber);
language.getName.apply(weber);
language.getName.bind(weber)();

执行上面的代码,结果为:

1
2
3
4
java is the best language in the world!
javascript is the best language in the world!
javascript is the best language in the world!
javascript is the best language in the world!

三兄弟的区别

  • call、apply和bind方法接收的第一个参数都是要绑定的this对象。
  • apply方法的第二个参数是一个参数数组,call和bind方法的第二个及之后的参数作为函数实参按顺序传入。
  • bind方法不会立即调用,call和apply方法都会立即调用。

来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
const developer = {
getSkills: function(...args) {
console.log(...args);
}
};
const weber = {
skiller: ["HTML"]
};
developer.getSkills.call(weber, "HTML5", "CSS3", "javascript");
developer.getSkills.apply(weber, ["HTML5", "CSS3", "javascript"]);
developer.getSkills.bind(weber)("HTML5", "CSS3", "javascript");

执行上面的代码,结果为:

1
2
3
HTML5 CSS3 javascript
HTML5 CSS3 javascript
HTML5 CSS3 javascript

call方法的模拟实现

首先要理清思路:

  • 函数定义在哪里?call方法是可以被所有方法调用的,所以毫无疑问要定义在Function的原型上。
  • 函数接收参数?绑定函数被调用时只传入第二个参数及之后的参数。
  • 如何显式绑定this?如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

理清了思路,开撸:

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myCall = function(context) {
if (typeof this !== "function") { //验证当前被调用的是否是方法
throw new TypeError("not function");
}
const cxt = context || window;
cxt.fn = this; // 将当前被调用的方法定义在cxt.fn上,为了能以对象调用的形式绑定this
const args = [...arguments].slice(1); //获取实参
const res = cxt.fn(...args); //以对象调用的形式调用fn,此时this指向cxt,也就是传入的需要绑定的this指向
delete cxt.fn; //删除该方法,不然会对传入对象造成污染(添加该方法)
return res;
};

apply方法的模拟实现

apply方法实现的思路与call方法基本相同,我们只需要对参数进行不同处理即可。

1
2
3
4
5
6
7
8
9
10
Function.prototype.myApply = function(context) {
if (typeof this !== "function") { //验证当前被调用的是否是方法
throw new TypeError("not function");
}
const cxt = context || window;
cxt.fn = this; // 将当前被调用的方法定义在cxt.fn上,为了能以对象调用的形式绑定this
const res = arguments[1] ? cxt.fn(...arguments[1]) : cxt.fn(); //以对象调用的形式调用fn,此时this指向cxt,也就是传入的需要绑定的this指向
delete cxt.fn; //删除该方法,不然会对传入对象造成污染(添加该方法)
return res;
};

bind方法的模拟实现

首先要理清思路:

  • 函数定义在哪里?bind方法是可以被所有方法调用的,所以毫无疑问要定义在Function的原型上。
  • 函数接收参数?bind方法返回一个绑定函数,最终调用需要传入函数实参和绑定函数的实参。
  • 如何显式绑定this?如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。

我模拟实现的bind方法代码如下:

1
2
3
4
5
6
7
8
9
10
11
Function.prototype.myBind = function(context) {
if (typeof this !== "function") { //验证当前被调用的是否是方法
throw new TypeError(this + "isn't a function!");
}
const _this = this;
const args = [...arguments].slice(1);
return function fn() {
//处理函数使用new的情况
return this instanceof fn ? new _this(...args, ...arguments) : _this.myApply(context, args.concat(...arguments));
};
};

更多bind方法的实现详见:
面试官问:能否模拟实现JS的bind方法?
bind方法的实现

经测试,模拟实现的myCall、myApply和myBind方法和call、apply和bind方法效果一致。到这里,我们已经模拟实现了自己的三兄弟方法,如果你有任何的问题,欢迎评论区留言,你的支持是我前进的最大动力。

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2021 Sanmu
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信