不再彷徨:完全弄懂JavaScript中的this(译文总结)

比特筑梦家说
• 阅读 14931

其实this是一个老生常谈的问题了。关于this的文章非常多,其实我本以为自己早弄明白了它,不过昨天在做项目的过程中,还是出现了一丝疑惑,想到大概之前在JavaScript weekly里收藏待看的一篇详解this的文章(后有链接,也附上了稀土上的中文译文)和另一篇一位前辈推荐的文章,就把它们看了看,对this的认识确实提升了一些。

JavaScript 中的’this‘是动态的,它在函数运行时被确定而非在函数声明时被确定。所有的函数都可以调用'this',这无关于该函数是否属于某个对象。关于this,主要有以下四种情况。

1.被当做对象的方法被调用

如果该函数是被当做某一个对象的方法,那么该函数的this指向该对象;

    var john = {
      firstName: "John"
    }
    function func() {
      alert(this.firstName + ": hi!")
    }
    john.sayHi = func
    john.sayHi()  // this = john

这里有一点值得注意,当一个对象的方法被取出来赋值给一个变量时,该方法变为函数触发,this指向window或underfind(严格模式)。

2.函数之内调用

当函数中有 this,其实就意味着它被当做方法调用,之间调用相当于把他当做window对象的方法,this指向window,值得注意的是ES5其实是规定这种情况this=undefined的,只浏览器大多还是按照老的方法执行(本人在最新版的Chrome,Safari,Firefox中测试都指向window(201607)),在火狐下使用严格模式指向undefined;

    func()
    function func() {
        alert(this) // [object Window] or [object global] or kind of..
    }

为了传递this,()之前应该为引用类型,类似于obj.a 或者 obj['a'],不能是别的了。

这里还存在一个小坑,当对象的方法中还存在函数时,该函数其实是当做函数模式触发,所以其this默认为window(严格模式下为undefined)解决办法是给该函数绑定this。

var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       // this is window or undefined in strict mode
       console.log(this === numbers); // => false
       return this.numberA + this.numberB;
     }
     return calculate();
   }
};
numbers.sum(); // => NaN or throws TypeError in strict mode  
var numbers = {  
   numberA: 5,
   numberB: 10,
   sum: function() {
     console.log(this === numbers); // => true
     function calculate() {
       console.log(this === numbers); // => true
       return this.numberA + this.numberB;
     }
     // use .call() method to modify the context
     return calculate.call(this);
   }
};
numbers.sum(); // => 15  

3.在new中调用

一个引用对象的变量实际上保存了对该对象的引用,也就是说变量实际保存的是对真实数据的一个指针。
使用new关键字时this的改变其实有以下几步:

  • 创建 this = {}.

  • new执行的过程中可能改变this,然后添加属性和方法;

  • 返回被改变的this.

    function Animal(name) {
        this.name = name
        this.canWalk = true
    }
    var animal = new Animal("beastie")
    alert(animal.name)

需要注意的是如果构造函数返回一个对象,那么this指向返回的那个对象;

    function Animal() {
        this.name = 'Mousie';
        this.age = '18';
        return {
            name: 'Godzilla'
        } // <-- will be returned
    }

    var animal = new Animal()
    console.log(animal.name) // Godzilla
    console.log(animal.age)//undefined

这里需要注意的是不要忘记使用new,否则不会创建一个新的函数。而是只是执行了函数,相当于函数调用,this其实指向window

function Vehicle(type, wheelsCount) {  
  this.type = type;
  this.wheelsCount = wheelsCount;
  return this;
}
// Function invocation
var car = Vehicle('Car', 4);  
car.type;       // => 'Car'  
car.wheelsCount // => 4  
car === window  // => true 

4.明确调用this,使用callapply

这是最具JavaScript特色的地方。
如下代码:

  func.call(obj, arg1, arg2,...)

第一个参数将作为this的指代对象,之后的参数将被作为函数的参数,解决方法是使用bind。

function Animal(type, legs) {  
  this.type = type;
  this.legs = legs;  
  this.logInfo = function() {
    console.log(this === myCat); // => true
    console.log('The ' + this.type + ' has ' + this.legs + ' legs');
  };
}
var myCat = new Animal('Cat', 4);  
// logs "The Cat has 4 legs"
setTimeout(myCat.logInfo.bind(myCat), 1000); 
// setTimeout??
  var john = {
    firstName: "John",
    surname: "Smith"
  }
  function func(a, b) {
    alert( this[a] + ' ' + this[b] )
  }
  func.call(john, 'firstName', 'surname')  // "John Smith"

