参考:阮一峰《javascript的this用法》及《JS中this关键字详解

this是Javascript语言的一个关键字它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。定义:this是包含它的函数作为方法被调用时所属的对象。总原则,this指的是,调用函数的那个对象,this永远指向函数运行时所在的对象!而非创建时的。

以下是基于浏览器环境做的测试:

作为函数调用:

  1. function $(){
  2. this.count = 1;
  3. return this;
  4. }
  5. window.onload = function(){
  6. console.info($());
  7. }

控制台返回结果如下:

一个window对象。

对于内部函数,即声明在另外一个函数体内的函数,这种绑定到全局对象的方式会产生一个问题,即它会隐式声明全局变量。代码如下:

  1. var point = {
  2. x: 0,
  3. y: 0,
  4. moveTo: function(x, y) {
  5. var fn1 = function(x) {
  6. this.x = this.x + x;
  7. return this.x;
  8. };
  9. var fn2 = function(y) {
  10. this.y = this.y + y;
  11. };
  12. return fn1();
  13. }
  14. }
  15. console.log(point.moveTo());

结果是:

而若将fn1中return的值改为this的话,打印结果:

一个window全局对象。这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,这个设计错误错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数的this被绑定了错误的值,所以不能共享该方法对对象的访问权。为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。如下:

  1. var point = {
  2. x: 0,
  3. y: 0,
  4. moveTo: function(x, y) {
  5. var that = this;
  6. var x = x;
  7. var y = y;
  8. var fn1 = function(x) {
  9. that.x = that.x + x;
  10. return that;
  11. };
  12. var fn2 = function(y) {
  13. that.y = that.y + y;
  14. };
  15. return fn1(x);
  16. }
  17. }
  18. console.log(point.moveTo(1,1));  

返回结果:

函数调用中,this总是指向函数的直接调用者(而非间接调用者)。如果在严格模式下,有可能输出undefined,严格模式下,禁止this关键字指向全局对象。如下:

  1. 'use strict';
  2. console.log(this === window); // true
  3. var foo = function() {
  4. console.log(this === window);
  5. console.log('this:',this);
  6. };
  7. foo();
  8. window.foo();

控制台打印结果是:

使用that的另一个例子:

  1. var myObj = {
  2. value: 0,
  3. increment: function(inc) {
  4. this.value += typeof inc === 'number' ? inc: 1;
  5. }
  6. }
  7. myObj.increment();
  8. document.writeln(myObj.value);
  9. myObj.increment(2);
  10. document.writeln(myObj.value);
  11. function add(num1, num2){
  12. return num1 + num2;
  13. }
  14. console.log(add(40,27));
  15. myObj.double = function() {
  16. var that = this;
  17. var helper = function() {
  18. that.value = add(that.value, that.value);
  19. }
  20. helper();
  21. }
  22. myObj.getValue = function() {
  23. var that = this;
  24. return that.value;
  25. }
  26. myObj.double();
  27. document.writeln(myObj.getValue());

作为对象方法调用:

如果一个调用表达式包含一个属性存取表达式(即一个.点表达式或者[subscript]下标表达式),那么它被当做一个方法来调用。另一段代码:

  1. var o = {};
  2. o.fn = $;
  3. function $(){
  4. console.log(this);
  5. }
  6. window.onload = function(){
  7. o.fn();
  8. }

控制台返回结果如下:

调用函数的o对象。

在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;

另外一种嵌套调用:

  1. var personA={
  2. name:"xl",
  3. showName:function(){
  4. console.log(this.name);
  5. }
  6. }
  7. var personB={
  8. name:"XL",
  9. sayName:personA.showName
  10. }
  11. personB.sayName();

控制台输出结果:

从这里很明显的看出,在js中this关键字指向是直接调用它的对象,而非间接的。

作为函数及对象方法的混合调用:

  1. var myObject={
  2.  
  3. foo : "",
  4. func : function(){
  5.  
  6. var self = this;
  7. console.log("outer func : this.foo = " + this.foo );
  8. console.log("outer func : self.foo = " + self.foo );
  9.  
  10. (function(){
  11. console.log("inner func : this.foo = " + this.foo );
  12. console.log("inner func : self.foo = " + self.foo );
  13. }());
  14. }
  15. }
  16. myObject.func();

输出结果如下:

证明函数内部函数的调用是由全局对象引发的,这在上面有所阐述。

将构造函数作为函数调用:

  1. function Person(name) {
  2. this.name = name;
  3. }
  4. var personA = Person("xl");
  5. // console.log(personA.name);
  6. console.log(window.name);
  7. console.log(name);
  8. var personB = new Person("xl");
  9. console.log(personB.name);

