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 相关文章推荐
js读取注册表的键值示例
Sep 25 Javascript
3分钟写出来的Jquery版checkbox全选反选功能
Oct 23 Javascript
jquery清空表单数据示例分享
Feb 13 Javascript
js判断一个字符串是否包含一个子串的方法
Jan 26 Javascript
深入理解JavaScript系列(18):面向对象编程之ECMAScript实现
Mar 05 Javascript
基于jQuery实现歌词滚动版音乐播放器的代码
Sep 17 Javascript
JavaScript实现窗口抖动效果
Oct 19 Javascript
JavaScript requestAnimationFrame动画详解
Sep 14 Javascript
js实现轮播图的完整代码
Oct 26 Javascript
实例详解ztree在vue项目中使用并且带有搜索功能
Aug 24 Javascript
layui 解决富文本框form表单提交为空的问题
Oct 26 Javascript
Element Dialog对话框的使用示例
Jul 26 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中记录用户访问过的产品,在cookie记录产品id,id取得产品信息
2011/05/04 PHP
深入解析PHP中SESSION反序列化机制
2017/03/01 PHP
laravel 时间格式转时间戳的例子
2019/10/11 PHP
JS控件的生命周期介绍
2012/10/22 Javascript
jquery事件与函数的使用介绍
2013/09/29 Javascript
JS滚轮事件onmousewheel使用介绍
2013/11/01 Javascript
JavaScript判断对象是否为数组
2015/12/22 Javascript
JavaScript字符串常用的方法
2016/03/10 Javascript
jQuery Validate表单验证插件的基本使用方法及功能拓展
2017/01/04 Javascript
vue父子组件的嵌套的示例代码
2017/09/08 Javascript
初学者AngularJS的环境搭建过程
2017/10/27 Javascript
ajax请求data遇到的问题分析
2018/01/18 Javascript
vue和react等项目中更简单的实现展开收起更多等效果示例
2018/02/22 Javascript
vue.js中实现登录控制的方法示例
2018/04/23 Javascript
jquery层次选择器的介绍
2019/01/18 jQuery
微信小程序通过js实现瀑布流布局详解
2019/08/28 Javascript
layui中select,radio设置不生效的解决方法
2019/09/05 Javascript
Vue.set 全局操作简单示例
2019/09/19 Javascript
微信小程序日历插件代码实例
2019/12/04 Javascript
Vue中this.$nextTick的作用及用法
2020/02/04 Javascript
JS实现简单移动端鼠标拖拽
2020/07/23 Javascript
Vue 组件注册全解析
2020/12/17 Vue.js
使用python编写批量卸载手机中安装的android应用脚本
2014/07/21 Python
python kmeans聚类简单介绍和实现代码
2018/02/23 Python
[原创]Python入门教程4. 元组基本操作
2018/10/31 Python
Python可视化mhd格式和raw格式的医学图像并保存的方法
2019/01/24 Python
韩国乐天网上商城:Lotte iMall
2021/02/03 全球购物
员工拾金不昧表扬信
2014/01/09 职场文书
幼儿园教师自我鉴定
2014/03/20 职场文书
五水共治一句话承诺
2014/05/30 职场文书
2014年教师政治学习材料
2014/06/02 职场文书
交警失职检讨书
2015/01/26 职场文书
招商银行工作证明
2015/06/17 职场文书
初中班级口号霸气押韵
2015/12/24 职场文书
全家福照片寄语怎么写?
2019/04/02 职场文书
演讲稿之我的初心我的成长
2019/08/12 职场文书