Skip to content

js 中的 this

参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this。

这篇文章假定 js 的执行环境为浏览器。

this 具有动态性

this 具有动态性,是指它的值在函数调用时才确定,而不是在函数定义时。这导致了 this 的行为具有上下文相关性,即它的值取决于代码的执行上下文。

比如在全局上下文中,this 指向全局对象(通常是 window 对象)。

执行 node window.js,打印 undefined

js
function fn() {
  console.log(this) // undefined
}

fn()

在对象方法中,this 指向调用该方法的对象。

js
const obj = {
  fn() {
    console.log(this) // { fn: [Function: fn] }
  }
}

obj.fn();

在构造函数中,this 指向新创建的实例对象。

js
class A {
  constructor() {
    console.log(this) // A {}
  }
}

new A();

在事件处理函数中,this 通常指向触发事件的 DOM 元素(e.target)。

默认绑定

非严格环境下,全局下的 this 指向 window,而在严格环境下是 undefined,不允许 this 指向全局 window。

独立调用

在浏览器环境下,当函数独立调用时,this 会指向 window。如果在 node 环境下,this 会为 undefined。

js
function foo() {
	console.log(this === window)  // true
}
foo()

看下面这段代码,为什么 能访问到 this.a,但是访问不到 this.b 呢?

js
var a = 'ikun'
let b = 'jntm'
function foo() {
	console.log(this.a) // ikun
	console.log(this.b) // undefined
}

foo()

这是因为,

在 ES5 中,顶层对象 window 的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。 换句话说,ES5 中全局变量会自动挂载到 window 对象上。

在 ES6 规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。

再看一个独立调用例子:

js
var a = 'ikun'

const obj ={
	a: 'jntm',
	foo: function() {
		function fn() {
      // 如果是 node 环境,为 undefined
      // 如果是 浏览器环境,为 window
			console.log(this)
			console.log(this.a)
		}
		return fn() // 独立调用
	}
}

obj.foo()

在 foo 函数执行时,此时的 this 指向的是 obj,但是在函数内部独立调用了一个函数 fn,此时 fn 内的 this 会指向 window。

作为方法调用

当函数作为对象的属性方法调用时,this 会绑定到这个对象,也就是谁调用就指向谁。

js
var a = 'ikun'
var obj = {
  a: 'jntm',
  foo: function () {
    var a = 'jnssssztm'
    console.log(this) // { a: 'jntm', foo: [Function: foo] }
    console.log(this.a) // jntm
  }
}
obj.foo()

foo 函数在 obj 下调用,所以此时的 this 指向 obj 对象,也就输出 jntm。

如果函数调用前面有多个对象,this 指向离自己最近的那个对象,比如像下面这个例子:

js
function fn(){
	console.log(this.a)
}

const obj1 = {
	a: 'ikun',
	foo: fn
}

const obj2 = {
	a: 'jntm',
	o: obj1
}

obj2.o.foo() // ikun

通过 o 调用 obj1 下的 foo,this 会指向 o 也就是 obj1。

立即执行函数

立即执行函数也就是定义后立刻执行的匿名函数,在立即执行函数内 this 指向 window。

js
var a = 'ikun'

const obj = {
	a: 'jntm',
	foo: function() {
		(function() {
			console.log(this) // window
			console.log(this.a) // ikun
		})()
	}
}

obj.foo()

隐式绑定

在某些特殊情况下会存在 this 丢失的问题,常见的就是将调用函数作为参数传递或者将变量赋值给另外一个变量,此时 this 指向 window。

比如这样,通过一个变量 fn1 来接收函数,此时指向 window,也就输出 window、ikun。

js
var bar = 'ikun'
const foo = {
	bar: 'jntm',
	fn: function() {
		console.log(this)
		console.log(this.bar)
	}
}
var fn1 = foo.fn
fn1()

例子 1

下面三个输出语句分别打印什么?

js
const obj1 = {
	text: 1,
	fn: function(){
		return this.text
	}
}

const obj2 = {
	text: 2,
	fn: function(){
		return obj1.fn()
	}
}

const obj3 = {
	text: 3,
	fn: function(){
		var fn1 = obj1.fn
		return fn1()
	}
}

// 属于 this 指向调用者的情况。fn 的 this 指向 obj1,所以是 1
console.log(obj1.fn())

// 也是属于 this 指向调用者的情况。打印 1
console.log(obj2.fn())

// 独立调用,fn 的 this 指向 undefined
console.log(obj3.fn())

例子 2

和例子 1 其实是一样的。看方法中的 this 属于哪个东西,就是看这个方法是被独立调用的还是被对象方法调用的。

js
const obj1 = {
	text: 1,
	fn: function(){
		return this.text
	}
}