控制台打印结果:

注释掉的console因为那么已经成为全局对象的属性,因此打印为未定义变量报错。第二个显式调用,第三个隐式调用。最后一个则是作为构造方法调用。

作为构造函数调用:

JavaScript 支持面向对象式编程,与主流的面向对象式编程语言不同,JavaScript 并没有类(class)的概念,而是使用基于原型(prototype)的继承方式。相应的,JavaScript 中的构造函数也很特殊,如果不使用 new 调用,则和普通函数一样。作为又一项约定俗成的准则,构造函数以大写字母开头,提醒调用者使用正确的方式调用。如果调用正确,this 绑定到新创建的对象上。

通过new实例化一个函数:

  1. function $(){
  2. console.log(this);
  3. }
  4. var fn = new $();

控制台输出如下:

this就指这个新对象。

使用apply或call调用:

使用apply方法((当然使用Function.call也是可以的)),另一个方法 call 也具备同样功能,不同的是最后的参数不是作为一个数组统一传入,而是分开传入的。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。

  1. function $(){
  2. console.log(this);
  3. }
  4. var o = {};
  5. o.m = $;
  6. o.m.apply();  

控制台打印结果:

注:Function.apply(obj,args)方法能接收两个参数

obj:这个对象将代替Function类里this对象

args:这个是数组,它将作为参数传给Function(args-->arguments)

apply()的参数为空时,默认调用全局对象。

如果将call的第一个destination的值设为一个对象,如下:

  1. function $(){
  2. console.log(this);
  3. }
  4. var u = {};
  5. var o = {};
  6. o.m = $;
  7. o.m.apply(u,null);

控制台打印结果如下:

也即this指向了apply绑定的那个对象。

作为构造函数及apply/call的混用:

  1. //下面这段代码模拟了new操作符(实例化对象)的内部过程
  2. function person(name){
  3. var o={};
  4. o.__proto__=Person.prototype; //原型继承
  5. Person.call(o,name);
  6. return o;
  7. }
  8. var personB=person("xl");
  9. console.log(personB.name); // 输出 xl

person里面首先创建一个空对象o,将o的proto指向Person.prototype完成对原型的属性和方法的继承。Person.call(o,name)这里即函数Person作为apply/call调用(具体内容下方),将Person对象里的this改为o,即完成了o.name=name操作。返回对象o。

作为回调函数的this:

我们来看看 this 在 JavaScript 中经常被误用的一种情况:回调函数。JavaScript 支持函数式编程,函数属于一级对象,可以作为参数被传递。请看下面的例子 myObject.handler 作为回调函数,会在 onclick 事件被触发时调用,但此时,该函数已经在另外一个执行环境(ExecutionContext)中执行了,this 自然也不会绑定到 myObject 对象上。

  1. button.onclick = obj.handler;

代码如下:

  1. <p id="p">click me</p>
  2. <script type="text/javascript">
  3. var obj = {
  4. handler: function() {
  5. console.log(this);
  6. }
  7. }
  8. var p = document.getElementById("p");
  9. p.onclick = obj.handler;

执行结果如下:

很显然指向是触发click事件的dom元素对象,而非obj对象。这是 JavaScript 新手们经常犯的一个错误,为了避免这种错误,许多 JavaScript 框架都提供了手动绑定 this 的方法。比如 Dojo 就提供了 lang.hitch,该方法接受一个对象和函数作为参数,返回一个新函数,执行时 this 绑定到传入的对象上。使用 Dojo,可以将上面的例子改为:button.onclick = lang.hitch(myObject, myObject.handler);在新版的 JavaScript 中,已经提供了内置的 bind 方法供大家使用。

eval方法中this的指向:

JavaScript 中的 eval 方法可以将字符串转换为 JavaScript 代码,使用 eval 方法时,this 指向哪里呢?答案很简单,看谁在调用 eval 方法,调用者的执行环境(ExecutionContext)中的 this 就被 eval 方法继承下来了。

var name="XL";
var person={
     name:"xl",
     showName:function(){
     eval("console.log(this.name)");
  }
}

person.showName(); //输出 "xl"

var a=person.showName;

a(); //输出 "XL"

第一次的执行环境是person对象,第二个是global全局环境。

Function.prototype.bind()方法:

  1. 1.var name = "XL";

           function Person(name) {
              this.name = name;
               console.log(this);
              this.sayName = function() {
                  console.log(this);
                  setTimeout(function(){
                  console.log(this);
                  console.log("my name is " + this.name);
              },50)
           }
       }
