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 相关文章推荐
你需要知道的10个最佳javascript开发实践小结
Apr 15 Javascript
Jquery 模拟用户点击超链接或者按钮的方法
Oct 25 Javascript
javascript中的五种基本数据类型
Aug 26 Javascript
推荐10 个很棒的 jQuery 特效代码
Oct 04 Javascript
js中小数向上取整数,向下取整数,四舍五入取整数的实现(必看篇)
Feb 13 Javascript
浅谈gulp创建完整的项目流程
Dec 20 Javascript
javascript实现文本框标签验证的实例代码
Oct 14 Javascript
如何使用50行javaScript代码实现简单版的call,apply,bind
Aug 14 Javascript
详解小程序云开发攻略(解决最棘手的问题)
Sep 30 Javascript
JS面向对象之多选框实现
Jan 17 Javascript
如何在CocosCreator里画个炫酷的雷达图
Apr 16 Javascript
JS 4个超级实用的小技巧 提升开发效率
Oct 05 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之数组(遍历顺序)  Laruence原创
2012/06/13 PHP
PHP合并discuz用户脚本的方法
2015/08/04 PHP
JQuery实现自定义对话框的代码
2008/06/15 Javascript
Jquery Ajax学习实例5 向WebService发出请求,返回泛型集合数据的异步调用
2010/03/17 Javascript
Visual Studio中的jQuery智能提示设置方法
2010/03/27 Javascript
从零开始学习jQuery (十) jQueryUI常用功能实战
2011/02/23 Javascript
jquery处理页面弹出层查询数据等待操作实例
2015/03/25 Javascript
js窗口关闭提示信息(兼容IE和firefox)
2015/10/23 Javascript
JS控制按钮10秒钟后可用的方法
2015/12/22 Javascript
javascript瀑布流布局实现方法详解
2016/02/17 Javascript
轻松掌握JavaScript享元模式
2016/08/27 Javascript
Query常用DIV操作获取和设置长度宽度的实现方法
2016/09/19 Javascript
js实现复选框的全选和取消全选效果
2017/01/03 Javascript
原生js开发的日历插件
2017/02/04 Javascript
js for终止循环 跳出多层循环
2018/10/04 Javascript
详解vue-cli3 中跨域解决方案
2019/04/10 Javascript
vue elementUI 表单校验功能之数组多层嵌套
2019/06/04 Javascript
在vue中使用回调函数,this调用无效的解决
2020/08/11 Javascript
[40:05]LGD vs Winstrike 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/18 DOTA
Python Web框架Tornado运行和部署
2020/10/19 Python
Python配置虚拟环境图文步骤
2019/05/20 Python
安装python及pycharm的教程图解
2019/10/10 Python
OpenCV+Python--RGB转HSI的实现
2019/11/27 Python
浅谈python 调用open()打开文件时路径出错的原因
2020/06/05 Python
Python发送邮件实现基础解析
2020/08/14 Python
Python 打印自己设计的字体的实例讲解
2021/01/04 Python
介绍一下write命令
2012/09/24 面试题
简历中自我评价分享
2013/10/09 职场文书
如何写一份好的英文求职信
2014/03/19 职场文书
幼儿教师求职信
2014/05/24 职场文书
五一促销活动总结
2014/07/01 职场文书
励志演讲稿800字
2014/08/21 职场文书
先进个人申报材料
2014/12/30 职场文书
芙蓉镇观后感
2015/06/10 职场文书
教师教育心得体会
2016/01/19 职场文书
如何使用pdb进行Python调试
2021/06/30 Python