this
做为JS
语言中的关键字,其复杂的指向每每是初学者混淆的地方,同时也是面试官常常询问的考点之一,理解好JS
中的this
指向,才能算是迈入了JS
这门语言的门槛,本文将梳理出this
指向的不一样情景,以及如何更好、更准确的判断出this指向,让面试的时候再也不为this
指向头疼。git
在ES5中,判断this
指向的关键是在于这句话,this永远指向调用它的那个对象,想要理解本句话就只有一个重要的点,是谁调用了this
,只要从这个点出发,找到这个谁,那么就能轻车熟路的判断this
的指向。es6
This 被分为三种状况:全局对象、当前对象或者任意对象;判断处于哪一种状况,彻底取决于函数的调用方式github
从这句话能够看出,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上的属性 bash
window.a === this.a === arzh
;
若是把函数改为这样app
var a = 'arzh';
function thisName() {
var a = 'arzh1'
console.log(this.a);
}
thisName(); //'arzh'
复制代码
这个也好理解,由于this
始终都是指向window
,那么window
中a
变量一直都是'arzh',由于thisName
函数中的a
变量不会覆盖了window
上的a
变量。函数
咱们再把函数变一下oop
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
函数均可以用来更改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
会指向新生成对象了。
在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
指向有必定的用处
[1] 阮一峰博客es6教程
[2] 冴羽博客new模拟实现