完整梳理this指向,看完保证秒懂

栏目: JavaScript · 发布时间: 4年前

内容简介:在ES5中,判断This 被分为三种情况:全局对象、当前对象或者任意对象;判断处于哪种情况,完全取决于函数的调用方式

this 作为 JS 语言中的关键字,其复杂的指向往往是初学者混淆的地方,同时也是面试官经常询问的考点之一,理解好 JS 中的 this 指向,才能算是迈入了 JS 这门语言的门槛,本文将梳理出 this 指向的不同情景,以及如何更好、更准确的判断出this指向,让面试的时候不再为 this 指向头疼。

理解好this指向的关键要素

在ES5中,判断 this 指向的关键是在于这句话, this永远指向调用它的那个对象 ,想要理解本句话就只有一个重要的点,是谁调用了 this ,只要从这个点出发,找到这个 ,那么就能轻车熟路的判断 this 的指向。

this的调用

This 被分为三种情况:全局对象、当前对象或者任意对象;判断处于哪种情况,完全取决于函数的调用方式

从这句话可以看出, this 调用的指向,其实取决于函数的调用方式,也就是在运行时, 哪个函数内部使用了 this ,那么 this 就指向调用此函数的对象 。这样就很容易理解 this 指向了,看到判断 this 指向问题的第一反应,去找使用了 this 这个函数被谁调用就完事了。如,A函数中使用了 this ,那么就去找哪个对象调用了A函数。

麻烦就麻烦在,如何去找到哪个对象调用了使用 this 的函数,因为函数的调用在 JS 中是可以通过多种方式的。

  1. 作为普通函数调用
  2. 作为对象方法调用
  3. 使用 applycall 调用
  4. 作为构造函数调用

那么问题就变成了只要判断函数被哪个对象调用就行了。由此分析下四种不同的函数调用情况(针对ES5)

作为普通函数调用

var a = 'arzh';
function thisName() {
    console.log(this.a);
}
thisName(); //'arzh'
console.log(window);
复制代码

我们拿这个 thisName 函数分析一下,作为普通函数调用时,挂载在全局,可轻易判断出函数被 window 调用了,那么就可以判断出 this 指向了 window , this.a 就相当于 window.a ,因为在ES5中定义的全局变量会变成window上的属性

完整梳理this指向,看完保证秒懂
所以 window.a === this.a === arzh

;

如果把函数改成这样

var a = 'arzh';
function thisName() {
    var a = 'arzh1'
    console.log(this.a);
}
thisName(); //'arzh'
复制代码

这个也好理解,因为 this 始终都是指向 window ,那么 windowa 变量一直都是'arzh',因为 thisName 函数中的 a 变量不会覆盖了 window 上的 a 变量。

我们再把函数变一下

var a = 'arzh';
function thisName() {
    a = 'arzh1'
    console.log(this.a);
}
thisName(); //'arzh1'
复制代码

这个如何理解呢,同理这个时候是 window 调用了 thisName 函数, this 还是始终指向 window ,但是 thisName 函数中 a 变量没有加 var ,就被默认为全局的变量,覆盖了上一次的 a 变量,所以 this.a 就变成了最后一次定义 a 变量的字符串,也就是'arzh1';

作为对象方法调用

作为对象方法调用,顾名思义,就是对象调用了这个函数(方法),那么根据上面的分析, 谁调用了使用this的函数,this就指向谁 ,可知 this 肯定就指向了此对象。用代码看一下

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
b.fn(); //'arzh1'
复制代码

这个就很容易理解, fn 作为b对象的方法调用,即 b 对象调用了 fn 函数,那么 this 指向了 b 对象,所以 this.a === b.a === 'arzh1'

修改一下函数,如下:

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
window.b.fn(); //'arzh1'
复制代码

window 调用了 b 对象, b对象调用了fn方法 ,本质上,调用 fn 方法的还是 b 对象,所以 this 一直指向 b 对象,也即输出了'arzh1',如果将 b 对象中的 a 属性去掉,那么理论上就应该输出 undefined ,因为 this 还是在 b 对象上, b 中并没有 a 属性。代码如下:

var a = 'arzh';
var b = {
    //a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
window.b.fn(); //undefined
复制代码

我们继续往下看

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
var fn1 = b.fn; //fn并没有被调用
fn1(); //'arzh'
复制代码

这种情况也是一样的, b 对象将 fn 方法赋值给 fn1 变量,那么此时 fn 方法并没有被任何人调用,只是单纯的赋值。执行 fn1 方法之后, fn 方法才算真正被调用,那么此时 fn1 挂载在全局 window 上,也就是 window 对象去调用了 fn1 方法,所以 fn1 的指向就变成了 window ,也就输出了'arzh';

那么如果是这种情况呢

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function(){
        setTimeout(function() {
            console.log(this.a)
        },100)
    }
}
b.fn(); //arzh
复制代码

