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 相关文章推荐
jQuery入门第一课 jQuery选择符
Mar 14 Javascript
jquery获取tagName再进行判断
May 29 Javascript
jQuery获取iframe的document对象的方法
Oct 10 Javascript
bootstrap table 服务器端分页例子分享
Feb 10 Javascript
基于jQuery实现的仿百度首页滑动选项卡效果代码
Nov 16 Javascript
jQuery实现百叶窗焦点图动画效果代码分享(附源码下载)
Mar 14 Javascript
javascript实现消灭星星小游戏简单版
Nov 15 Javascript
Json按某个键的值进行排序
Dec 22 Javascript
JS实现的将html转为pdf功能【基于浏览器端插件jsPDF】
Feb 06 Javascript
jQuery实现获取及设置CSS样式操作详解
Sep 05 jQuery
vuex提交state&amp;&amp;实时监听state数据的改变方法
Sep 16 Javascript
js 闭包深入理解与实例分析
Mar 19 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动态添加url查询参数的方法
2015/04/14 PHP
php获得客户端浏览器名称及版本的方法(基于ECShop函数)
2015/12/23 PHP
JavaScript在IE中“意外地调用了方法或属性访问”
2008/11/19 Javascript
jquery 插件学习(五)
2012/08/06 Javascript
JavaScript 七大技巧(一)
2015/12/13 Javascript
javascript从定义到执行 你不知道的那些事
2016/01/04 Javascript
jQuery数据类型小结(14个)
2016/01/08 Javascript
Angularjs中$http以post请求通过消息体传递参数的实现方法
2016/08/05 Javascript
用js读写cookie的简单方法(推荐)
2016/08/08 Javascript
浅谈JS中的三种字符串连接方式及其性能比较
2016/09/02 Javascript
AngularJS API之copy深拷贝详解及实例
2016/09/14 Javascript
javascript 单例模式详解及简单实例
2017/02/14 Javascript
JavaScript实现按键精灵的原理分析
2017/02/21 Javascript
Zepto实现密码的隐藏/显示
2017/04/07 Javascript
NodeJs操作MongoDB教程之分页功能以及常见问题
2019/04/09 NodeJs
element-ui 实现响应式导航栏的示例代码
2020/05/08 Javascript
微信小程序仿抖音短视频切换效果的实例代码
2020/06/24 Javascript
Python2.5/2.6实用教程 入门基础篇
2009/11/29 Python
对于Python装饰器使用的一些建议
2015/06/03 Python
深入讲解Python编程中的字符串
2015/10/14 Python
Python实现列表删除重复元素的三种常用方法分析
2017/11/24 Python
python基础之包的导入和__init__.py的介绍
2018/01/08 Python
浅谈numpy生成数组的零值问题
2018/11/12 Python
如何爬取通过ajax加载数据的网站
2019/08/15 Python
python使用梯度下降和牛顿法寻找Rosenbrock函数最小值实例
2020/04/02 Python
Python捕获异常堆栈信息的几种方法(小结)
2020/05/18 Python
Python2手动安装更新pip过程实例解析
2020/07/16 Python
Python文件名匹配与文件复制的实现
2020/12/11 Python
网络工程师职业规划
2014/02/10 职场文书
2014年幼儿园元旦活动方案
2014/02/13 职场文书
亲子活动总结
2014/04/26 职场文书
义卖募捐活动总结
2015/05/09 职场文书
运动会宣传稿50字
2015/07/23 职场文书
春节随笔
2015/08/15 职场文书
学者《孟子》名人名言
2019/08/09 职场文书
Python利器openpyxl之操作excel表格
2021/04/17 Python