在面向对象的语言中(例如Java,C#等),this 含义是明确且具体的,即指向当前对象。一般在编译期绑定。
然而js中this 是在运行期进行绑定的,这是js中this 关键字具备多重含义的本质原因。下面就让我们一起来分析一下具体情况。
由于js中this 是在运行期进行绑定的,所以js中的 this 可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:
- 作为对象方法调用
- 作为函数调用
- 作为构造函数调用
- 使用 apply 或 call 调用
JavaScript this决策树
┌─是:this指向新创建的对象 │ 函数是否用new调用 ┌─是:this指向dot前面的对象 │ │ └─否:函数是否用dot(.)进行调用 │ └─否:this指向全局对象window
根据这个决策树我们需要进行两步判断:
1.函数调用是用new进行调用的吗?如果是,则this指向新创建的对象,否则,进入”否”分支;
2.判断该函数是否是用dot(.)进行调用的,如果是,则即进入”是”分支,即this指向dot(.)之前的对象;否则this指向全局对象window.
demo1:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> function test(a){ this.a = a;//相当于window.a } test(3);//这里相当于window.test(3) //所以test函数中的this指向了window console.log(a);//3 这里相当于与访问window.a </script> </body> </html>
demo2:
对于say方法中的sayA和sayB中的this来说:不是通过new操作符来调用的,也没有通过dot(.)来调用,因此this指向window
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var obj = { a:0, b:0, say:function(a,b){ var sayA = function(a){ console.log(this);//Window this.a = a; }; var sayB = function(b){ console.log(this);//Window this.b = b; }; sayA(a); sayB(b); } } obj.say(1,1); console.log(obj.a+"--"+obj.b);//0--0 console.log(window.a+"-----"+window.b);//1-----1 </script> </body> </html>
demo3:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>面试题</title> </head> <body> <script type="text/javascript"> function Person(name,age){ this.name = name; this.age = age; } var person1 = new Person("lisi",18); console.log(person1.name);//lisi var person2 = Person("wangwu",12); //person2是undefined // console.log(person2.name);//Uncaught TypeError: Cannot read property 'name' of undefined console.log(window.name);//wangwu //Person("wangwu",12)相当于window.Person("wangwu",12) </script> </body> </html>
demo4:
apply 和 call 这两个方法切换函数执行的上下文环境(context),即可以改变this指向。obj1.say.call(obj2,3,3)实际上是obj2.say(3,3),所以say中的this就指向了obj2
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> function Test(a,b){ this.a = a; this.b = b; this.say = function(a,b){ this.a = a; this.b = b; } } var obj1 = new Test(1,1); var obj2 = {a:2,b:2}; obj1.say.call(obj2,3,3); console.log(obj2.a+"--"+obj2.b);//3--3 </script> </body> </html>
demo5:
this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象.当然这句话不完全准确,因为在不同环境下情况就会有不同。下面我们就来分析一下各种情况。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> // 这里的函数test实际是被Window对象调用的 function test(){ var name = "lisi"; console.log(this.name);//undefined console.log(this);//Window } test();//这里相当于window.test(); </script> </body> </html>
demo6:
如果一个函数中有this,这个函数有被上一级的对象所调用,那么this指向的就是上一级的对象。
这个例子就是指向上一级对象obj
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var obj = { name:"lisi", sayName:function(){ console.log(this.name);//lisi } } obj.sayName(); </script> </body> </html>
demo7:
对象字面量obj={}
变量obj其实也是window对象的属性
所以可以这样来调用window.obj.sayName();
如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象,这里this指向obj,而不是window
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var name = "wangwu"; var obj = { name:"lisi", sayName:function(){ console.log(this.name);//lisi } } window.obj.sayName(); </script> </body> </html>
demo8:
如果一个函数中有this,这个函数中包含多个对象,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var obj = { name:"wangwu", objInner:{ name:"lisi", sayName:function(){ console.log(this.name);//lisi } } } obj.objInner.sayName(); </script> </body> </html>
demo9:
尽管对象objInner中没有属性name,这个this指向的也是对象objInner,因为this只会指向它的上一级对象,不管这个对象中有没有this要的东西。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var obj = { name:"wangwu", objInner:{ // name:"lisi", sayName:function(){ console.log(this.name);//undefined } } } obj.objInner.sayName(); </script> </body> </html>
demo10:
从这个例子我们可以看出:this永远指向的是最后调用它的对象,也就是看它执行的时候是谁调用的
在这个例子中虽然函数sayName是被对象objInner所引用,但是在将sayName赋值给变量fn的时候并没有执行,所以this最终指向的是window对象。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>this指向</title> </head> <body> <script type="text/javascript"> var obj = { name:"wangwu", objInner:{ name:"lisi", sayName:function(){ console.log(this.name);//undefined console.log(this);//Window } } } var fn = obj.objInner.sayName; fn(); </script> </body> </html>
demo11:
对象person可以点出构造函数中的name是因为new关键字可以改变this的指向,将这个this指向对象person.
这里new Person()创建了一个Person对象的实例,并赋值给了变量person
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>构造函数版this</title> </head> <body> <script type="text/javascript"> function Person(){ this.name = "lisi"; } var person = new Person(); console.log(person.name);//lisi </script> </body> </html>
demo12:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>当this碰到return</title> </head> <body> <script type="text/javascript"> /* 如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例 */ function fn2(){ this.username = "lisi"; return {}; } var obj = new fn2; console.log(obj);//Object{} console.log(obj.username);//undefined function fn1(){ this.username = '王五'; return function(){}; } var b = new fn1; console.log(b);//function(){} console.log(b.username);//undefined function fn() { this.username = '王五'; return 1; // return undefined; } var a = new fn; console.log(a);//fn {username: "王五"} console.log(a.username); //王五 //虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊 function fn3() { this.username = '王五'; return null; } var a = new fn; console.log(a);//fn {username: "王五"} console.log(a.username); //王五 </script> </body> </html>