谈"this"
首先,很多人对this有两个误解
1.this指向的是当前函数本身
什么意思呢,下面看一段代码。
1 function func(){ 2 this.a ++; 3 } 4 5 var a = 0; 6 func.a = 0; 7 8 for(let i = 0; i < 5; i++){ 9 func(); 10 } 11 12 console.log(a); //->5 13 console.log(func.a);//->0
发现问题了,因为函数本身也是一个对象,如果this指向的是当前函数本身,那么在调用func函数5次后,func.a应该输出的是5,但是程序结果输出的是0,但是全局定义的a变量输出了结果5,显然在这里函数中this指向的不是函数本身,而指向了全局对象.
这是为什么呢?接着往下看。
2.this指向的是当前函数的作用域
先看一段代码:
function foo(){ var a = 0; this.bar(); } function bar(){ console.log(this.a); } foo();//->undefined
这段代码在按照“this指向的是当前函数的作用域”这样的理解下,在理想的执行状态下是这样子的:
//foo(); function foo(){ var a = 0; console.log(a); }
因为按照上文理解,this指向的是当前的函数作用域,所以这段代码能够执行成功是有问题的,如果this指向的是当前的函数作用域,foo函数的词法作用域中并没有一个标识符为bar的函数;
在严格模式下,这段代码是无法执行的(报错)。
this到底是一个什么样的机制呢?那么这里下一个定义:this的指向是取决于函数调用的方式,跟函数怎么写没有半毛钱关系。this的指向并没有在编写代码的时候就绑定了,而发生在函数的调用时。
当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。
函数调用位置的追踪:
function baz(){ //当前的调用栈是:baz //因此,当前调用位置是全局作用域 console.log("baz"); bar(); // <-- bar的调用位置 } function bar(){ //当前调用栈是:baz -> bar //因此,当前调用位置在baz中 console.log("bar"); foo(); // <-- foo的调用位置 } function foo(){ //当前的调用栈是:baz -> bar -> foo //因此,当前的调用位置在bar中 console.log("foo"); } baz(); //<-- baz的调用位置
既然this的绑定取决于函数的调用方式,那么this存在下面这四种绑定规则:
- 默认绑定
- 隐式绑定
- 显示绑定
- new绑定
默认绑定:简单地来讲,默认绑定就是单独的,没有任何修饰符的函数调用,就像这样。
foo();
这时,this指向全局对象,下面给这个函数加上this来试一试。
function foo(){ console.log(this.num); } var num = 6; foo(); //->6
但是,只有在非严格模式下,this在默认绑定下才能绑定到全局对象上;如果在严格模式下,this会绑定到undefined。
记住了,就光写一个光秃秃的函数调用,就是默认绑定,这时this指向的是全局对象(在非严格模式下)。
隐式绑定:又是简单地来讲,隐式绑定就是当这个函数作为某个对象的方法被调用时,this就会指向这个对象。
obj.foo();
考虑一下调用的位置是否有上下文对象,就是说这个函数是否被某个对象拥有或包含。
function foo(){ console.log(this.a); } var obj = { a : 2, foo : foo }; obj.foo(); // -> 2 这时foo函数的this指向obj这个对象
因为这时this函数指向的就是包含它的obj对象,所以此时this.a === obj.a
这就叫做隐式绑定,不过隐式绑定有一个问题,最常见的问题就是被隐式绑定的函数会丢失绑定对象,怎么回事呢,来看一下下面的代码:
function foo(){ console.log( this.a ); } var obj = { a : 2, foo : foo }; var bar = obj.foo; var a = "global"; //a是全局对象的属性 bar(); // -> "global"
看到没?本来foo应该指向obj来着,现在指向了全局对象,是怎么回事呢?那是因为obj的方法foo作为一个单独的函数被赋值给了bar,bar就成了foo函数的别名,输出一下bar,看看里面是什么:
这里可以看到,bar的内容是foo函数的函数体,所以这里调用bar,与foo()语句等价,使用了默认绑定,this指向全局对象,所以才会输出"global",在编程中,要注意这个细节。
显式绑定:显式绑定说白了就是函数使用call方法或apply显式地指定了this的指向。
例如:
function foo(){ console.log( this.a ); } var obj = { a : 2 }; foo.call( obj ); //->2
说到了call和apply,他俩也没啥区别,只是调用时写法有点不一样:
func.call(this, arg1, arg2); func.apply(this, [arg1, arg2]);
call的函数参数一直写,中间用逗号隔开,apply的函数参数以数组形式给出。
new绑定:这个就不用说了,在用new调用函数时,函数内的this指向当前对象。
在JavaScript中,没有明显的标识符什么的说明构造函数是构造函数,实际上,Javascript不存在构造函数,只有被“构造调用”的普通函数。new关键字将一个普通函数“构造调用”,并且将这个普通函数的this与创建的对象相关联。
function Person( name, sex ){ this.name = name; this.sex = sex; } // -> Person的this现在指向xiaoming var xiaoming = new Person("小明","男");
那么基本的this指向问题就这么简单地说完了,总结一下,this的指向其实跟函数如何编写没有什么关系,只跟函数调用的方式有关系。希望这篇文章可以帮到大家更好地理解this的问题,如果有补充或者疑问,请在评论区留言,谢谢!