Skip to main content
  1. Posts/

一篇文章搞懂this

·2 分钟

this是什么 #

它是JS中的一个关键字,被自动定义在所有函数的作用域中。

this提供了一个更优雅的方式来隐式“传递”一个对象引用。我们需要搞清楚的就是this到底指向了哪里。

对this最常见的误解是把它理解成指向函数自身,我更喜欢理解成是指向调用该函数的对象

this的绑定规则 #

默认绑定 #

最常用也最简单的独立函数调用,直接使用不带任何修饰的函数引用进行调用。

function foo() {
    console.log(this.a)
}
var a = 2
foo()  // 2

这种调用方式this指向的是全局对象window,所以打印结果是申明的变量a

function foo() {
    this.count++
}
foo.count = 0
foo()
console.log(foo.count) // 0

上面这个例子为什么foo.count输出结果为0?就是因为foo函数被调用时this指向的是全局对象window,而不是对象foo。

函数调用链(一个函数调用另一个函数),以及将函数作为参数传入另一个函数中,也会造成默认绑定。

在严格模式下,全局对象不能用于默认绑定,所以this会绑定到undefined。

隐式绑定 #

一般的对象调用 #

通过某个对象调用的函数

function foo() {
    console.log(this.a)
}
const b = {
    a: 2,
    foo: foo
}
b.foo()  // 2

foo()是被对象b调用的,所以this指向的是对象b

对象属性引用链 #

当函数被对象属性链式调用时,只有调用对象的上一层作用域会被绑定到this上。

function foo() {
    console.log(this.a)
}
const obj1 = {
    a: 1,
    foo: foo
}
const obj2 = {
    a: 2,
    obj1: obj1
}
obj2.obj1.foo()  // 1

这里函数foo()的上一层调用是obj1,所以this指向的是obj1

隐式丢失 #

这就是为什么this的指向问题这么让人摸不着头脑,因为有些隐式绑定会导致绑定对象的丢失,最终应用的是默认绑定。

当我们把对象里的函数赋值给一个全局变量再调用时,this就指向了全局对象window。

function foo() {
    console.log(this.a)
}
var obj = {
    a: 1,
    foo: foo
}
var bar = obj.foo
var a = 2
bar() // 2

传入回调函数,也就是前面所说的作为参数传入函数,造成默认绑定。

function foo() {
    console.log(this.a)
}
function doFoo(fn) {
    fn()
}
var obj = {
    a: 1,
    foo: foo
}
var a = 2
doFoo(obj.foo) // 2

显式绑定 #

JS内置方法 #

call()apply()方法的第一个参数就是用来指定调用该方法的函数的this指向的对象。

function foo() {
    console.log(this.a)
}
var obj = {
    a: 2
}
foo.call(obj) // 2

函数foo的this被指定给了obj

如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对 象,这个原始值会被转换成它的对象形式(也就是 new String(..)、new Boolean(..) 或者 new Number(..))。

还有个bind()方法,它和call、apply不同的是,它会返回一个新的函数,这个新函数的this就指向了传入的参数。

function foo(num) {
    console.log(this.a, num)
    return this.a + num
}
var obj = {
    a: 2
}
var bar = foo.bind(obj)
var b = bar(3) // 2 3
console.log(b) // 5

其实JS内置函数里都能够传入参数指定this指向,比如forEach(),第一个参数是一个回调函数,第二个参数就是指定回调函数的this指向。

new绑定 #

当你new一个对象时,js为你做了哪些工作?

  • 创建一个新对象
  • 新对象的prototype属性指向构造函数的原型对象
  • 构造函数的this指向新对象
  • 通过构造函数初始化新对象
  • 如果构造函数返回一个非空对象,则返回该对象;否则返回刚创建的新对象

绑定规则优先级 #

默认绑定 < 隐式绑定 < 显示绑定 < new绑定

箭头函数 #

箭头函数的this指向是最特殊的,它本身没有this,只能通过查找作用域链获得this值,所以它根据执行上下文决定this指向的。

var a = 1
function foo() {
    console.log('foo', this.a)
}
var boo = {
    a: 2,
    foo: foo,
    fn: () => {
        console.log('fn', this.a)
    }
}
boo.foo()  // 2
boo.fn() // 1

foo()this指向boo对象,而fn()this指向的是window,虽然fn()是被boo调用的,但它需要获取boo的this指向,就是window。

其它意外 #

被忽略的this #

当使用call、apply或bind方法显示绑定this时,如果传入的值是nullundefined,this还是会指向全局。

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

var a = 2

foo.call(null) // 2
foo.call(undefined) // 2
foo.bind(null)();

间接引用 #

function foo() {
  console.log(this.a)
}
var a = 2
var o = { a: 3, foo: foo }
var p = { a: 4 }

o.foo(); // 3

// 函数赋值
(p.foo = o.foo)()  // 2
console.log(p.foo) // foo(){ console.log(this.a) }

p.foo = o.foo赋的值是目标函数的引用,因此调用执行的实际是foo()函数,而不是p.foo()也不是o.foo(),所以这里应用的是默认绑定。