【JavaScript】这个?那个?this到底是哪个?

192 阅读4分钟

this是一个代词,代指一个对象

为什么要有 this ?

  • 以下代码展示了不用this时需要多层函数嵌套时传参的写法,可以发现我们这个时候需要手动显式传入上下文对象,设置形参实参才能让函数正确读取我们需要的东西
// 需要给 speak 和 identify 显式传入一个上下文对象

function identify(context) {
  return context.name.toUpperCase()
}

function speak(context) {
  var greeting = 'hello, I am ' + identify(context)
  console.log(greeting);
}

var me = {
  name: 'Tom'
}
speak(me)
  • 使用this后的代码可以长这样:可以想象,在传多个参数时代码可以变得多么简洁易读
function identify () {
  return this.name.toUpperCase()
}

function speak () {
  var greeting = 'hello, I am ' + identify.call(this)
  console.log(greeting)
}

var me = {
  name: 'FWB',
}
speak.call(me)

很显然,this提供了一种更优雅的方式来隐式的传递一个对象引用,可以让代码更加简洁易于复用

this 可以用在哪里?

  • 只有作用域中才可以写this
    1. 全局作用域:通常在浏览器中,this指向window
    2. 函数作用域:根据函数调用方式决定this指向

this的绑定规则

1.默认绑定

  • 当函数被独立调用时,函数的this指向window

独立调用是指函数直接被调用,前面没有"前缀"

var a = 3

function foo () {
  var a = 2

  function bar () {
    var a = 1
    console.log(this.a) // 浏览器环境下输出3,node中为 undefined
  }

  bar() // 独立调用
}

foo() // 独立调用

2.隐式绑定

  • 当函数引用有上下文对象 且被该对象调用时,隐式绑定会把函数调用中的this绑定到这个上下文对象

绕来绕去其实意思就是和独立调用相反,有"前缀"的调用,就是上述概念

var a = 1

function foo () {
  console.log(this.a) // obj.a
}

var obj = {
  a: 2,
  foo: foo,
}
var obj2 = {
  a: 3,
  obj: obj,
}
obj2.obj.foo() // this 指向obj

i. 小贴士

隐式绑定时采取 "就近原则" ,当一个函数被多层对象调用时,this指向最近的那一层对象

ii. 隐式丢失

 var a = '全局变量';
 var obj = {
     a: '对象属性',
     foo: function() {
         console.log(this.a);
     }
 };

 var bar = obj.foo; // 将 obj.foo 赋值给 bar,此时 bar 只是一个普通函数引用
 bar(); // 输出 '全局变量',因为 bar 是独立调用,this 发生隐式丢失,指向 window

 setTimeout(obj.foo, 100); // 输出 '全局变量',setTimeout 的回调函数也是独立调用

3.显式绑定

  • fn.call(obj, x, x, ...)

    显式地将fn里面的this绑定到obj上,call负责帮fn接受参数

  • fn.apply(obj, [x, x, ... ])

    显式地将fn里面的this绑定到obj上,apply负责帮fn接受参数,参数必须是数据组形式

  • fn.bind(obj, x, x, ...)(x, x, ...)

    显式地将fn里面的this绑定到obj上,bind会返回一个新的函数,bind和新函数都可以负责帮fn接受参数,参数零散的传入

function foo (x, y) {
  console.log(this.a, x + y)
}

var obj = {
  a: 1,
}
// foo.call(obj, 1, 2)
// foo.apply(obj, [1, 2])
const bar = foo.bind(obj, 2, 3)  // 返回的是函数,需要用bar接收

// 当bind中参数已经完全接收时,bar()中的参数就不会被接收
bar(4)

4.new绑定

function Person() {
  // var obj = {}
  // Person.call(obj)
  this.name = 'FWB'
  this.age = 20
  console.log(this);  // p1

  // obj.__proto__ = Person.prototype
  // return obj
}
const p1 = new Person()

箭头函数

箭头函数会捕获其被定义时的上下文this

箭头函数中没有this这个概念,写在了箭头函数中的this,也是它外层那个非箭头函数的this

案例1

function a() {
  console.log(this);
  let b = function() {
    let c = () => {
      let d = () => {
        console.log(this);
      }
      d()
    }
    c()
  }
  b()
}
a()

其实就是window或者global,不管有多少层,这些函数的调用都是独立的,所以this指向全局

案例2

var a = 1
var obj = {
  a: 2,
  bar: function () {
    const baz = () => {
      console.log(this.a)  // 2
    }
    baz()
  },
}
obj.bar()
  • 当执行obj.bar()时,由于是作为对象方法调用,bar函数内部的this指向obj对象
  • bar的箭头函数baz没有自己的this绑定,而是继承其定义时所在环境(bar函数)的this
  • 因此,箭头函数baz中的this.a实际上是访问obj.a,值为2

总结

  1. 优先使用显式绑定:当需要明确this指向时,使用call/apply/bind
  2. 方法调用注意上下文:确保对象方法被正确调用,避免隐式丢失
  3. 箭头函数谨慎使用:在需要动态this的场景避免箭头函数
  4. 构造函数使用new:确保构造函数正确实例化对象

理解this是掌握JavaScript核心的关键,它让我们的代码更加灵活高效。通过熟练掌握这四大规则,你就能在复杂场景中游刃有余地使用this了!