转自:http://www.imooc.com/article/1758

以前看某本书上讲:

掌握了JS中this的用法才算真正的跨过了JS的门槛

我深以为是!但是JS的this却并不是那么简单的内容,许多同学都很困惑,也肯定有同学像我曾经一样查阅各种资料想知道How to understand this of javascript。很幸运的是,网上有非常非常多的文章关于this的,简直就是琳琅满目…曾经,我看到某些文章非常开心,因为他们讲得确实非常好,以至于我确信我已掌握了this的用法。然而可能是由于我太笨了,经常过一段时间就忘得一干二净了,这真的是一件很尴尬的事情……直到后来我仔细地琢磨又琢磨,终于感觉我可能以后不会再忘记了。所以想把我琢磨的内容和大家分享一下


说到this,就不得不提到function,相信看过其它类似文章的同学也知道,正是由于调用function的对象不同,才导致了this的指向不同。所以以前老是去记忆每种调用function的情况所对应的this,因为情况有限而且很少,所以这当然是可行的——对于聪明人来说。所以我不得不思考另外一些方式来让我记住。

那么首先我们需要明确的一个事情是:function也是对象
同时我们还需要明确的一个事情是:function执行时是在某个特定的上下文中执行的。那什么是上下文呢?打个比方,比如你练会了辟邪剑谱,那这时候你的掌门让你用辟邪剑谱砍人。如果仅仅是这样的话,你是没法完成这个任务的,因为你必须得知道要砍谁吧,其次去哪儿砍吧,那么是个地下通道还是一望无尽的大草原,要是地下通道你走路都困难,还怎么用辟邪剑谱呢对吧。这就是上下文,函数执行时它也需要一些额外的信息来支撑它的运行。那么既然function是对象的话,就会有方法。而function中最核心的方法是call方法。因此我们就从这儿入手。

this是Javascript语言的一个关键字。

它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。比如,

  function test(){

    this.x = 1;

  }

随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。

call方法

先来看一下如何使用call方法:

function say(content) {

  console.log("From " + this + ": Hello "+ content);
}
say.call("Bob", "World"); //==> From Bob: Hello World

接下来仔细分析一下call的用法:

  • Step1: 把第二个到最后一个参数作为函数执行时要传入的参数

  • Step2: 把函数执行时的this指向第一个参数

  • Step3: 在上面这个特殊的上下文中执行函数

上面例子中,我们通过call方法,让say函数执行时的this指向"Bob",然后把"World"作为参数传进去,所以输出结果是可以预见的。
js执行函数时会默认完成以上的步骤,你可以把直接调用函数理解为一种语法糖
比如

function say(word) {
 

   console.log(world);
}
say("Hello world");
say.call(window, "Hello world");

以上可以把say("Hello world")看做是say.call(window,"Hello world")的语法糖。
这个结论非常关键
所以以后每次看见functionName(xxx)的时候,你需要马上在脑海中把它替换为functionName.call(window,xxxx),这对你理解this的指向非常重要。不过也有例外,在ES5的strict mode中call的第一个参数不是window而是undefined。之后的例子我假设总是不在strictmode下,但你需要记住strictmode有一点儿不同。
对于匿名函数来说,上面的结论也是成立的

(function(name) {

  //

})("aa");
//等价于
(function(name) {    
 //
}).call(window, "aa");

函数作为对象的方法被调用

直接来看代码:

var person = {


   name : "caibirdme",


   run : function(time) {


       console.log(this.name + "has been running for over "+ time+ " minutes");


   }};
person.run(30);
//==> caibirdme has been running for over 30 minutes//等价于
person.run.call(person, 30); // the same

你会发现这里call的第一个参数是person而不是window。
当你明白了这两点,下意识地把函数调用翻译成foo.call()的形式,明确call的第一个参数,那基本上this的问题就难不住你了。


还是来举几个例子吧
例一:

function hello(thing) {

 
 console.log(this + " says hello " + thing);
}
 person = { name: "caibirdme" }  
 person.hello = hello;
 person.hello("world")
// 相当于执行 person.hello.call(person, "world")
hello("world")
// 相当于执行 hello.call(window, "world")