至于apply,其只是以数组的方传入参数,其它部分是一样的,如下:

  func.call(john, 'firstName', 'surname')
  func.apply(john, ['firstName', 'surname'])

它们也可用于在 ES5 中的类继承中,调用父级构造器。

    function Runner(name) {  
      console.log(this instanceof Rabbit); // => true
      this.name = name;  
    }
    function Rabbit(name, countLegs) {  
      console.log(this instanceof Rabbit); // => true
      // 间接调用,调用了父级构造器
      Runner.call(this, name);
      this.countLegs = countLegs;
    }
    var myRabbit = new Rabbit('White Rabbit', 4);  
    myRabbit; // { name: 'White Rabbit', countLegs: 4 }

5..bind()

对比方法 .apply() 和 .call(),它俩都立即执行了函数,而 .bind() 函数返回了一个新方法,绑定了预先指定好的 this ,并可以延后调用。
.bind() 方法的作用是创建一个新的函数,执行时的上下文环境为 .bind() 传递的第一个参数,它允许创建预先设置好 this 的函数。

var numbers = {  
  array: [3, 5, 10],
  getNumbers: function() {
    return this.array;    
  }
};
// Create a bound function
var boundGetNumbers = numbers.getNumbers.bind(numbers);  
boundGetNumbers(); // => [3, 5, 10]  
// Extract method from object
var simpleGetNumbers = numbers.getNumbers;  
simpleGetNumbers(); // => undefined or throws an error in strict mode  

使用.bind()时应该注意,.bind() 创建了一个永恒的上下文链并不可修改。一个绑定函数即使使用 .call() 或者 .apply()传入其他不同的上下文环境,也不会更改它之前连接的上下文环境,重新绑定也不会起任何作用。
只有在构造器调用时,绑定函数可以改变上下文,然而这并不是特别推荐的做法。

6.箭头函数

箭头函数并不创建它自身执行的上下文,使得 this 取决于它在定义时的外部函数。
箭头函数一次绑定上下文后便不可更改,即使使用了上下文更改的方法:

    var numbers = [1, 2];  
    (function() {  
      var get = () => {
        console.log(this === numbers); // => true
        return this;
      };
      console.log(this === numbers); // => true
      get(); // => [1, 2]
      // 箭头函数使用 .apply() 和 .call()
      get.call([0]);  // => [1, 2]
      get.apply([0]); // => [1, 2]
      // Bind
      get.bind([0])(); // => [1, 2]
    }).call(numbers);

这是因为箭头函数拥有静态的上下文环境,不会因为不同的调用而改变。因此不要使用箭头函数定义方法

function Period (hours, minutes) {  
      this.hours = hours;
      this.minutes = minutes;
    }
    Period.prototype.format = () => {  
      console.log(this === window); // => true
      return this.hours + ' hours and ' + this.minutes + ' minutes';
    };
    var walkPeriod = new Period(2, 30);  
    walkPeriod.format(); // => 'undefined hours and undefined minutes' 

参考

强烈推荐觉得没弄明白的同学看看上面三篇文章,其中第三篇是第二篇的译文。如果大家对this还有疑问,也欢迎大家一起讨论,交流促进思考,共同进步。

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Karen110 Karen110
4年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
东方客主 东方客主
4年前
一篇文章弄明白Javascript
在一片混沌中,一个叫Function的函数开天辟地,自己创建了自己。于是,一个叫JavaScript的星球诞生了。图一:创建了自己的Function新诞生
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
艾木酱 艾木酱
4年前
我们与MemFire Cloud的缘起2--MemFireDB
中大概阐述了一下我们之前碰到过的一些应用开发中的痛点:现有的云数据库起步使用成本较高应用开发对团队的起步要求较高现有云数据库配置复杂这些痛点当然不是我们先发现的,也不是我们先要尝试解决的。Google早在2014年就收购了firebase,或许是对这些痛点认可的最佳佐证。就在前两天,InfoQ上发布了一篇翻译文章:,这其实是作者的副标题,原标题是,文章
Wesley13 Wesley13
4年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
4年前
30 天学习 30 种新技术系列
编者注:我们发现了比较有趣的系列文章《30天学习30种新技术》,准备翻译,一天一篇更新,年终礼包。以下是译文,英文标题表示还未翻译,附原文链接;中文标题表示已翻译,附译文链接。更新:全系列已经全部翻译完成。让你30天学习30种新技术,你会觉得这是挑战吗?!img1(http://segmentfault.com/img/b
Python进阶者 Python进阶者
2年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这