var person = new Person("xl");
person.sayName();

  1. 2.var name="XL";
  2. function Person(name){
  3. this.name=name;
  4. this.sayName=function(){
  5. setTimeout(function(){
  6. console.log("my name is "+this.name);
  7. }.bind(this),50) //注意这个地方使用的bind()方法,绑定setTimeout里面的匿名函数的this一直指向Person对象
  8. }
  9. }
  10. var person=new Person("xl");
  11. person.sayName(); //输出 “my name is xl”;

这里setTimeout(function(){console.log(this.name)}.bind(this),50);,匿名函数使用bind(this)方法后创建了新的函数,这个新的函数不管在什么地方执行,this都指向的调用它的对象,而非window。而如果不加bind在第一段代码中,window执行环境中创建了一个变量person,被赋值了Person的实例,此时的调用顺序是person调用了sayName这个方法,这个方法被赋值了一个函数(此处有问题,该赋值函数是匿名还是非匿名?下篇讨论),创建了一个执行环境,此时setTimeOut函数开始执行,创建一个环境。匿名函数及setTimeout/setInterval在非手动改变指向额情况下都在全局作用域当中。

SO,第一段代码的打印结果是:

sf的解释:setTimeout/setInterval/匿名函数执行的时候,this默认指向window对象,除非手动改变this的指向。在《javascript高级程序设计》当中,写到:“超时调用的代码(setTimeout)都是在全局作用域中执行的,因此函数中的this的值,在非严格模式下是指向window对象,在严格模式下是指向undefined”。本文都是在非严格模式下的情况。

第二段代码的打印结果是:

这相当于手动将this进行了强制转向。

另一种手动转向的方法:

  1. var name="XL";
  2. function Person(){
  3. this.name="xl";
  4. var that=this;
  5. this.showName=function(){
  6. console.log(that.name);
  7. }
  8. setTimeout(this.showName,50)
  9. }
  10. var person=new Person(); //输出 "xl"

借用了上面提到的that保存this指针值进行复用的技巧。

匿名函数中的this:

  1. var name="XL";
  2. var person={
  3. name:"xl",
  4. showName:function(){
  5. console.log(this.name);
  6. }
  7. sayName:function(){
  8. (function(callback){
  9. callback();
  10. })(this.showName)
  11. }
  12. }
  13. person.sayName(); //输出 XL
  14. var name="XL";
  15. var person={
  16. name:"xl",
  17. showName:function(){
  18. console.log(this.name);
  19. }
  20. sayName:function(){
  21. var that=this;
  22. (function(callback){
  23. callback();
  24. })(that.showName)
  25. }
  26. }
  27. person.sayName() ; //输出 "xl"

此处采用了that技巧保存this指针。

箭头函数(点击这里):

箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。示例代码:

var obj = {
     birth: 1990,
     getAge: function (year) {
     var b = this.birth; // 1990
     var fn = (y) => y - this.birth; // this.birth仍是1990
     return fn.call({birth:2000}, year);
}
};
console.log(obj.getAge(2015)); // 25

控制台打印结果:

this的四种使用场景

面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式,带来灵活性也带来困惑。js“超级”迟绑定( very late binding)使得函数可以对this高度复用。通过this可取得它们所属对象的上下文的方法称为公共方法。使用this关键字在面向对象语言中多数情况下是为了避免命名冲突。总的来说,JavaScript 中函数的调用有以上几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。

函数的执行环境

JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环境(ExecutionContext),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(ExecutionContext)创建成功后,函数执行时才会执行,这点对于我们理解 JavaScript 中的变量作用域非常重要,鉴于篇幅,我们先不在这里讨论这个话题。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(ExecutionContext)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(ExecutionContext)中读取。

本文是综合了互联网多篇文章结论之后亲测的结论,目的也在于细化自己的知识体系,并进而更深刻的理解js的内核实现。经过此番梳理,对this的灵活指向已经有了一个比较清晰的认识,不过还需在实际的工程中不断运用以达到完全掌握。不过经过此一番,没必要死记硬背以上多种场景,若需使用的时候,在对印位置打印出来看看不就知道了?

