前言
在我自学前端的几个月中,this指向在我曾经刷面试题中,给我看的晕头转向,所以我觉得有必要重新梳理一下在JS中this指向的知识点。
This指向
在ES5中,This指向始终坚持一个原则:This永远指向最后调用他的那个对象
接下来我们来看一个简单的例子:
var name = 'zzz'
function a() {
var name = 'hhh';
console.log(this.name);
console.log('inner'+this);
}
a()
console.log('outer'+this);
根据This指向的原则This永远指向最后调用他的那个对象,我们看函数a,最后调用它的地方为a(),在它前面没有调用对象,在一般情况下,前面没有调用对象的时候,它的调用对象就是全局对象window,这就相当于window.a(),函数a()的this的指向为window,这也是为啥this.name输出的值为zzz。
注意:这里我们没有使用严格模式,如果使用严格模式的话,全局对象就是undefined,那上述代码中的this.name就会报错。
接着我们再看一个例子:
var name = 'zzz';
var a = {
name : 'hhh',
fn : function () {
console.log(this.name);
}
}
a.fn()
在上述代码中,函数fn就是对象a调用的,函数fn的this指向为a,所以输出的name为hhh
在这个代码的基础上,我们做一点小小的改动:
var name = 'zzz';
var a = {
name : 'hhh',
fn : function () {
console.log(this.name);
}
}
window.a.fn()
在这里,我们使用window.a.fn(),一开始使用了window来调用fn最后又使用a来调用,但因为This指向始终坚持一个原则:Thie始终指向最后调用它的那个对象,所以这里输出的结果为hhh。
接下来我们再来看一个例子:
var name = 'zzz';
var a = {
fn : function () {
console.log(this.name);
}
}
window.a.fn()
这里由于函数fn的指向还是a,但由于对象a中缺少对属性name的定义,所以最终输出的结果为undefined,在对象a外面还有一个name的变量,但由于两个处于不同的作用域,就算在对象a这个作用域中没有this.name,它也不会去上一个对象的作用域中去寻找name这个属性。
接着我们再来看一个比较离谱的例子:
var name = 'zzz';
var a = {
name : 'hhh',
fn : function () {
console.log(this.name);
}
}
var b = a.fn;
b()
看到这里,我们可能会产生一些疑问,为啥输出不是hhh,而是zzz,我们先来看下谁调用了fn,在代码中,我们一开始只是将对象a中的函数fn赋值给变量b,对象a并没有调用这个方法,接下来变量b执行了方法,也就是说变量b执行了函数fn,此时变量b的this指向为window,所以输出为zzz。
通过上面的例子,我们可以看出This的执行不是在创建的时候确定的,而是在它执行的时候才确认的,在ES5中,This的指向永远指向最后调用它的对象。
如何改变This指向
改变This指向
- 使用ES6的箭头函数
- 在函数内部使用_this = this
- 使用apply,call,bind
- new实例化一个对象
箭头函数
在介绍箭头函数之前,我们先来看一段代码:
let name = 'zzz';
let a = {
name : 'hhh',
fn1 : function() {
console.log(this.name);
},
fn2 : function () {
setTimeout(function () {
this.fn1()
}, 1000);
}
}
a.fn2()
上述代码,使用了setTimeout函数,这个函数在不改变他的This指向之前,它的This指向永远指向window,正因为这个原因,我们在调用这个函数时,会出现错误,在开发过程中,我们会经常使用延时函数,此时我们就需要改变setTimeout函数的This指向来使用它。
在ES6中,提出了箭头函数,箭头函数的This始终指向函数定义时的This,而非执行时,箭头函数没有This绑定,必须通过查找作用域链来决定其值。如果箭头函数被非箭头函数包含时,则This绑定的是最近一层非箭头函数的this,否则This就为undefined。
let name = 'zzz';
let a = {
name : 'hhh',
fn1 : function() {
console.log(this.name);
},
fn2 : function () {
setTimeout( ()=> {
this.fn1()
}, 1000);
}
}
a.fn2()
上述代码中,在函数fn2中,setTimeout函数中使用了箭头函数,箭头函数的this在它定义时就被确定了,它继承了它外层执行环境中的fn2的执行环境的this。
在函数内部使用 _this = this
这个方法是我看别人使用才懂的,如果不使用ES6,这种方式是最简单的。这个方法主要是:将调用这个函数的对象保存在变量_this中,然后在函数中都使用这个_this,这样_this就不会改变。
let name = 'zzz';
let a = {
name : 'hhh',
fn1 : function fn1() {
console.log(this.name);
},
fn2 : function fn2() {
let _this = this
setTimeout( function () {
console.log(_this.name);
},1000)
}
}
a.fn2()
在fn2中,我们先设置_this = this,在调用时,fn2的this指向为a,在setTimeout中本来的this指向应该为window,但我们调用的是_this,此时_this的指向为a。
使用apply,call,bind
call
call方法是使用一个指定的this和单独给出一个或多个函数来调用一个函数。
语法:
function.call(thisArg, arg1, arg2, ...)
调用call函数必须是一个函数,call函数接收两个参数,第一个参数接收一个参数名,可以使调用这个函数的this指向变为接收的函数。第二个参数为一个参数列表,表示的是调用这个函数所需要的参数。
let person = {
fullName : function () {
return this.firstName + '' + this.lastName
}
}
let person1 = {
firstName : 'w',
lastName : 'x'
}
let person2 = {
firstName : 'a',
lastName : 'g'
}
console.log(person.fullName.call(person1));
apply
apply函数的使用与call大致相同,区别是两个函数接收的第二个参数,call接收的是一个参数列表,而apply接收的是一个数组。
语法:
apply(thisArg, argsArray)
例子:
let person = {
fullName(city) {
return this.firstName + ' ' + this.lastName + ' ' + city
}
}
let person1 = {
firstName : 'a',
lastName : 'g'
}
console.log(person.fullName.apply(person1,['xm']));
bind
bind的使用与call的用法差不多,但bind返回的是一个函数。
语法:
function.bind(thisArg[, arg1[, arg2[, ...]]])
例子:
let hello = function (a,b,c,d) {
console.log(this.name);
console.log(a,b,c,d);
}
let demo = {
name: 'demo'
}
let h = hello.bind(demo,1,2)
h()