前言
this是很多初学者容易混淆的概念,其实它并不难,在这篇文章中,你将会弄清this关键字指的是什么;同时,你还将学习到.call,.apply,.bind和new以及箭头函数等知识。
this是什么
this 是JavaScript中很特别的一个关键字,被自动定义在所有函数作用域中,但是它即不指向函数自身也不指向函数的词法作用域,它指向的是调用函数的对象。
this到底指向哪里
要判断this指向哪里,我们必须理解每个函数的this并不是在声明时就被绑定的,而是在调用时被绑定。
正如上文所说this指向的是调用函数的对象,所以要判断this到底指向哪里,首先我们要知道 “是哪个对象调用了函数?”
举个例子来帮助同学们理解这一点:
例子1:
function person(name) {
console.log(name);
}
可以看到例子里声明了一个person函数,接收一个name参数,想知道name会打印出什么,必须得看person函数调用过程中传入参数是什么。同样的道理想判断this指向哪里,就得看函数调用方式是什么。
根据函数调用方式不同,我将其分为下面👇几种情况:
1、首先就是最常用的函数调用方法:函数名直接调用
例2:
function person() {
console.log(this);
}
function personStrict() {
'use strict'
console.log(this);
}
person(); // window
personStrict(); // undefined
可以看到,函数被直接调用时,就相当于全局对象调用的;这样它的 this就指向全局对象:window;
PS:但是在严格模式下,this不会指向全局对象,而是指向undefined,这是为了尽量减少不严谨的出错行为,你只需要在你所写代码作用域的最顶端加上use strict;建议将其放在一个立即执行函数中,避免污染全局。
例3:
(function(){
//'use strict'
function getDoc() {
console.log(this.document);
}
getDoc();
})();
可以看到,如果程序在非严格模式下运行,不会有错误抛出,那是因为在全局对象window中存在一个名为document的属性,而在严格模式下,由于此时this指向undefined,于是抛出一个错误。
2、作为对象的方法调用
例4:
let name = 'window';
let person = {
name : 'Heternally',
getName : function(){
console.log(this.name);
}
}
person.getName(); // 'Heternally'
let name = 'window';
let person = {
name : 'Heternally',
getName
}
function getName(){
console.log(this.name)
}
person.getName(); // 'Heternally'
可以看到在上面👆例子中,getName函数是对象person调用的,所以打印出来的值是person中name的值
例5:
let name = 'window';
let person = {
name : 'Heternally',
getName : function () {
console.log(this.name);
}
}
let getName = person.getName;
getName(); // 'window'
例5对例4做了一点点小改动,可以看到打印出的结果就是window了,这是因为getName函数最终还是被window调用,所以这个this指向的是window
这又应了上文的话,this的指向不能在声明的时候确定,而是取决于谁调用了它
例6:
let person1 = {
name : 'person1',
getThis: function () {
console.log(this);
}
}
let person2 = {
name : 'person2',
getThis: function () {
return person1.getThis();
}
}
let person3 = {
name : 'person3',
getThis: function () {
var getThis3 = person1.getThis;
return getThis3();
}
}
person1.getThis(); // person1
person2.getThis(); // person1
person3.getThis(); // window
看到这可能有同学会问,不是说this的指向取决于谁调用了它吗,那为什么person3.getThis()打印出来的是window呢,因为在person3.getThis里,调用this的函数是getThis3,所以此时this指向了window
由上面👆几个例子可以得出,this指向最后调用它的那个对象。
常用改变this指向方法
1、call、apply、bind
这三个函数的作用都是改变函数执行时的上下文,即改变函数运行时的this指向。有了这个人生,接下来我们通过几个例子来学习如何使用这三个函数。
简单说一下三个方法的用法:
1.1 call
fun.call(thisArg[, arg1[, arg2[, ...]]])
它会立即执行函数,第一个参数时指定执行函数中this的上下文,如果不传或者传null、undefined,则表示this指向window,后面的参数是执行函数所需的参数;
1.2 apply
fun.apply(thisArg[, [arg1, arg2, ...]])
它会立即执行函数,第一个参数时指定执行函数中this的上下文,如果不传或者传null、undefined,则表示this指向window,第二个参数接收一个数组(这是与call唯一的区别);
1.3 bind
var foo = fun.bind(thisArg[, arg1[, arg2[, ...]]]);
它不会立即执行函数,而是返回一个新的函数,这个新的函数被指定了this上下文,接收的参数与call一致;
例7:
let person = {
name : 'Heternally'
}
var name = 'window';
function getName() {
console.log(`Hello, my name is ${this.name}`);
}
getName(); // 'Hello, my name is window'
getName.call(person); // 'Hello, my name is Heternally'
getName.apply(person); // 'Hello, my name is Heternally'
getName.bind(person)(); // 'Hello, my name is Heternally'
可以看到,使用call、apply、bind方法后,可以动态改变函数执行上下文的this指向
2、new关键字
每当使用new关键字调用函数时,会自动把this绑定在新对象上,然后再调用这个函数
例8:
function Person(name) {
this.name = name;
console.log(this);
}
var people = Person('Heternally'); // window
var people1 = new Person('Heternally'); // Person {name: "Heternally"}
people.name; //Cannot read property 'name' of undefined
people1.name; // 'Heternally'
3、ES6箭头函数
在ES6中,新加入了箭头函数,它和普通函数最不同的一点就是,对于箭头函数的this指向,只需要看它是在哪里创建即可
例9:
var person = {
name : 'Heternally',
getName1 : function () {
setTimeout(function(){
console.log(this);
},1000)
},
getName2 : function () {
setTimeout(()=>{
console.log(this);
},1000)
}
}
person.getName1(); //window
person.getName2(); // {name:'Heternally',getName1:f,getName2:f}
可以看到,在getName2方法的setTimeout函数中,没有跟getName1中setTimeout函数一样打印window,而是打印出person对象。
简单说:箭头函数中的this只和定义它时作用域有关,并不受在哪里及如何调用的影响;
优先级
了解了this指向及多种改变this指向的方法后,我们还需要了解这多种方法的优先级,
函数直接调用 < 对象方法调用 < call/apply < bind < new < 箭头函数
总结
要判断this指向,需要找到是这个函数的直接调用位置,找到之后可以依据如下方法进行判断this的指向: