this关键字
在js中经常能看到各种this,this的理解对初学者有难度,如果弄不明白会寸步难行。网上针对this的解释千篇一律,很多人看了依然懵逼。本文将结合案例和自己的理解来解释this。文章不是教材,会存在口语化,类比的解释,目的是让读者看的更明白。nodejs和chrome环境中运行的结果有差异,会单独指出来。
面向对象
先以java为基础解释面向对象。new一个对象并执行可分为以下几个步骤:
- 定义一个class
- 以class中main方法为入口,然后再定义其他方法如method()
- new 一个对象为obj,然后obj.method()
典型代码如下:
public class Person{
public static void main(String[] args) {
Person obj = new Person();
obj.method();
}
public void method(){
System.out.println("Hello World");
}
}
面向对象的基本用法是new一个实例,也就是对象出来,然后对象就可以调用这个类中定义的方法method。基本语法是obj.method(),我们把这个作为学习js中this关键字的主线。
obj.method()
js语法借鉴了java,是面向对象的编程语言,虽然继承是基于原型来设计的,但obj.method()的思路同样适用于js。
大家平时看到的最基础this使用场景是这样的:
let person = {
name:"alice",
show:function(){
console.log(this);//person对象自己
}
}
person.show();
执行show方法时,this就是person对象,换句话说就是哪个对象在执行方法,符合obj.method(),对象调用方法执行,方法中的this就是对象obj自己。上述代码在nodejs和chrome运行结果相同。
chrome中方法定义
console.log(this);//nodejs环境中为空对象{},chrome中为window对象
function show(){
console.log(this);
}
show();//nodejs中为global对象,chrome中还是window对象
在nodejs中this和global不一样,这个要注意。global对象描述的是nodejs环境信息,也可以理解为是nodejs的自我简介。在这个代码的背后,nodejs是在极不情愿的情况下才返回的global对象,它其实是想返回undefined未定义的。换个理解思路,就是nodejs返回global对象是一个兜底的,不得已的选择。如果在代码首行开启严格模式"use strict",返回的就是undefined。早期js存在设计缺陷,它不是一个完整的编程语言,现在js发展起来有资格说话了,但为了兼容老代码才给了一个兜底的global。如果在chrome中,这个兜底的this就是window对象
上述代码2~4行定义了show方法,如果这3行代码在chrome运行,chrome实际会把这个方法放进this,也就是window对象中。this.show是定义定义,this.show()是方法在执行,是符合obj.method()的。即使此时直接使用show(),show前面不加任何东西,还是相同的结果。因为show前面的this或者window被隐藏了,它始终都符合obj.method()的。
nodejs中方法定义
但这个定义show的方法放在nodejs中会怎么样?也会放进this或者global对象中吗?答案是都不会,因为this.show和global.show都是undefined。在nodejs中定义了show方法,然后执行show(),这其实是一个裸方法(自己给起的名字,不要较真),不符合obj.method()。针对这种裸方法,它是没有this的,它就应该返回undefined(开启严格模式)。没开启严格模式,nodejs会给个兜底的global对象。这样就解释了同样的代码,在不同环境下的运行差异。看是否符合obj.method(),和兜底的this不同,nodejs兜底的this是global,chrome兜底的是window。
apply、bind、call
前面提到了obj.method(),对象obj在方法左边。如果是裸方法method()执行,method方法里的this就会被兜底的global或者window取代。js中引入call、apply、bind关键字,就是为了将裸方法拉回来,强行给一个obj。
method.call(obj),method.apply(obj),method.bind(obj)()
obj由开始的在左边,现在变为右边了。bind返回的是函数,因此还要单独加个括号表示执行
案例解析
案例1
var x = 1;//chrome中,本质是this.x = 1或者window.x=1;
//nodejs,就是一个普通变量,既不在this中,也不在global中
function test() {
console.log(this.x);
}
test(); // nodejs结果为undefined,因为是裸方法,test方法中的this就是兜底的global,
//但global中又没有x字段,只能undefined。
// chrome中运行结果为1,因为实际执行的是this.test(),符合obj.method(),这里this就是window,
//而第一行代码中也提到了window.x就是1。这个window是合法合理的,和用来兜底的window对象不要混为一谈,
//兜底的window对象是给裸方法准备的,是为实在找不到this准备的,这里不是裸方法。
案例2
var obj = {
bar: 1,
foo: function () { console.log(this.bar) }
};
var foo = obj.foo;//obj.foo是方法引用,简单点说就是把原本在obj对象中的方法单独捞出来了
var bar = 2;//chrome中就是this.bar = 2或者window.bar = 2
//nodejs中就是普通变量,不在this或者global对象中
obj.foo() // 1 符合obj.method(),自然就是1
foo() // 前面把方法捞出来了,这里要执行
//nodejs中就是裸方法执行,this就是兜底的global对象,而global对象又没有bar字段,故返回undefined;
//chrome中实际执行的是this.foo(),符合obj.method(),this为window对象,而bar就在window对象中,
//故返回2;