从上述中可以看到 b.fn() 里面执行了 setTimeout 函数, setTimeout 内的 this 是指向了 window 对象,这是由于 setTimeout() 调用的代码运行在与所在函数完全分离的执行环境上。熟知 EventLoop 的人员应该明白, setTimeout 函数其实调用的是浏览器提供的其他线程,当 JS 主线程走完之后,会调用任务队列的函数,也即是 setTimeout 函数,此时 setTimeout 是在 window 上调用的,那么 this 自然指向了 window

使用 apply 或 call 调用

applycall 函数都可以用来更改 this 指向,具体的用法可以参考apply和call.因为 applycall 函数在使用上,基本只有传入参数不同的区别( apply 第二个参数是数组, call 第二个参数不为数组),本文拿 call 来讲解。我们直接看代码

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
var c = {
    a : 'arzh2'
}
b.fn(); //arzh1
b.fn.call(window); //arzh
b.fn.call(c); //arzh2
复制代码

从代码可以看出, call 可以直接把 this 指向为传入的第一个参数。我们这里只是通过结果来得出 call 可以改变this指向,那么问题来了, call 函数是怎么改变 this 指向的呢,我们可以看一下 call 的简单实现

Function.prototype.call = function(context) {
    // 这里的this是指调用call的函数
    // b.fn.call => this就是fn,上述已经有类似的例子
    context.fn = this;
    context.fn();
    delete context.fn;
}
复制代码

看到这里,应该就能明白 call 是如何改变 this 指向了吧,也是借用了 作为对象方法调用 的原理,我们只要把传入 call 的第一个参数当成一个对象, 然后直接调用方法,那么 this 自然就指向了这个对象了。我们拿上述 b.fn.call(window) 来说明一下

  1. context 就是传入的 window
  2. window.fn = this = window.fn
  3. 执行 context.fn => window.fn() => window.fn()

作为构造函数调用

JavaScript 中的构造函数也很特殊,构造函数,其实就是通过这个函数生成一个新对象(object),这时候的 this 就会指向这个新对象;如果不使用 new 调用,则和普通函数一样

我们知道在 JS 中普通函数可以直接当构造函数使用,使用 new 来生成新的对象

function Name(b) {
    this.b = b;
    console.log(this)
}
var o = new Name('arzh');
//Name {b: "arzh"}

function Name(b) {
    this.b = b;
    console.log(this)
}
Name('arzh')
//Window
复制代码

可以看出如果是通过 new 生成的对象, this 是指向已经生成的对象的。

我们来分析下具体原因:这里我们引用一下冴羽大大的 new 实现,具体的实现过程可以看 这里

function objectFactory() {
    //新创建一个对象
    var obj = new Object(),
    //获取函数参数的第一个
    Constructor = [].shift.call(arguments);
    //原型关联
    obj.__proto__ = Constructor.prototype;
    //执行第一个参数的函数
    Constructor.apply(obj, arguments);
    //返回创建成功的对象
    return obj;
};
复制代码

这里主要关注一下第四步, new 内部构造对象的时候,使用了 apply 函数, apply 函数的一个参数就是我们需要生成的对象 obj ,根据上述的 使用 apply 或 call 调用 ,我们也就不难理解使用 new 为什么 this 会指向新生成对象了。

箭头函数的this指向(ES6)

es5 中, this 的指向可以简单的归结为一句话: this指向函数的调用者 。那么在 ES6 中其实这句话在箭头函数下是不成立的。我们来看看这个例子

var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : function() {
        console.log(this.a)
    }
}
b.fn(); //'arzh1'
复制代码
var a = 'arzh';
var b = {
    a : 'arzh1',
    fn : () => {
        console.log(this.a)
    }
}
b.fn(); //'arzh'
复制代码

通过这两个例子,我们可以很明显的发现,在箭头函数中的this指向,跟在普通函数中的 this 指向是不同的。

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

所以可以这么理解,在 ES6this 的指向是固定的, this 绑定定义时所在的作用域,而不是指向运行时所在的作用域

上述例子中,箭头函数输出 'arzh' 是因为对象不构成单独的作用域,导致 fn 箭头函数定义时的作用域就是全局作用域

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向

结语

其实 this 指向不难,比较麻烦的场景,只要用心去确定哪个对象调用了函数,就能很快确定 this 的指向,希望看了这篇文章,对你以后判断 this 指向有一定的用处


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

互联网+ 战略版

互联网+ 战略版

刘润 / 中国华侨出版社 / 2015-5-1 / 49.8

1、“互联网+”上升为国家战略,“互联网+”成为下一个超级畅销书的热点话题在商业环境巨变的今天,传统企业该怎么走?传统企业转型是一个系统工程,如何定战略、抓主要矛盾? 2、首本“互联网+传统企业”的战略指导书。“我互联网+”时代到来了,传统企业的外部环境发生了哪些变化?了解商业新生代的新商业环境,跟之前工业时代的不同,从战略上指导传统企业转型,更安全也更大局把握游刃有余。一起来看看 《互联网+ 战略版》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码