前言
在说 this 指向之前,请观察以下代码,并说出它们的输出结果:
第 1 组:标准函数
window.color = "red";
let o = {
color: "blue",
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 输出值是什么?
o.sayColor = sayColor;
o.sayColor(); // 输出值是什么?
第 2 组:箭头函数
window.color = "red";
let o = {
color: "blue",
};
let sayColor = () => console.log(this.color);
sayColor(); // 输出值是什么?
o.sayColor = sayColor;
o.sayColor(); // 输出值是什么?
一、标准函数和箭头函数的 this 指向
先公布一下以上函数的输出结果,不知道屏幕前的你答对了吗?
第 1 组输出结果如下
sayColor(); // 'red'
o.sayColor(); // 'blue'
第 2 组输出结果如下
sayColor(); // 'red'
o.sayColor(); // 'red'
为什么会呈现出这样的输出结果呢?
标准函数中,this
指向的是把函数当成方法调用的上下文对象,在网页的全局上下文中调用函数时,this
指向 window
,因此 this 指向是会发生改变的
箭头函数中,this
指向的是定义箭头函数的上下文对象,箭头函数在哪个对象的上下文中定义的,this
就指向谁,,箭头函数中,this 的指向是固定的
二、改变 this 指向
我们知道,函数其实是一个对象,因此它有属性和方法。每个函数都有两个属性:length
和 prototype
。其中 length 属性保存函数定义的命名参数的个数,prototype 属性保存引用类型所有实例和方法。call
、apply
、bind
,就是保存在 prototype
中的方法。
前面已经提到,标准函数
和箭头函数
的默认 this 指向,但是在某些时候,我们需要改变 this
的 默认指向
,这时候我们就需要用到 call
、apply
、bind
方法,这 3 个方法都可以用来改变 this 指向
,接下来我们来详细介绍这 3 个方法。
1. call
call()
方法会以指定的 this 值
来调用函数,即会设置调用函数时函数体内 this 对象
的值。 call() 方法接收两个参数,第 1 个参数是函数内的 this 值,第 2 个参数是调用该函数需要传入的值。第 1 个参数是必填项,第 2 个参数可以不填。
call(this, num);
以前言中的代码举例,观察 this 的变化
window.color = "red";
let o = {
color: "blue",
};
function sayColor() {
console.log(this.color);
}
sayColor(); // 'red'
sayColor.call(o); // 'blue'
sayColor
是一个标准函数,它的 this
指向是调用时的上下文对象
,我们是在全局调用的,因此它的上下文对象应该是 window
,所以 sayColor()
输出 'red'
是毫无疑问的。但是 sayColor.call(o) 为什么会输出 'blue'
呢?虽然它也是在全局上下文中调用的,但是我们利用 call()
方法,给它指定了一个新的 this 值
,也就是对象 o
,所以这里的 this.color
指的是对象 o
中的 color
。
call()
方法传值需要将参数一个一个列出来,用 ','
分开,从第 2 个参数开始,看下面示例:
function sum(num1, num2) {
return num1 + num2;
}
num.call(this, 10, 20); // 30
2. apply
apply()
方法和 call()
作用一样,第一个参数也是指定 this 值
,但是参数需要是 Array 实例
或者 arguments 对象
,看下面示例:
function sum(num1, num2) {
return num1 + num2;
}
function callSum1(num1, num2) {
return sum.apply(this, arguments);
}
function callSum2(num1, num2) {
return sum.apply(this, [num1, num2]);
}
console.log(callSum1(10, 20)); // 30
console.log(callSum2(10, 20)); // 30
从示例中可以看出,无论是传入类数组对象还是数组,可以进行正常的传值。call()
和 apply()
真正强大的地方在于,它们可以控制函数调用上下文,可以将任意对象设置为任意函数的作用域。
3. bind
与 call()
和 apply()
不同,bind()
方法会新创建一个函数实例,这个新函数的 this 值
,会绑定到传给 bind() 的对象。下面来看示例:
window.color = "red";
let o = {
color: "blue",
};
function sayColor() {
console.log(this.color);
}
let objectSayColor = sayColor.bind(o);
objectSayColor(); // 'blue'
在这里,我们在 sayColor()
上调用了 bind()
方法,并传入了一个对象 o
,创建了一个新的函数 objectSayColor()
,指定其作用域为对象 o
。所以,我们在任意地方调用该函数,输出的都是字符串 'blue'
。
4. 三者之间的区别
通过以上的分析,我们已经知道,call()
、apply()
、bind()
都可以用来修改 this 指向
。它们的区别如下:
call()
、apply()
是直接修改原函数的this 值
,不会创建新的函数call()
传值需要将值一个一个列出来,用','
隔开apply()
只能传入数组
格式或者类数组
格式的值bind()
会返回一个修改过this 值
的新函数
三、总结
call()
、apply()
、bind()
是为了灵活改变函数 this 指向
而发明的,他们的主要用途也是用来改变 this 指向。当然,由于这些方法本身的特性,也可以用来做些其它的事情,比如计算数组最大值、最小值等。
不过,这个用途并不广泛,因为解构赋值可以更轻松达到这个目的。
const arr = [1, 2, 3, 4, 5];
Math.max.apply(this, arr);
Math.min.apply(this, arr);
Math.max(...arr);
Math.min(...arr);