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的全局环境中。因此上面的代码最后输出windowundefined

这里可能有人会有疑问,如果是在全局环境中,那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
    如果现在的需求是想让
console.log(o2.fn())

输出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指向,并且返回一个新的函数,不会调用函数。callapply的区别就是参数格式不同。详见如下代码:

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

分析下上面代码,fnfoo函数调用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,foothis指向了o1fn接收的箭头函数的this自然也会指向o1。而箭头函数的this是不会再次改变的,所以尽管用显式绑定call去改变this指向,也是不起作用的。


结束啦!
this涉及知识点繁多,碰到优先级问题也是让人头疼。
没有什么捷径,唯有“死记硬背”+“慢慢理解”

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容