JavaScript中的this使用详解


Posted in Javascript onJuly 27, 2016

其实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,使用call和apply

这是最具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'

参考

Four scents of "this"

Gentle explanation of 'this' keyword in JavaScript

JavaScript This 之谜(译文)

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

Javascript 相关文章推荐
阻止JavaScript事件冒泡传递(cancelBubble 、stopPropagation)
May 08 Javascript
jquery中通过过滤器获取表单元素的实现代码
Jul 05 Javascript
js或jquery实现页面打印可局部打印
Mar 27 Javascript
JavaScript利用正则表达式去除日期中的“-”
Jul 01 Javascript
jQuery控制网页打印指定区域的方法
Apr 07 Javascript
js组件SlotMachine实现图片切换效果制作抽奖系统
Apr 17 Javascript
Jquery给当前页或者跳转后页面的导航栏添加选中后样式的实例
Dec 08 Javascript
Angular4学习笔记之准备和环境搭建项目
Aug 01 Javascript
js异步编程小技巧详解
Aug 14 Javascript
Jquery和CSS实现选择框重置按钮功能
Nov 08 jQuery
layui实现根据table数据判断按钮显示情况的方法
Sep 26 Javascript
vue移动端的左右滑动事件详解
Jun 17 Javascript
js删除数组元素、清空数组的简单方法(必看)
Jul 27 #Javascript
javascript简单实现等比例缩小图片的方法
Jul 27 #Javascript
第一次接触神奇的Bootstrap网格系统
Jul 27 #Javascript
Js删除数组中某一项或几项的几种方法(推荐)
Jul 27 #Javascript
javascript获取网页各种高宽及位置的方法总结
Jul 27 #Javascript
第一次接触神奇的Bootstrap表单
Jul 27 #Javascript
AngularJS 表达式详细讲解及实例代码
Jul 26 #Javascript
You might like
十天学会php之第五天
2006/10/09 PHP
php auth_http类库进行身份效验
2009/03/19 PHP
详解PHP执行定时任务的实现思路
2015/12/21 PHP
Yii2 RESTful中api的使用及开发实例详解
2016/07/06 PHP
PHP针对字符串开头和结尾的判断方法
2016/07/11 PHP
用JavaScript显示随机图像或引用
2009/04/21 Javascript
js DOM的学习笔记
2011/12/22 Javascript
JS验证图片格式和大小并预览的简单实例
2016/10/11 Javascript
微信小程序 基础组件与导航组件详细介绍
2017/02/21 Javascript
layui表格实现代码
2017/05/20 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
详解Vue+elementUI build打包部署后字体图标丢失问题
2020/07/13 Javascript
[40:48]DOTA2上海特级锦标赛D组败者赛 Liquid VS COL第二局
2016/02/28 DOTA
详解Python中用于计算指数的exp()方法
2015/05/14 Python
python2 与python3的print区别小结
2018/01/16 Python
Python tkinter事件高级用法实例
2018/01/31 Python
python中多个装饰器的执行顺序详解
2018/10/08 Python
python爬虫之urllib库常用方法用法总结大全
2018/11/14 Python
Python3将数据保存为txt文件的方法
2019/09/12 Python
pycharm解决关闭flask后依旧可以访问服务的问题
2020/04/03 Python
Softmax函数原理及Python实现过程解析
2020/05/22 Python
Python如何自动获取目标网站最新通知
2020/06/18 Python
PyTorch安装与基本使用详解
2020/08/31 Python
德国骆驼商店:ActiveFashionWorld
2017/11/18 全球购物
Solaris操作系统的线程机制
2015/07/28 面试题
银行贷款承诺书
2014/03/29 职场文书
党支部书记四风问题整改措施
2014/09/24 职场文书
房地产公司财务总监岗位职责
2015/04/03 职场文书
2015年城管个人工作总结范文
2015/04/20 职场文书
比赛主持人开场白
2015/05/29 职场文书
聘用合同范本
2015/09/21 职场文书
2016年10月份红领巾广播稿
2015/12/21 职场文书
2016年社区服务活动总结
2016/04/06 职场文书
导游词之昭君岛
2020/01/17 职场文书
Java中常用解析工具jackson及fastjson的使用
2021/06/28 Java/Android
Python echarts实现数据可视化实例详解
2022/03/03 Python