JavaScript中的 this 到底指向哪里

在我们编写js代码过程中,经常会用到this这个关键字,但是如果对于this这个关键字缺乏清晰系统的认识,常常会让我们感到混乱。

为什么要使用this关键字

通常情况下我们会编写如下的代码

function sayHi(){
    console.log(`Hi , My name is ${this.name}`)
}
const me={name:"王章昊"}
sayHi.call(me);//输出 Hi , My name is 王章昊
// 或者
const you={name:"无名氏",sayHi:sayHi}
you.sayHi();//输出 Hi , My name is 无名氏

如果我们没有this这个关键字,上面的功能我们就需要显式的传入一个上下文对象

function sayHi(context){
    console.log(`Hi , My name is ${context.name}`)
}
const me={name:"王章昊"}
const you={name:"无名氏"}
sayHi(me);//输出 Hi , My name is 王章昊
sayHi(you);//输出 Hi , My name is 无名氏

随着我们项目模块越来越复杂,显式的传递上下文对象会让代码越来越混乱,this就是为我们提供了一个更为简洁且易用的模式,前提是我们对this的机制有一个明确的了解,否则在一些复杂的情况下我们很难分析出this到底指向什么

this在不同情况下的指向

ES6发布了新的箭头函数,而箭头函数中this的指向和function定义的函数中完全不一样,我们主要是从以下几个方向去分析this指向问题

首先我们要先了解,通过var关键字直接定义变量,变量会被绑定在全局的window对象上。

在函数外使用this

函数外使用this举几个例子,我们在浏览器控制台执行如下代码

var name = 'wzh';
var arr=[this.name];
var arr2=[name];
var obj={name:this.name,name2:name};
console.log(arr,arr2,obj);
// 打印结果  ["wzh"] ["wzh"] {name: "wzh", name2: "wzh"}

由上面的分析我们可以得出,在函数外直接使用this,this指向的就是window对象,通过this访问变量和直接访问是一样的

在function函数中使用this

function定义的函数的位置取决于被调用的时机。

  • 直接被调用。下面的代码等同于say.call(window),而call的作用就是将第一个参数作为函数的this并执行函数。因此输出的是wzh。
var name="wzh"
function say(){
    console.log(this.name)
}
say();// wzh
//  还有自执行函数 下面的写法就等于上面先定义再执行,也是属于直接调用
(function say(){
    console.log(this.name)
})()
  • 作为对象属性被调用。下面代码中say函数是通过obj对象调用的,等同与obj.say.call(obj),call函数将obj对象作为say函数的this并执行。
var name='wzh';
const obj={
    name:'王章昊',
    say:function(){
       console.log(this.name)
    }
}
obj.say();// 王章昊
  • 作为参数传入另一个函数中被调用

当我们将一个函数a作为参数传入另一个函数b后,a中的this指向情况仅仅取决与在b中是如何调用a的,是直接使用还是又将其作为一个对象的属性了。而和我们是如何将a传入b没有任何关系,我们可以使用函数名,匿名函数,或者将一个对象的函数方法传进去都是一样的。

var name=1;
function say(){
 console.log(this.name)
}
say();// 1 直接被调用
const obj={
   name:2,
   say:say
} 
obj.say();//2 通过对象调用

function foo(fn){
  fn()
}
foo(say);//1 
foo(obj.say);//1 
// 上面两种方式都等于是只把函数传递进去 等同于
foo(function(){
  console.log(this.name)
})
  • 对象中函数嵌套
var name=1;
const obj={
 name:2,
 say:function(){
  console.log(this.name);
  function say2(){
   console.log(this.name)
  }
  say2()
 }
}
obj.say();// 先输出2  在输出1 ,因为say2虽然也是定义在obj内部,但是其调用的时候属于直接调用
//如果我们想要say2也输出2,通常用一个that变量去保存这个this。但我们也可以通过箭头函数解决
const obj={
 name:2,
 say:function(){
  console.log(this.name);
  let that=this;
  function say2(){
   console.log(that.name)
  }
  say2()
 }
}
  • 对函数使用new 关键字时

new 关键字会先创建一个空对象,并把this指向这个空对象,等函数执行完毕后把这个对象返回。(注:如果函数有return语句则直接返回return中的)

在箭头函数中使用this

function函数中的this取决于真正被执行时的上下文,而es6新增的箭头函数内部的this则取决于其外层(函数或者全局)的作用域

var name=1;
const obj={
 name:2,
 say:function(){
  console.log(this.name);
  function say2(){
   console.log(this.name)
  }
  say2()
 }
}
obj.say();// 先输出2  在输出1 

上面的代码中我们在say函数内部又创建了一个say2函数,而这个say2函数由于是直接执行的所有this指向了全局,如果要是say2中也能获取到obj中的name,我们之前的例子用到了一个间接变量that,我们也可以通过箭头函数去实现,如下

var name=1;
const obj={
 name:2,
 say:function(){
  console.log(this.name);
  const say2=()=>{console.log(this.name)}
  say2()
 }
}
obj.say();// 输出两个2

上面的例子中,say2在执行时其上层作用域是say函数,因此say2中的this就继承了say的this,指向obj,因此会打印两个2。我们再看一个更复杂一些的例子

// 返回普通函数
function foo1(){
  // this 取决于被执行时的上下文情况
  return function(){console.log(this.a)}
}
// 返回箭头函数
function foo2(){
  // this 取决于foo2的this
  return ()=>{console.log(this.a)}
}
// 直接执行
var a=1
foo1()();// 1
foo2()();// 1
// 主动绑定this
obj1={a:2};
obj2={a:3};
var bar1=foo1.call(obj1);
bar1.call(obj2);// 3

var bar2 =foo2.call(obj1);
bar2.call(obj2);// 2 !不是3

分析上面的代码,直接执行时两个的结果都是1,但是原因是不一样的:两个括号相连我们可以拆分看,就相当于var res=foo();res();

对于foo1它直接返回了一个普通函数,res()就是直接执行普通函数,this指向window;对于foo2它是一个普通函数,直接执行时this指向window,而返回的箭头函数是继承了foo2执行时的this,因此返回的箭头函数中this也是指向window。

我们通过call主动绑定this能更直观的看出差别。

首先为foo1内部的this绑定obj1并执行,返回一个普通函数,再为返回的普通函数绑定obj2并执行我们看到输出的结果是obj2的3。而同样的操作对于foo2则是打印了foo2绑定的obj1的2。因此我们能分析出箭头函数的this是继承自其上层作用域的this,并且不能通过call等函数绑定this。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注