完整梳理this指向,看完保证秒懂
栏目: JavaScript · 发布时间: 6年前
内容简介:在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 中是可以通过多种方式的。
- 作为普通函数调用
- 作为对象方法调用
- 使用
apply或call调用 - 作为构造函数调用
那么问题就变成了只要判断函数被哪个对象调用就行了。由此分析下四种不同的函数调用情况(针对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上的属性
window.a === this.a === arzh
;
如果把函数改成这样
var a = 'arzh';
function thisName() {
var a = 'arzh1'
console.log(this.a);
}
thisName(); //'arzh'
复制代码
这个也好理解,因为 this 始终都是指向 window ,那么 window 中 a 变量一直都是'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 调用
apply 、 call 函数都可以用来更改 this 指向,具体的用法可以参考apply和call.因为 apply 与 call 函数在使用上,基本只有传入参数不同的区别( 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) 来说明一下
-
context就是传入的window -
window.fn = this = window.fn - 执行
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对象,就是定义时所在的对象,而不是使用时所在的对象
所以可以这么理解,在 ES6 中 this 的指向是固定的, this 绑定定义时所在的作用域,而不是指向运行时所在的作用域
上述例子中,箭头函数输出 'arzh' 是因为对象不构成单独的作用域,导致 fn 箭头函数定义时的作用域就是全局作用域
this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向
结语
其实 this 指向不难,比较麻烦的场景,只要用心去确定哪个对象调用了函数,就能很快确定 this 的指向,希望看了这篇文章,对你以后判断 this 指向有一定的用处
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Pro HTML5 and CSS3 Design Patterns
Michael Bowers / Apress / 2011-11-15 / GBP 35.50
Pro HTML5 and CSS3 Design Patterns is a reference book and a cookbook on how to style web pages using CSS3 and HTML5. It contains 350 ready--to--use patterns (CSS3 and HTML5 code snippets) that you ca......一起来看看 《Pro HTML5 and CSS3 Design Patterns》 这本书的介绍吧!