例二:

var obj = {


   x: 20,


   f: function(){

     console.log(this.x);

   }
};
obj.f(); // obj.f.call(obj)//==> 20
obj.innerobj = {


   x: 30,


   f: function(){

     console.log(this.x);

   }
}
obj.innerobj.f();
// obj.innerobj.f.call(obj.innerobj)// ==> 30

例三:

var x = 10;
var obj = {


   x: 20,


   f: function(){


       console.log(this.x); //this equals obj
               // ==> 20

       var foo = function(){

       console.log(this.x);

      }
       

      foo();
       // foo.call(window)
       //foo中this被指定为window,所以==> 10

   }
};
obj.f();  // obj.f.call(obj)// ==> 20 10

由例三引出一个非常common的问题,如果我想让foo输出20怎么办?这时候需要用到一点小技巧
例四:

var x = 10;
var obj = {


   x: 20,


   f: function(){


       console.log(this.x);


       var that = this;
     //使用that保留当前函数执行上下文的this


       var foo = function(){

         console.log(that.x);

       }
      //此时foo函数中的this仍然指向window,但我们使用that取得obj


       foo(); // foo.call(window)
   }
};
obj.f();
obj.f.call(obj)// ==> 20 20

再来一个稍微难一点点的(但其实用call替换法一点儿也不难)
例五:

var x = 10;
var obj = {


   x: 20,


   f: function(){

     console.log(this.x);

   }
};
obj.f(); // obj.f.call(obj)// ==> 20
var fOut = obj.f;
fOut();
// fOut.call(window)//==> 10
var obj2 = {


   x: 30,


   f: obj.f
}
obj2.f(); // obj2.f.call(obj2)//==> 30

例五有些同学会可能出错的原因,是没有明确我上面说的:
this是在执行是才会被确认的
他可能会认为说obj.f那个函数定义在obj里面,那this就该指向obj。如果看完这篇文章你还这么想的话,我会觉得我的表达水平太失败了……


用于构造函数

先看一段代码:

func person(name) {


   this.name = name;

}
   

var caibirdme = new person("deen");
   // caibirdme.name == deen

我上面也说了,函数在用作构造函数时同样可以用call方法去代替,那这里怎么代替呢?
这里你又需要明确一点:
new constrcut()是一种创建对象的语法糖
它等价于

   function person(name) {


      this.name = name;


   }


   var foo = new person("deen");


   //通过new创建了一个对象


   //new是一种语法糖,new person等价于


   var bar = (function(name) {


       var _newObj = {

           constructor : person,


           __proto__ : person.prototype,


       };


       _newObj.constructor(name);
// _newObj.constructor.call(_newObj, name)


       return _newObj;


   })();

So you can see……为什么new的时候this就指向新的对象了吧?


通过我这篇文章,我希望学会通过把一个函数调用替换成funcName.call的形式,从而理解运行时上下文中this到底指向谁。总结来说就是下面两个等价变形:

  • foo() ---> foo.call(window)

  • obj.foo() --> obj.foo.call(obj)

只要理解以上两个变形,this就不再是问题啦!!希望我的这种方法对各位同学认识this有所帮助,不要再像我曾经一样掉入this的坑中,相关面试题也不再怕怕啦,哈哈。