js中this关键字测试集锦的更多相关文章

  1. JS中的关键字和保留字

    JavaScript中不能作为变量名的关键字和保留字总结: 1.js中的关键字: break case catch continue default delete do else finally fo ...

  2. 关于js中this关键字的补充

    前面: 前面虽然综合了网络上不少大牛的心得,但感觉还是意犹未尽,为了彻底搞清楚js中this的相关知识,决定再写一篇.个人觉得,在技术上,除非钻到细枝末节,否则很难达至非常高的水平. 补充1: 无法重 ...

  3. JS中this关键字详解

    本文主要解释在JS里面this关键字的指向问题(在浏览器环境下). 阅读此文章,还需要心平气和的阅读完,相信一定会有所收获,我也会不定期的发布,分享一些文章,共同学习 首先,必须搞清楚在JS里面,函数 ...

  4. JS 中 this 关键字详解

    本文主要解释在JS里面this关键字的指向问题(在浏览器环境下). 首先,必须搞清楚在JS里面,函数的几种调用方式: 普通函数调用 作为方法来调用 作为构造函数来调用 使用apply/call方法来调 ...

  5. 转:js中this关键字详解

    this指向哪里? 一般而言,在Javascript中,this指向函数执行时的当前对象. In JavaScript, as in most object-oriented programming ...

  6. js中的关键字与保留字

    关键字就是指:js中用到的单词,比如var : function: 保留字是指:js以后可能会发展成为关键字的,先保留起来不让你用.比如class,要是给一个对象添加class:obj.classNa ...

  7. js中this关键字的作用

    this中的几种情况 1.普通函数中的this window 2.构造函数中的this 是当前构造函数创建的对象在new这个构造函数的时候会在内存中创建一个对象,此时会让this指向刚创建好的这个对象 ...

  8. js中this关键字用法详解

    1.全局环境中的this 在全局环境中,this 指向全局对象Global,即 window 对象 如: alert(this); // 显示 [object Window] alert(this = ...

  9. js中this关键字的使用

    <script> //题目一:理解r1与r2的输出 function addFactory(){ var adder = 5; return function(data){ adder + ...

随机推荐

  1. python 添加tab补全

    在平时查看Python方法用到tab补全还是很方便的. 1. mac 平台 配置如下: mac是类Unix平台,需要在添加一条配置内容到bash_profile 中(默认是没有这个文件,可以新建一个放 ...

  2. SQL Server游标(转)

    清晰地介绍了SQL游标,很好的学习资料. 转自 http://www.cnblogs.com/knowledgesea/p/3699851.html 什么是游标 结果集,结果集就是select查询之后 ...

  3. angularjs中的filter(过滤器)——格式化日期的date

    date过滤器的功能是基于要求的格式格式化一个日期成为一个字符串. 格式化字符串的基本参数: 'yyyy': 用4位数字表示年(例如:AD 1 => 0001, AD 2010 => 20 ...

  4. Javascript高性能编程-提高javascript加载速度

        1.将所有<script>标签放在尽可能接近<body>标签底部的位置,以保证页面在脚本运行之前完成解析尽量减少对整个页面下载的影响     2.限制页面的<sc ...

  5. Web报表工具FineReport的JS开发之字符串

    在报表开发过程中,有些需求可能无法通过现有的功能来实现,需要开发人员二次开发,以FineReport为例,可以使用网页脚本.API接口等进行深入的开发与控制. 考虑到JS脚本开发的使用较多,这里先先简 ...

  6. H3 BPM初次安装常见错误详解5-7

    错误5:登陆无反应,F12查看后台网络请求错误如下图所示  错误原因:ISAPI未对相应的.net版本允许. 解决方法:IIS的根节点--右侧"ISAPI和CGI限制"打开--将相 ...

  7. 【原】HTTP in iOS你看我就够

    声明:本文是本人 编程小翁 原创,转载请注明. 本文同步发布在简书中,强烈建议移步简书查看,编程小翁 HTTP属于老话题了,在项目中我们经常需要往服务端发POST或者GET请求,但是对于HTTP的了解 ...

  8. 解决adobe air sdk打包 apk后自动在包名前面加上air. (有个点)前缀的问题

    早就找到了这个方法,但是一直忙没心思写博客. 默认情况下,所有 AIR Android 应用程序的包名称都带 air 前缀.若不想使用此默认行为,可将计算机环境变量 AIR_NOANDROIDFLAI ...

  9. Highchart基础教程-图表配置

    一.图表容器: Highcharts 实例化中绑定容器的两种方式: 1.通过 dom 调用 highcharts() 函数的方式 $("#container").highchart ...

  10. ThinkPHP实现定时任务

    项目服务端框架我选用的是ThinkPHP,由于策划案中有需求要定时刷新指定数据,所以在windows平台我使用微软的计划任务调用bat脚本来执行下面的命令来完成 php index.php /Home ...