你真的弄懂this指向了吗
前言
在说 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);
转载自:https://juejin.cn/post/7208381011608617018