学会JS的this这一篇就够了的更多相关文章

  1. JS 装饰器,一篇就够

    更多文章,请在Github blog查看 在 ES6 中增加了对类对象的相关定义和操作(比如 class 和 extends ),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并 ...

  2. Node.js最新技术栈之Promise篇

    前言 大家好,我是桑世龙,github和cnodejs上的i5ting,目前在天津创业,公司目前使用技术主要是nodejs,算所谓的MEAN(mongodb + express + angular + ...

  3. JavaScript--我发现,原来你是这样的JS(基础概念--灵魂篇,一起来学js吧)

    介绍 这是红宝书(JavaScript高级程序设计 3版)的读书笔记第三篇(灵魂篇介绍),有着剩下的第三章的知识内容,当然其中还有我个人的理解.红宝书这本书可以说是难啃的,要看完不容易,挺厚的,要看懂 ...

  4. 学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳

    学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳 JERRY_Z. ~ 2020 / 9 / 25 转载请注明出处!️ 目录 学会Git玩转GitHub(第一篇) 入门详解 - 精简归纳 ...

  5. 学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳

    学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳 JERRY_Z. ~ 2020 / 10 / 25 转载请注明出处!️ 目录 学会Git玩转GitHub(第二篇) 入门详解 - 精简归纳 ...

  6. #3使用html+css+js制作网页 番外篇 使用python flask 框架 (II)

    #3使用html+css+js制作网页 番外篇 使用python flask 框架 II第二部 0. 本系列教程 1. 登录功能准备 a.python中操控mysql b. 安装数据库 c.安装mys ...

  7. #3使用html+css+js制作网页 番外篇 使用python flask 框架 (I)

    #3使用html+css+js制作网页 番外篇 使用python flask 框架(I 第一部) 0. 本系列教程 1. 准备 a.python b. flask c. flask 环境安装 d. f ...

  8. #3使用html+css+js制作网页 番外篇 制作接收php

    使用html+css+js制作网页 番外篇 制作接收php 本系列链接 基础 php语法 例子 本系列链接 #1使用html+css+js制作网站教程 准备 #2使用html+css+js制作网站教程 ...

  9. Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) JAVA日志的前世今生 .NET MVC采用SignalR更新在线用户数 C#多线程编程系列(五)- 使用任务并行库 C#多线程编程系列(三)- 线程同步 C#多线程编程系列(二)- 线程基础 C#多线程编程系列(一)- 简介

    Python GUI之tkinter窗口视窗教程大集合(看这篇就够了) 一.前言 由于本篇文章较长,所以下面给出内容目录方便跳转阅读,当然也可以用博客页面最右侧的文章目录导航栏进行跳转查阅. 一.前言 ...

随机推荐

  1. (转)c++ new/delete,new[]/delete[]原理解析

    转自: 推荐:http://www.cnblogs.com/hazir/p/new_and_delete.html http://blog.csdn.net/crazyboyzzx/article/d ...

  2. 杂记之--如何把项目托管到GitHub上面

    参考了文顶顶大神的方法,这里仅做记录用! https://pan.baidu.com/s/1gfCaCXd

  3. LA5059 Playing With Stones

    题意:nim游戏.加上限制每次不得取走超过当前堆一半的石子 1 ≤ N ≤ 100,1 ≤ ai ≤ 2 ∗ 1018 分析:由于ai过大.所以我们采用SG函数递推找规律. (详见代码) #inclu ...

  4. 【BZOJ2801】[Poi2012]Minimalist Security BFS

    [BZOJ2801][Poi2012]Minimalist Security Description 给出一个N个顶点.M条边的无向图,边(u,v)有权值w(u,v),顶点i也有权值p(i),并且对于 ...

  5. 很好用的canvas

    一.基本用法 1.要使用canvas元素,需要先给定其width和height来设置绘图区域的大小.canvas中间的文本会在浏览器不支持canvas的情况下显示出来. <canvas widt ...

  6. 170502、linux下配置jdk8

    1.下载源码包jdk-8u45-linux-x64.tar.gz(省略) 2.解压 tar -zxvf jdk-8u45-linux-x64.tar.gz 3.配置jdk vi /etc/profil ...

  7. 160519、Oracle中将查询出的多条记录的某个字段拼接成一个字符串的方法

    with temp as( select 'China' nation ,'Guangzhou' city from dual union all select 'China' nation ,'Sh ...

  8. HDFS 常用Shell命令

    HDFS Shell命令 概述 HDFS Shell命令允许使用命令行在HDFS存储中进行文件夹和文件操作. 如文件夹的增删改查.文件的增删改查等. 开始练习hadoop时,打开Linux之后要用 s ...

  9. JUnit常用断言及注解

    断言是编写测试用例的核心实现方式,即期望值是多少,测试的结果是多少,以此来判断测试是否通过. 断言核心方法   assertArrayEquals(expecteds, actuals) 查看两个数组 ...

  10. POJ 2374 Fence Obstacle Course(线段树+动态规划)

    Fence Obstacle Course Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 2524   Accepted:  ...