这是terminal的第7篇原创整理
预备知识:this指向,作用域
对于bind
,apply
和call
方法,已经是前端面试官乐此不疲的考察内容了。虽然 ES6 对于this
的处理已经改善很多,但是这些内容并不会很快被抛弃,不管是面试还是维护老代码,掌握它依然是必要的。
相同点-修改this
至于为什么这三个方法要放到一起对比,因为他们都有一个相同的重要作用:
- 改变函数的执行环境,也就是
this
指向
通过代码简单示范:
function say() {
console.info("name", this.name);
}
var personA = {
name: "A",
say,
};
var personB = { name: "B" };
say();
personA.say();
personA.say.call(personB);
personA.say.apply(personB);
personA.say.bind(personB)();
//name undefined
//name A
//name B
//name B
//name B
上面的代码,say
方法直接调用,其中的this
是全局对象。只有正常调用personA
的say
方法的时候才会输出A
。如果要输出B
但是personB
中并没有say
方法,要怎么做?
可以通过三种方式,call,apply,bind 来完成。call
和apply
将函数内部的this
改变指向,指向了对应函数的传入的第一个参数,也就是personB
,这是的say
方法中的this.name
就是personB
的name
。故输出B
。可能你也注意到,bind
函数传入personB
调用之后后面还有一个调用,因为bind
方法改变this
的时候并不执行该函数,而是返回一个新的函数。
call/apply 与 bind
如上,bind
的返回和call/apply
调用并不一样,**bind
调用之后是返回一个新的函数的,该函数的this
是已经修改过的,指向传递进bind
函数的第一个参数,如果函数需要执行的话,需要自己再额外调用一次新的函数。而call
,apply
这两个方法在调用之后,函数是直接执行的。**这就是call/apply
和bind
方法最大的区别。
而对于call
和apply
方法,依然存在的相同点和不同点,call
和apply
方法的第一个参数都是调用call/apply
后的函数的this
指向。区别就是两者接收其他参数的方式,call
方法是接受若干个参数列表,而apply
方法接受的是一个数组,数组的元素是各个参数。
call 方法::
function.call(thisArg, arg1, arg2, ...)
thisArg
并不是必须的,不传值或者值为undefined/null
的时候函数的this
指向全局对象。
apply 方法:
function.apply(thisArg, [argsArray])
apply 第一个参数则是必须的,第二个参数(数组)中的元素会作为单独的参数传给函数。
call
和apply
的返回值是改变this
后的函数的返回值。
下面是一个call
和appply
的例子:
function say(age, gender) {
console.info("name/age/gender", this.name + "/" + age + "/" + gender);
}
var personA = {
name: "A",
say,
};
var personB = { name: "B" };
personA.say.call(personB, 20, "man");
personA.say.apply(personB, [24, "woman"]);
手动实现 bind/call/apply 方法
了解了call/aplly/bind
方法的作用和特点,为了更好的理解,我们可以手动实现一遍
- call 方法,先看一个常规的
call
方法使用例子:
const a = { name: "xw" };
function say(gender, age) {
const info = "name/gender/age:" + this.name + "/" + gender + "/" + age;
console.info(info);
return "返回值:" + info;
}
const data = say.call(a, "man", 20);
console.info(data);
//name/gender/age:xw/man/20
//data 返回值:name/gender/age:xw/man/20
say
通过call
调用,say
自身可以接受两个参数,用于输出,并且具有返回值。可以像这样实现:
Function.prototype.MyCall = function (context) {
var env = context || window; //如果没有传入就指向全局
env.fun = this; // this就是调用Mycall的函数,将其挂在env上。运行时就是say函数
var args = [...arguments].slice(1); //获取第二个开始的参数
var result = env.fun(...args); // 执行函数并传参,相当于say(...args)
delete env.fn;
return result;
};
实现完之后调用一下:
const data = say.MyCall(a, "man", 20); //name/gender/age:xw/man/20
console.info("data", data); // name/gender/age:xw/man/20
执行输出和返回值和上面的call
方法调用无异。
- apply 方法。
apply
的实现和call
很相似,只是参数的格式是数组。
Function.prototype.MyApply = function (context) {
var env = context || window;
env.fun = this;
var result;
if (arguments[1]) { //需要对第二个参数(数组)用到展开,加一层判断
result = env.fun(...arguments[1]);
} else {
result = env.fun();
}
delete env.fn;
return result;
};
//调用
const data = say.MyApply(a, ["man", 20]);
console.info("data", data);
结果和上面一样,且在不传其他参数的时候正常执行。
- bind 方法。区别于
apply
和call
会直接执行函数,bind
则是会返回函数的引用,在传参上还有一个区别,就是**bind
是支持柯里化形式传参的**。
柯里化: 将接受多个参数的函数转换成接受一个单一参数的函数,该函数返回一个接受剩余参数且返回结果的新函数。
也就是说,如果一个函数的调用传参是 funtionA(a,b),那么柯里化之后的函数 functionB,应该这样调用 functionB(a)(b),其效果不变
Function.prototype.MyBind = function (context) {
var inThis = this;
var args = [...arguments].slice(1);
return function fun() {
if (this instanceof fun) { //区分是否构造函数
return new inThis(...args, ...arguments); // 支持柯里化,两次获取参数
} else {
return inThis.apply(context, args.concat(...arguments));
}
};
};
然后进行调用测试,也是符合预期的:
const a = { name: "xw" };
function say() {
console.info("name is", this.name);
}
say.MyBind(a)();
// name is xw
总结
call,apply 和 bind 都用来改变函数的this
指向,指向call/apply/bind
函数的第一个参数,其中call/apply
调用时会直接执行该函数,而bind
并不会直接运行函数,而是返回一个新的函数,需要手动调用才会执行。apply
接受的其他参数是一个数组,而call
函数是直接传多个参数的(一个参数列表)。
以上便是对 bind/call/apply 方法的使用总结,觉得不错的给点个赞吧~(关注即送前端资源包)
推荐阅读:
关于 this 指向的问题ES6 系列(五):Generator 和迭代协议