天天看点

js中this到底指向谁

什么是this

JavaScript中的this是什么?

定义:this是包含它的函数作为方法被调用时所属的对象。

function fn1(){
	this.name = "halo";
}
fn1();
           
  • 我们将定义拆分一下
    • 包含它的函数:包含this的函数是fn1。
    • 作为方法被调用:fn1(); 此处fn1函数被调用。
    • 所属的对象:函数式调用函数默认所属的对象是window。

通过上面三点分析,很容易知道fn1函数里的this指向的是window。

那么如果是更复杂的场景我们如何判断this的指向呢?

this到底指向谁

如果想用一句话总结this的指向,稍微了解一些

this

指向的人都能脱口而出

谁调用它,this就指向谁。
           

也就是说

this

的指向是在调用时确定的,而不是在定义时确定的。这么说没有错,但是并不全面。

其实,调用函数会创建新的术语函数自身的执行上下文。执行上下文的调用创建阶段会决定

this

的指向。所以更加准确的总结应该是:

this的指向,是在调用函数时根据执行上下文所动态确定的。
           

在es6箭头函数之前,想要判断一个函数内部this指向谁,就根据以下四种方式来决定的。

  1. 函数式调用
  2. 上下文对象调用
  3. 构造函数调用
  4. bind、call、apply改变this指向

1、函数式调用

先来看一种相对简单的情况,函数在全局环境中被直接调用,严格模式下函数内this指向undefined,非严格模式下函数内this指向window。如下

function fn1() {
	console.log(this)
}

function fn2() {
	'use strict'
	console.log(this)
}

fn1() // window
fn2() // undefined
           

再看下面例子:

const age = 18;
const p = {
     age:15,
     say:function(){
     	 console.log(this)
         console.log(this.age)
     }
}
var s1 = p.say
s1()       
           

这里

say

方法内的

this

仍然指向

window

,因为

p

中的

say

函数赋值给

s1

后,

s1

的执行仍然是在

window

的全局环境中。因此上面的代码最后输出

window

undefined

这里可能有人会有疑问,如果是在全局环境中,那

this.age

不是应该输出18么?这是因为使用

const

声明的变量不会挂载到

window

全局对象上,因此

this

指向

window

时找不到

window

上的

age

。换成var声明即可输出18.

如果想让代码输出

p

中的

age

,并且让say函数中的this指向对象

p

。只需要改变函数的调用方式,如下

const age = 18;
const p = {
    age:15,
    say:function(){
    	console.log(this)
        console.log(this.age)
    }
}
p.say()
           

输出

{age: 15, say: ƒ}
	15
           

因为此刻

say

函数内部的

this

指向的是最后调用它的对象。再次验证了那句话,this的指向,是在调用函数时根据执行上下文所动态确定的。

2、上下文对象调用

const p = {
	age: 18,
	fn: function() {
		return this
	}
}

console.log(p.fn() === p)
           

输出

true
           

如果第一节的函数式调用理解了。那么这里应该也不会有疑问。

我们再重复一遍this的指向,是在调用函数时根据执行上下文所动态确定的。

记住这句话后遇到更复杂的场景也可以很容易的确定this指向。

如下:

const p = {
    age: 20,
    child: {
        age: 18,
        fn: function() {
            return this.age
        }
    }
}
console.log(p.child.fn())
           

不论浅套关系如何变化,this都只想最后调用它的对象,因此输出18。

再升级下代码:

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()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())
           

输出结果

o1
o1
undefined
           
  • 第一个,应该没有问题,直接找到调用this的那个函数。
  • 第二个,看似调用

    o2.fn()

    ,其实内部调用的是

    o1.fn()

    ,因此还是输出

    o1

  • 第三个,赋值后调用

    fn()

    ,相当于在全局环境调用函数。

    this

    指向

    window

    如果现在的需求是想让

输出o2,代码该如何修改?

如下:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: o1.fn
}

console.log(o2.fn())
           

3、构造函数调用

function Foo() {
    this.age = 18
}
const instance = new Foo()
console.log(instance.age)
           

输出18。知道输出结果并不难,但是

new

操作符调用构造函数时都做了什么呢?

  • 创建一个新对象;
  • 将构造函数的

    this

    指向这个新对象;
  • 为新对象添加属性、方法;
  • 返回新对象。

需要注意的是,如果在构造函数中出现显式的

return

,那么就要分为两种场景分析。

function Foo(){
    this.age = 18
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.age)
           

将会输出

undefined

,因为如果在构造函数中出现显式的

return

,并且返回一个对象时,那么创建的构造函数实例就是

return

返回的对象,这里

instance

就是返回的空对象

