this 指向
this 指向
this 的指向是 JS 中一个十分重要的知识,理解清楚 this
的指向问题可以帮助我们更好的理解 JS 中代码的运行逻辑,避免许多不必要的错误
什么是this
this 关键字我们在开发需求编写代码时经常会用到它,但是 this
的理解却比较抽象,下面引用一段 w3school 对于 this
关键字的描述:
In JavaScript, the this keyword refers to an object.
Which object depends on how this is being invoked (used or called).
The this keyword refers to different objects depending on how it is used
简单来说,this
可以理解为它是一个对象,代表了函数的调用者(函数的执行上下文),而 this
的值则取决于这个函数是如何被调用的(即运行时绑定)
为什么用this
可以先观察下面一段代码:
//我有一个电话,自带充电器,充电器只能给我的电话充电
const myPhone = {
battery:"50%",
charge:function(){
//将当前电话的电量充满到100%
this.battery = "100%"
}
}
//你也有一个电话,但是没有充电器
const yourPhone = {
battery:"30%"
}
//我的电话电量不足,需要充电
myPhone.charge();
//电量充满了100%
console.log(myPhone.battery); // 100%
//你也想充电,但是不想重新买充电器,只好从我这里借用了充电器
myPhone.charge.call(yourPhone);
//电量也充满了100%
console.log(yourPhone.battery); //100%
从上面的代码可以看出,使用 this
关键字使我们能够读取到当前执行上下文的属性或者方法,使方法的运行结果能够符合我们的预期,极大的方便了我们对于代码的重复利用
确定this指向
this 的指向通常可以分为以下两类:
- 全局环境:
在全局环境中this
指向的是全局对象,浏览器中指向了全局对象window
,NodeJs中指向了全局对象global
,严格模式下则是undefined
- 函数环境: 在函数环境(局部环境)中
this
的指向就要看这个函数是如何被调用的,不同的调用方式会导致this
的指向不同
var name = "John";
const person = {name:"Blob"};
function getName(){
// this = person; //这里会报错,this 指向不能这样更改
console.log(this.name)
}
getName(); // John(this 指向了全局对象 window)
getName.call(person); //Blob (this 指向了普通对象 person)
this
指向是在函数运行时确定的,而不是在函数定义时确定,并且在函数执行时,this
指向一旦被确定就不能再进行更改
改变this指向
函数在被调用时,this 的指向并非是固定不变的,JS 中为我们提供了 3 种方法来改变 this 的指向:
call()
:以给定的this
值和逐个提供的参数调用该函数apply()
:以给定的this
值和作为数组(或类数组对象)提供的arguments
调用该函数bind()
:创建一个新函数,当调用该新函数时,它会调用原始函数并将其this
关键字设置为给定的值,同时,还可以逐个传入参数,这些参数会插入到调用新函数时传入的参数的前面
var count = "0";
const counter = {
count:0,
add:function(num){
this.count += num;
}
}
counter.add(1);
console.log(counter.count); //1
const newCounter = {
count:2
}
// call() 方法改变 this 指向
counter.add.call(newCounter,1);
console.log(newCounter.count); //3
// apply() 方法改变 this 指向
counter.add.apply(newCounter,[1]);
console.log(newCounter.count); //4
const newAdd = counter.add;
//直接调用方法 this 指向全局
newAdd(1);
console.log(this.count); // "01"
console.log(counter.count); //1
// bind() 方法改变 this 指向
const bindAdd = newAdd.bind(newCounter,1);
bindAdd();
console.log(newCounter.count); //5
绑定规则
对函数的调用方式不同,this
指向的绑定也有所不同,可以分为以下几类:
- 默认绑定
- 隐式绑定
- 显式绑定
new
绑定
默认绑定
var name = "foo";
function foo(){
console.log(this.name);
}
foo(); // foo
在上面的例子中,函数 foo
被直接调用,似乎不能直接看出它的调用者,但 foo
函数还是输出了结果,这样的调用方式就是典型的对 this
进行了默认绑定
像上面这样直接调用函数,并且在调用函数时函数名称前面不加 .
修饰符,此时的 this
绑定就只能应用 默认绑定 的规则,将 this
指向全局对象
隐式绑定
const bar = {
name:"bar"
};
const baz = {
name:"baz",
child: {
name:"baz-child"
}
}
function foo(){
console.log(this.name);
}
bar.foo = foo;
bar.foo(); // obj
baz.child.foo = foo;
baz.child.foo(); // baz-child
在上面的例子中,函数 foo
都是作为了对象的方法被调用,由于有了调用者,this
就指向了调用它的对象,这就是 隐式绑定,并且在函数调用时,无论函数名称前面的调用链有多长,this
总是指向最后一个调用者(与函数名称紧挨着的那个)
显式绑定
var baz = {
name:"baz"
}
function foo(){
console.log(this.name);
}
foo.call(baz); //baz
foo.apply(baz); //baz
const bar = foo.bind(baz);
bar(); //baz
在上面的例子中,我们通过使用 call()
、apply()
、bind()
函数来手动更改 this
的指向,将 this
指向了我们所决定的对象,这样的方式就是 显式绑定 ,显式绑定中 this
总是指向这3个函数中传入的第一个参数
new 绑定
function Foo(name){
this.name = name
}
Foo(); //全局对象
const foo = new Foo("foo");
console.log(foo.name); //foo
在上面的例子中,通过 new
关键字创建了一个对象,使函数的 this
指向了这个新创建的对象,这样的方式就是 new 绑定
在 JS 中,由 function
关键字声明的函数都可以当作构造函数来使用,通过 new
关键字来创建它的实列对象。更多关于 new
的信息请点击这里
绑定优先级
function Foo(name){
this.name = name;
}
const obj1 = {
foo:Foo
}
const obj2 = {}
obj1.foo("obj1");
console.log(obj1.name); //obj1 //隐式绑定
obj1.foo.call(obj2,"obj2");
console.log(obj2.name); //obj2 //显式绑定
const obj3 = new obj1.foo("obj3");
console.log(obj3.name); //obj3 //new绑定
console.log(window.name); // ""
通过以上代码可以看出 this
绑定的优先级为:new
绑定 > 显式绑定 > 隐式绑定 > 默认绑定