跳至主要內容

this 指向

wzCoding大约 5 分钟JavaScriptthis 指向

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
ℹ️提示

更多关于 callapplybind 的信息请点击这里:

绑定规则

对函数的调用方式不同,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绑定 > 显式绑定 > 隐式绑定 > 默认绑定