o

function Foo(){
    this.age = 18
    return 1
}
const instance = new Foo()
console.log(instance.age)
           

将会输出

18

,因为如果构造函数中出现显式

return

,但是返回一个非对象的值时,那么

this

还是指向实例。

总结:当构造函数显式返回一个值,并且返回的是一个对象,那么

this

就指向这个返回的对象。如果返回的不是一个对象,

this

仍然指向实例。

4、bind、call、apply改变this指向

关于基础用法,这里不再赘述。需要知道的是

bind/call/apply

三者都是改变函数

this

指向的,

call/apply

是改变的同时直接进行函数调用,而

bind

只是改变

this

指向,并且返回一个新的函数,不会调用函数。

call

apply

的区别就是参数格式不同。详见如下代码:

const target = {}
fn.call(target, 'arg1', 'arg2')
           

上述代码等同于如下代码

const target = {}
fn.apply(target, ['arg1', 'arg2'])
           

可以看出知识调用的参数形式不同而已,改写成bind如下所示

const target = {}
fn.bind(target, 'arg1', 'arg2')()
           

不光要调用bind传入参数,还是在调用bind后再次执行函数。

明白call/apply/bind的使用后,再来看一段代码:

const foo = {
    age: 18,
    showAge: function() {
        console.log(this.age)
    }
}
const target = {
    age: 22
}
console.log(foo.showAge.call(target))
           

结果输出22,只要掌握了

call/apply/bind

的基本用法,对于输出结果并不难理解。我们往往会遇到多种方式同时出现的情况,我们在说完箭头函数的

this

后会再详细说明

this

优先级相关内容。

5、箭头函数this指向

熟悉es6的人应该会知道箭头函数中的

this

指向,不再遵从上述的规制,而是根据外层的上下文来决定。

es5代码:

const foo = {  
    fn: function () {  
        setTimeout(function() {  
            console.log(this)
        })
    }  
}  
foo.fn()  // Window{……}
           

this

出现在

setTimeout()

中的匿名函数里时,

this

指向

window

对象。这种特性势必会给我们的开发带来一些坑,es6的箭头函数就很好的解决了这个问题。

es6代码:

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

箭头函数中的

this

指向,不再适用上面的标准,而是找到外层上下文,这段代码中

this

在箭头函数中,则找到外层的上下文的调用对象——

foo

。因此这里的

this

指向的就是

foo

注意:当箭头函数改变了

this

指向后,那么该

this

指向就不再受任何影响,也就是说不会再次发生改变,具体在

this

优先级章节中会举例说明。

总结:

  • 通过

    call、apply、bind、new

    等改成

    this

    指向的操作称为显式绑定;
  • 根据上下文关系确定的

    this

    指向成为隐式绑定。

如果一段代码中即出现显式绑定又有隐式绑定,该如何确定

this

指向呢?

往下看

6、this优先级

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

const o1 = {
    age: 1,
    foo: foo
}

const o2 = {
    age: 2,
    foo: foo
}

o1.foo.call(o2)
o2.foo.call(o1)
           

如果隐式绑定优先级高于显式绑定,那么应该输出

1,2

。但是运行代码发现结果输出

2,1

。这也就说明了显式绑定中的

call、apply

优先级更高。

再看:

function foo (age) {
    this.age = age
}

const o1 = {}

var fn = foo.bind(o1)
fn(18)
console.log(o1.age) // 18

var f1 = new fn(22)
console.log(f1.age); // 22
           

分析下上面代码,

fn

foo

函数调用

bind

方法返回的函数,也就相当于是返回

foo

函数,并且将

this

指向

o1

对象。执行了

fn(18)

o1

对象的

age

值就是18了,所以第一个输出结果是18。

然后通过

new

调用

fn

函数,这时

fn

函数作为构造函数被调用,

this

就会指向返回的实例,从而与

o1

对象解绑。

因此得出结论:

new

的优先级高于

bind

还记得上一节提到的箭头函数特行么?箭头函数影响的this指向无法被修改。看下面代码:

function foo() {
    return () => {
        console.log(this.age)
    };
}

const o1 = {
    age: 2
}

const o2 = {
    age: 3
}

const fn = foo.call(o1)
console.log(fn.call(o2))
           

输出为2,

foo

this

指向了

o1

fn

接收的箭头函数的

this

自然也会指向

o1

。而箭头函数的this是不会再次改变的,所以尽管用显式绑定

call

去改变

this

指向,也是不起作用的。

结束啦!

this涉及知识点繁多,碰到优先级问题也是让人头疼。

没有什么捷径,唯有“死记硬背”+“慢慢理解”

继续阅读