const obj2 = {
	text: 2,
	fn: function(){
		return obj1.fn()
	}
}

const v = obj2.fn()
console.log(v) // 1

例子 3

因为最终 foo 是独立调用的,所以 this 为 undefined。

js
var a = 'ikun'
function foo() {
	console.log(this.a)
}

function foo2() {
	foo()
}

const obj = {
	a: 1,
	foo3: foo2
}

obj.foo3() // undefined

通过 call、apply、bind 显示绑定 this

通过 call、apply、bind 方法强制改变 this 指向,让它指向我们指定的对象,不管方法是怎么被调用的。

这里要注意 call、apply、bind 三个方法的使用区别!

在调用方法时机上:

  • call、apply 会直接进行函数调用
  • bind 不会立即执行函数,而是返回一个新的函数,返回的这个新函数已经自动绑定了新的 this。

在传参上:

  • call、bind 都是接受多个参数
  • apply 接受一个数组。

正常的绑定:

js
const var = {
	name: 'ikun',
	foo: function() {
		console.log(this.name)
	}
}

const var = {
	name: 'tkl'
}
obj.foo.call(obj2)

这里输出结果为 tkl,不难理解,函数 foo 在调用时通过 call 改变了 this 指向,指向了 obj2。

需要注意的是,如果吧 null、undefined 作为参数传入 call、apply、bind,此时不会改变 this 的指向。

js
var a= 'ikun'
function fn() {
	console.log(this.a)
}

fn.call(null) // ikun

使用多个 bind 函数,最终的 this 由第一次的 bind 函数决定。

js
var obj = {
	name: 'ikun',
	foo: function() {
		console.log(this.name)
	}
}

var obj2 = {
	name: 'tkl'
}

var obj3 = {
	name: 'hcy'
}
obj.foo.bind(obj2).bind(obj3)()

最后输出结果 tkl,也就是第一次 bind() 函数绑定 obj2 对象的 name 属性。

setTimeout 和 setInterval

setTimeout() 执行的代码是从一个独立于调用 setTimeout 的函数的执行环境中调用的,它里面的 this 将默认为 window。

js
var name = 'ikun'

const obj = {
	name: 'jntm',
	foo: function(){
		setTimeout(function() {
			console.log(this.name)
		})
	}
}

obj.foo() // ikun

构造函数绑定

函数可以作为构造函数使用 new 创建对象,此时 this 会发生改变,new 关键字做一下操作:

  1. 创建一个空对象
  2. 将构造函数的原型指向空对象的 prototype 属性
  3. 将 this 指向这个对象,通过 apply 执行构造函数
  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象

看个例子,这里构造函数返回了一个引用数据类型:

js
function Foo() {
	this.name = 'ikun'
	this.age = 66
	const obj = {
		name: 'tkl'
	}
	return obj
}

const foo = new Foo()
console.log(foo.name) // tkl
console.log(foo.age) // undefined

在构造函数内返回了一个对象,此时的实例 foo 就指向返回的 obj,所以输出 tkl,也没有 age 属性。

如果没有返回或者返回是一个原始类型,就指向实例。

js
function Foo() {
	this.name = 'ikun'
	this.age = 66
	const obj = {
		name: 'tkl'
	}
	return name
}

const foo = new Foo()
console.log(foo.name) // ikun
console.log(foo.age) // 66

总之,如果构造函数中返回一个对象,那么 this 就指向这个对象,如果返回基础数据或者没有返回,this 就指向实例。

箭头函数绑定

ES6 新增的箭头函数是没有 this 的,它的 this 由调用函数时所在的上下文来决定。

js
const obj = {
	name: 'ikun',
	foo: () => {
		console.log(this)
	}
}

obj.foo()

obj.foo() 所处的环境的 this 为 undefined,所以 foo 里的 this 为 undefined。

看下面这道题,最后输出多少呢?

js
var obj = {
   say: function() {
     var f1 = () =>  {
       console.log("1111", this);
     }
     f1();
   },
   pro: {
     getPro:() =>  {
        console.log(this);
     }
   }
}
var o = obj.say;
o();
obj.say();
obj.pro.getPro();
  • obj.say 隐式绑定到 o 上,此时 this 为 window,执行函数 o,内部执行 f1 箭头函数,由于此时上下文为 window,所以箭头函数的 this 指向 window,最后输出 1111,window
  • 通过对象调用执行 say 函数,此时 this 指向 obj,所以箭头函数 this 指向 obj,最后输出 111 {pro: {…}, say: ƒ}
  • 通过对象调用执行 getPro 函数,它是一个箭头函数,此时 this 为 window

Released under the MIT License.