转自: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 modecall的第一个参数不是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. VB.NET多线程入门

    近期项目中遇到了一个处理速度慢阻塞用户界面操作的问题,因此想用多线程来解决. 在处理数据的循环中,新建线程,在新建的线程中处理数据.多线程同一时候处理数据,以此来达到加速的目的,使用户界面操作变得流畅 ...

  2. Ubuntu 16.04 LTS 完善解决亮度调整

    环境: ubuntu16.04 lts acer aspire 4752G i5-2450M 内容来源:点击这里 ubuntu无法调整屏幕亮度,对笔记本来说很耗电,同时也很刺眼,因为它是默认以最大亮度 ...

  3. android Contacts/Acore进程常常被Kill,导致联系人开机后丢失怎么办?

    Contacts/Acore进程,在内存较少和开机进程过多的情况下会常常被 ActivityManager Kill 掉. 导致Sim卡联系人开机后未导入或者仅仅导入一部分,造成联系人丢失的现象,可是 ...

  4. ionic跳转(二)

    1)网上说要想在js里跳转用,$state.go()方法,但找了大半天都没找到在ionic使用$state的方法 2)要想用js跳转,直接用原生js跳转也是可以的 location.href='#ho ...

  5. ios8推送问题

    博文转载至  http://blog.csdn.net/cerastes/article/details/39546625 ios8push推送通知适配 ios8推送问题 registerForRem ...

  6. dubbo项目实战代码展示

    最近公司项目使用dubbo服务,于是就去网上搜索关于dubbo的相关资料,真的很多,但是对于很多人并不是很了解框架或者 不是太适合新手的片段代码,于是我就根据项目的相关内容把dubbo部分单独切出来, ...

  7. [Docker]——container和主机(host)之间的文件拷贝

    1. 从 container 到 主机(host) 使用 docker cp 命令 docker cp <containerId>:/file/path/within/container ...

  8. 使用jquery-qrcode生成二维码(转载)

    一.使用jquery-qrcode生成二维码 先简单说一下jquery-qrcode,这个开源的三方库(可以从https://github.com/jeromeetienne/jquery-qrcod ...

  9. 05.Curator分布式锁

        锁:分布式的锁全局同步,这意味着任何一个时间点不会有两个客户端都拥有相同的锁. 1.可重入锁Shared Reentrant Lock     首先我们先看一个全局可重入的锁(可以多次获取,不 ...

  10. 2017 Multi-University Training Contest - Team 1—HDU6044

    Limited Permutation 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6044 题意:现在有一个排列p1,p2,p3,p4,p5,p6… ...