skip to content

JavaScript
一网打尽 this

全局环境下的 this

function f1() {
  console.log(this)
}
function f2() {
  'use strict'
  console.log(this)
}
f1() // window
f2() // undefined
const foo = {
  bar: 10,
  fn: function () {
    console.log(this)
    console.log(this.bar)
  }
}
var fn1 = foo.fn // 相当于 window.fn1 = foo.fn
fn1() // window undefined
const foo = {
  bar: 10,
  fn: function () {
    console.log(this)
    console.log(this.bar)
  }
}
foo.fn() // foo 10

结论:在执行函数时,如果函数中的 this 是被上一级的对象所调用,那么 this 指向的就是上一级的对象;否则指向全局环境。

上下文对象调用中的 this

const person = {
  name: 'Lucas',
  brother: {
    name: 'Mike',
    fn: function () {
      return this.name
    }
  }
}
console.log(person.brother.fn()) // 'Mike'
// 在这种嵌套的关系中,this 指向最后调用它的对象,因此输出将会是:Mike。
const o1 = {
  text: 'o1',
  fn: function () {
    return this.text
  }
}
const o2 = {
  text: 'o2',
  fn: function () {
    return o1.fn()
  }
}
const o3 = {
  text: 'o3',
  fn: function () {
    var fn = o1.fn
    return fn()
  }
}
const o4 = {
  text: 'o4',
  fn: o1.fn
}

console.log(o1.fn()) // o1
console.log(o2.fn()) // o1
console.log(o3.fn()) // undefined
console.log(o4.fn()) // 04
/* 
  第一个 console 最简单,o1 没有问题。难点在第二个和第三个上面,关键还是看调用 this 的那个函数。 
  第二个 console 的 o2.fn(),最终还是调用 o1.fn(),因此答案仍然是 o1。 
  第三个,在进行 var fn = o1.fn 赋值之后,是「裸奔」调用,因此这里的 this 指向 window,答案当然是 undefined。
  第四个,this 指向最后调用它的对象,在 fn 执行时,挂到 o4 对象上即可,我们提前进行了赋值操作
*/

bind/call/apply 改变 this 指向

// 三者使用区别
const printName = (firstName, lastName) => console.log(`${firstName} ${lastName}`)
// bind
printName.bind(null, 'Number', 'One')() // 常见于 React 绑定事件
// call
printName.call(null, 'Number', 'Two')
// apply
printName.apply(null, ['Number', 'Three'])
const foo = {
  name: 'lucas',
  logName: function () {
    return this.name
  }
}

const bar = {
  name: 'mike'
}
console.log(foo.logName.bind(bar)()) // mike
console.log(foo.logName.call(bar)) // mike
console.log(foo.logName.apply(bar)) // mike

构造函数和 this

function Foo() {
  this.bar = 'Lucas'
}
const instance = new Foo()
console.log(instance.bar)
/* 
  new 操作符调用构造函数,具体做了什么?以下供参考: 
  1.创建一个新的对象; 
  2.将构造函数的 this 指向这个新对象; 
  3.为这个对象添加属性、方法等; 
  4.最终返回新对象。

  以上过程,也可以用代码表述:
  var obj  = {}
  obj.__proto__ = Foo.prototype
  Foo.call(obj)
*/
function Foo() {
  this.user = 'Lucas'
  const o = {}
  return o
}
function Bar() {
  this.user = 'Lucas'
  return 1
}
console.log(new Foo()) // {}
console.log(new Bar()) // Bar {user: "Lucas"}
/* 
  结论:如果构造函数中显式返回一个值,且返回的是一个对象,那么 this 就指向这个返回的对象;
  如果返回的不是一个对象,那么 this 仍然指向实例。
*/

箭头函数中的 this 指向

const foo = {
  fn: function () {
    setTimeout(function () {
      console.log(this)
    })
  }
}
const bar = {
  fn: function () {
    setTimeout(() => {
      console.log(this)
    })
  }
}

console.log(foo.fn()) // window
console.log(bar.fn()) // {fn: ƒ}
/* 
  this 出现在 setTimeout() 中的匿名函数里,因此 this 指向 window 对象。
  如果需要 this 指向 foo 这个 object 对象,可以巧用箭头函数解决: 
*/

箭头函数不绑定它们自己的 this ,相反,它们从父作用域继承一个,这被称为“词法作用域”。

from Understanding "this" in javascript with arrow functions

最后的测试

const bar = {
  f1: function () {
    console.log(this)
  },
  f2: () => {
    console.log(this)
  },
  f3: function () {
    const fun = () => {
      console.log(this)
    }
    fun()
  },
  f4: () => {
    function fun() {
      console.log(this)
    }
    fun()
  }
}

bar.f1() // bar 
// `bar.f1()`:在 `bar` 对象上调用 `f1` 方法,此时 `f1` 中的 `this` 指向 `bar` 对象,因此输出 `bar`。
bar.f2() // window
// `f2` 是一个箭头函数,箭头函数的 `this` 始终指向外层作用域的 `this`,而在全局作用域中,`this` 指向全局对象。因此,在这个调用中,输出全局对象 `window`。
bar.f3() // bar
// 在 `bar` 对象上调用 `f3` 方法。在 `f3` 方法中定义了一个箭头函数 `fun`,它的 `this` 也指向 `bar` 对象。因此,在调用 `fun` 方法时,输出 `bar`。
bar.f4() // window
// `f4` 是一个箭头函数,而在 `f4` 中定义的 `fun` 函数是一个普通函数。在全局作用域中调用 `f4` 方法时,`f4` 中的箭头函数的 `this` 指向全局对象,而 `fun` 函数的 `this` 也指向全局对象。因此,在调用 `fun` 方法时,输出全局对象 `window`。

const f1 = bar.f1
f1() // window
// 将 `bar.f1` 赋值给了 `f1` 变量,然后在全局作用域中调用 `f1` 方法。由于此时 `f1` 中的 `this` 没有明确指定,因此默认指向全局对象 `window`,因此输出全局对象 `window`。
const f2 = bar.f2
f2() // window
// 将 `bar.f2` 赋值给了 `f2` 变量,然后在全局作用域中调用 `f2` 方法。由于 `f2` 是一个箭头函数,在全局作用域中定义的,因此它的 `this` 指向全局对象 `window`。因此,在这个调用中,输出全局对象 `window`。
const f3 = bar.f3
f3() // window
// 将 `bar.f3` 赋值给了 `f3` 变量,然后在全局作用域中调用 `f3` 方法。在 `f3` 方法中定义了一个箭头函数 `fun`,它的 `this` 也指向全局对象 `window`。因此,在调用 `fun` 方法时,输出全局对象 `window`。
const f4 = bar.f4
f4() // window
// 将 `bar.f4` 赋值给了 `f4` 变量,然后在全局作用域中调用 `f4` 方法。由于 `f4` 是一个箭头函数,在全局作用域中定义的,因此它的 `this` 指向全局对象 `window`。在 `f4` 中定义的 `fun` 函数的 `this` 也指向全局对象 `window`。因此,在调用 `fun` 方法时,输出全局对象 `window`。