在很多编程语言中都有this这个特殊关键字的存在,比如Java中的this,还有本文要说到的JavaScript中的this。那么,JavaScript中this究竟有什么特性和用法呢?它又是如何定义的呢?
先来看看ECMAScript 标准规范对this 的定义:
「The this keyword evaluates to the value of the ThisBinding of the current execution context.」
「this 这个关键字代表的值为当前执行上下文的ThisBinding。」
然后再来看看MDN 对this 的定义:
「In most cases, the value of this is determined by how a function is called.」
「在大多数的情况下,this 其值取决于函数的调用方式。」
而JavaScript中this有个有趣的特性,就是它不是固定不变的,它会随着执行环境的改变而改变 大致如下:
①在函数(方法)被调用时,在方法中的this 表示始终指向最后调用该方法时的那个对象;
在上面的例子中可以看出当全局环境下定义了和对象obj上属性同名的变量时,在对象上的方法被调用的会先在方法作用域内查找需要的变量如name、fn,如果能够找到就取值使用,找不到就沿着作用域链往创建这个方法fn的那个作用域obj去查找,如此一层一层网上一直找到最顶层的window为止。但是本例子关注的是this的指向,通过打印知道是指向了obj,而调用对象方法fn最近也就是最后的那个对象刚好就是obj,因此佐证了this的通俗定义【始终指向最后调用该方法的那个对象】。
另外在本例子中可能还会有人疑惑为何obj方法fn里面的name打印出来不是obj的那个name值123,反而是全局变量的2333?这里解释一下,首先我们知道要访问一个对象上的属性,规定的格式是 obj.属性或者obj[属性]。而此时打印的name并没有obj,因此会被当成一个变量去解析,这时候会在fn函数作用域查找有没有name变量,没有就往作用域链一层一层往上找(也有人认为是当没有明确的调用对象比如obj时,this直接指向了window对象)。此时的作用域链是这样子的:obj._proto_===window.prototype===window._proto_ ,根据这个作用域链最终找到了window的对象原型上有name这个变量,这就找到了!
②在全局环境中,无论是否是在严格模式下,this 都是指向全局对象,在浏览器中指向 window 对象,在Nodejs中的Global对象,在严格模式下指向的是undefined,因此严格模式下定义的全局变量在调用时就不可以使用this调用;
1 var num = 123;
2 console.log(this.num ) ; // 123
3 console.log(this.num === num ) ; // true
4 console.log(this === window) ; // true
严格模式下:
"use strict" // 使用严格模式
var num= 123; function fn() {
console.log(this); // undefined
console.log(this.num); // 报错 "Cannot read property 'num' of undefined",因为此时 this 是 undefined
} fn();
③在构造函数中,this就是被指向通过构造方法创建的那个实例对象。
1 var name="JavaScript";
2
3 function Student(sName,sAge){
4 this.name=sName;
5 this.age=sAge;
6 console.log(this); //打印实例对象Student{name:"张三",18}和Student{name:"李四",20}
7 }
8
9 var s1=new Student("张三",18);
10 var s2=new Student("李四",20);
以上都是讲了怎么讲this的指向的那个对象找出来,以便达到我们使用对象上的属性和方法的目的。然而,this的指向还可以人为地手动通过call、apply、bind这三个方法进行改变,这样有时候能满足某个方法里直接使用this重新指向的那个对象obj上的方法和属性,但是又不改变方法原来的对象。
从上图打印可以看出call将原来ok方法this的指向由obj变为newObj,这样ok方法就可以使用newObj对象上的属性和方法了,而第17行的打印输出说明原对象obj上的属性方法并没有被破坏。
call、apply、bind三个方法实现的功能(都是用来扩充函数运行时的作用域)是一样的,最后顺便补充说说三者之间的写法上一些小区别:
1)、bind()方法会返回一个函数,将接受多个参数的函数变换成接受一个单一参数。
注意:bind(thisArg[, arg1[, arg2[, ...]]]), 将接受多个参数的函数变换成接受一个单一参数。bind()方法所返回的函数的length(形参数量)等于原函数的形参数量减去传入bind()方法中的实参数量(第一个参数以后的所有参数),因为传入bind中的实参都会绑定到原函数的形参。
2)、apply()方法 接收两个参数,一个是函数运行的作用域(this),另一个是参数数组。
注意:apply([thisObj [,argArray] ])
,调用一个对象的一个方法,2另一个对象替换当前对象。如果argArray不是一个有效数组或不是arguments对象,那么将导致一个TypeError,如果没有提供argArray和thisObj任何一个参数,那么Global对象将用作thisObj。
3)、call()方法 第一个参数和apply()方法的一样,但是传递给函数的参数必须列举出来。
注意:call([thisObject[,arg1 [,arg2 [,...,argn]]]]);,应用某一对象的一个方法,用另一个对象替换当前对象。call方法可以用来代替另一个对象调用一个方法,call方法可以将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象,如果没有提供thisObj参数,那么Global对象被用于thisObj。
【三个方法记忆小窍门】call就像打电话召集需要的参数,一个一个跟在thisObj后面,apply就像填表格申请某样东西一样把需要的参数打包在一个数组里,bind是把参数一个一个捆绑装订在thisObj后面,跟call类似,但最后bind只返回一个函数,还需要自己手动调用一下(像快递员集中揽件后还要发货一样)。这三个方法如果用在定时器的匿名回调函数中时有破坏定时器的效果,因为他们会立即执行!
④都说ES6大法好,那么this在ES6中又是如何表现的呢?ES6带来的箭头函数让开发者眼前一亮的不止代码写法更简洁优雅了,还让函数中飘忽不定的this有了官方指定的去处!
在箭头函数中,this区别于传统的普通函数中的this,普通函数中this指向的是最后调用函数的那个对象(也就是函数被调用运行时的对象);而箭头函数的this却是在定义箭头函数时由外层函数的this“继承”(不是真正的继承,有点像借用更合适)过来的,换句话说就是实际上箭头函数自身并没有this,它依赖的是外层代码定义时那个this。箭头函数的this不是调用的时候决定的,而是在定义的时候包含着箭头函数的对象就是它的this。这里面其实也应该包含一个查找过程,如下:箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。
情景一:箭头函数的外层是函数,此时的外层函数是由obj最后调用了,于是this指向了obj,箭头函数“继承”了它。
情景二:对象上的方法直接定义为箭头函数时,由于箭头函数外层是对象,不是一个函数,此时this直接指向了window。
箭头函数带来便捷的同时也有一些需要留意的点,如下:
①在箭头函数中使用call、apply、bind时this参数绑定失效了,this被强制指向了window,至于原因本人尚未清楚,如有人知道可以在评论告知,不甚感激。
1 var name="Hello watching me!";
2 var obj={
3 name:"coder4web",
4 arrowFn:()=>{ //直接将对象属性设置为箭头函数情况
5 console.log("this===newObj的结果是:"+(this===newObj));
6 console.log("this===obj的结果是:"+(this===obj));
7 console.log(this.name+"--is testing :I'm an arrow function!");
8 }
9 };
10 var newObj={
11 name:123,
12 ok:function(){
13 console.log("哈哈哈哈~~");
14 }
15 };
16 // obj.arrowFn.apply();
17 // obj.arrowFn.apply(undefined);
18 // obj.arrowFn.apply(null);
19 obj.arrowFn.apply(newObj); //这4种方式的效果是一样的
②箭头函数不能作为构造函数,因为箭头函数都是匿名函数,而构造函数是具名函数,因此箭头函数也不能使用new,箭头函数也不能用argument,没有prototype属性。
1 var Student=(name)=>{
2 this.name=name;
3 }
4 // var s=new Student("李雷");
5 // console.log(s); //Uncaught TypeError: Student is not a constructor
6
7 var fn=(x)=>{
8 console.log(x);
9 // console.log(arguments); //Uncaught ReferenceError: arguments is not defined
10 }
11 // fn(11);
12
13 var t1=function(){}
14 var t2=function teacher(){}
15 var t3=()=>{}
16 console.log(t1);
17 console.log(t1.prototype);
18 console.log(t2);
19 console.log(t2.prototype);
20 console.log(t3);
21 console.log(t3.prototype);
至此,JavaScript中的this指向问题探究完毕,以上内容的大部分结论都是阅读思考后自己的总结,鉴于水平有限难免有认识上误差,如果有更好的见解,欢迎在评论区指正提建议哦!谢谢大家的阅读~
本文属于本人在博客园的第一篇原创文章,希望是个好的开始,一起加油!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。