一篇文章搞懂this
Table of Contents
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时,如果传入的值是null
、undefined
,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()
,所以这里应用的是默认绑定。