《JavaScript 深入浅出》系列

普通函数与箭头函数

普通函数指的是用function定义的函数:

var hello = function () {
console.log("Hello, Fundebug!");
}

箭头函数指的是用=>定义的函数:

var hello = () => {
console.log("Hello, Fundebug!");
}

JavaScript箭头函数与普通函数不只是写法上的区别,它们还有一些微妙的不同点,其中一个不同点就是this。

箭头函数没有自己的this值,箭头函数中所使用的this来自于函数作用域链。

这句话很简单,不过听着稍微有点莫名其妙,得从头说起。

this到底是什么?

关于this的文章也够多了,有时候越描越黑,我就不再添乱了,我只负责搬运一下MDN文档:this,感兴趣的可以仔细阅读一下,我摘录一些最重要的话就好了。

A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.

JavaScript是一门比较奇特的语言,它的this与其他语言不一样,并且它的取值还取决于代码是否为严格模式("use strict")。

this的值是什么?

The JavaScript context object in which the current code is executing.

this就是代码执行时当前的context object。

Global context

In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.

代码没有在任何函数中执行,而是在全局作用域中执行时,this的值就是global对象,对于浏览器来说,this就是window。

这一条规则还是比较容易接受的。

Function context

Inside a function, the value of this depends on how the function is called.

函数中的this值取决于这个函数是怎样被调用的,这一条规则就有点变态了,也是很容易出BUG的地方。

另外,this的值还与函数是否为严格模式("use strict")有关,这就非常的丧心病狂了...

大家如果好奇的话,出门左转看MDN文档,我多说无益,只说明一种简单的情况。

As an object method

When a function is called as a method of an object, its this is set to the object the method is called on.

当函数作为对象的方法被调用时,它的this值就是该对象。

var circle = {
radius: 10,
getRadius() {
console.log(this.radius);
}
}; circle.getRadius(); // 打印 10

self = this?

当我们需要在对象方法中嵌套一个内层函数时,this就会给我们带来实际的困扰了,大家应该写过这样的代码:

// 使用临时变量self
var circle = {
radius: 10,
outerDiameter() {
var self = this;
var innerDiameter = function() {
console.log(2 * self.radius);
};
innerDiameter();
}
}; circle.outerDiameter(); // 打印20

outerDiameter函数是circle对象的方法,因此其this值就是circle对象。

那我们直接写this.radius多好啊,可惜不能这么写,因为内层函数innerDiameter并不会继承外层函数outerDiameter的this值。outerDiameter函数的this值就是circle对象,this.radius等于10。

但是,innerDiameter函数的this值不是circle对象,那它到底是啥?它是innerDiameter函数执行时当前的context object,这个context object又是啥?其实我也晕了,所以不妨测试一下:

// innerDiameter函数中的this是window
var circle = {
radius: 10,
outerDiameter() {
var innerDiameter = function() {
console.log(this === window);
};
innerDiameter();
}
}; circle.outerDiameter(); // 打印true

innerDiameter函数中的this是window,为啥是window这个不去管它,反正不是circle对象。

因此,如果我们直接在innerDiameter函数中使用this的话,就出问题了:

// 使用普通函数
var circle = {
radius: 10,
outerDiameter() {
var innerDiameter = function() {
console.log(2 * this.radius);
};
innerDiameter();
}
}; circle.outerDiameter(); // 打印NaN

于是,我们不得不使用一个临时变量self将外层函数outerDiameter的this值搬运到内层函数innerDiameter。

.bind(this)

我们也可以使用.bind(this)来规避this变来变去的问题:

// 使用.bind(this)
var circle = {
radius: 10,
outerDiameter() {
var innerDiameter = function() {
console.log(2 * this.radius);
};
innerDiameter = innerDiameter.bind(this);
innerDiameter();
}
}; circle.outerDiameter(); // 打印20

但是,无论是使用临时变量self,还是使用.bind(this),都不是什么很简洁的方式。

总之,普通函数的this取值多少有点奇怪,尤其当我们采用面向对象的方式编程时,很多时候都需要用到this,大多数时候我们都不会去使用.bind(this),而是使用临时变量self或者that来搬运this的取值,写起来当然不是很爽,而且一不小心就会写出BUG来。

正如MDN文档所说:

Until arrow functions, every new function defined its own this value based on how the function was called。This proved to be less than ideal with an object-oriented style of programming.

箭头函数

箭头函数的this取值,规则非常简单,因为this在箭头函数中,可以看做一个普通变量。

An arrow function does not have its own this. The this value of the enclosing lexical scope is used; arrow functions follow the normal variable lookup rules.

箭头函数没有自己的this值,箭头函数中所使用的this都是来自函数作用域链,它的取值遵循普通普通变量一样的规则,在函数作用域链中一层一层往上找。

有了箭头函数,我只要遵守下面的规则,this的问题就可以基本上不用管了:

  • 对于需要使用object.method()方式调用的函数,使用普通函数定义,不要使用箭头函数。对象方法中所使用的this值有确定的含义,指的就是object本身。
  • 其他情况下,全部使用箭头函数。
// 使用箭头函数
var circle = {
radius: 10,
outerDiameter() {
var innerDiameter = () => {
console.log(2 * this.radius);
};
innerDiameter();
}
}; circle.outerDiameter(); // 打印20

对于内层函数innerDiameter,它本身并没有this值,其使用的this来自作用域链,来自更高层函数的作用域。innerDiameter的外层函数outerDiameter是普通函数,它是有this值的,它的this值就是circle对象。因此,innerDiameter函数中所使用的this来自outerDiameter函数,其值为circle对象。

结论

JavaScript是Brendan Eich花了10天时间设计出来的,因此各种莫名其妙的特性,this也算是其中一个奇葩。好在这些年ECMAScript标准发展很快也很稳定,每年撸一个新的标准,多少可以弥补一下JS的先天不足。

箭头函数对于this取值规则的简化,其实也就是为了少给大家添乱,谁能记得住普通函数this取值的那么多条条框框啊。。。

另外,MDN文档绝对是一个宝藏,大家可以多看看。

关于JS,我打算开始写一个系列的博客,大家还有啥不太清楚的地方?不妨留言一下,我可以研究一下,然后再与大家分享一下。也大家欢迎添加我的个人微信(KiwenLau),我是Fundebug的技术负责人,一个对JS又爱又恨的程序员。

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用

版权声明

转载时请注明作者Fundebug

以及本文地址:https://blog.fundebug.com/2019/06/18/arrow-function-this/

JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?的更多相关文章

  1. JavaScript深入浅出第2课:函数是一等公民是什么意思呢?

    摘要: 听起来很炫酷的一等公民是啥? <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函 ...

  2. JavaScript深入浅出第5课:Chrome是如何成功的?

    摘要: Chrome改变世界. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一 ...

  3. JavaScript深入浅出第4课:V8引擎是如何工作的?

    摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...

  4. JavaScript深入浅出第3课:什么是垃圾回收算法?

    摘要: JS是如何回收内存的? <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一 ...

  5. JavaScript箭头函数中的this详解

    前言 箭头函数极大地简化了this的取值规则. 普通函数与箭头函数 普通函数指的是用function定义的函数: var hello = function () { console.log(" ...

  6. ES6 箭头函数中的 this?你可能想多了(翻译)

    箭头函数=>无疑是ES6中最受关注的一个新特性了,通过它可以简写 function 函数表达式,你也可以在各种提及箭头函数的地方看到这样的观点——“=> 就是一个新的 function”. ...

  7. js中的this和箭头函数中的this

    一.ES6 允许使用"箭头"(=>)定义函数. // var f = v => v;// 上面的箭头函数等同于: // var f = function(v) {// ...

  8. 【进阶3-2期】JavaScript深入之重新认识箭头函数的this(转)

    这是我在公众号(高级前端进阶)看到的文章,现在做笔记 https://github.com/yygmind/blog/issues/21 上篇文章详细的分析了各种this的情况,看过之后对this的概 ...

  9. js 从两道面试题加深理解闭包与箭头函数中的this

     壹 ❀ 引 在本文之前我已经花了两个篇幅专门介绍了JavaScript中的闭包与this,正好今早地铁上看到了两道面试题,试着做了下发现挺有意思,所以想单独写一篇文章来记录解析过程.若你对于闭包与t ...

随机推荐

  1. Spring Cloud(一):入门篇

    Spring Cloud 简介 Spring Cloud 是一个基于 Spring Boot 实现的微服务架构开发工具,可以快速构建分布式系统中的某些常用模式,如配置管理.服务治理.断路器.智能路由. ...

  2. (转)简单移动平均线(Simple Moving Average,SMA) 定义及使用

    原文链接:https://blog.csdn.net/Enjolras_fuu/article/details/88602309   扩展:https://www.investopedia.com/t ...

  3. linux-VMtools安装

    一.解决的问题 1.剪切板无法共享 2.共享文件夹的设置 3.窗口无法自适应 二. 安装 1.进入centos 2.点击VMware菜单--->install VMware tools 3.打开 ...

  4. 2018最新cocoapods详细安装和使用

    1查看当前终端里存在的源 终端输入:$ gem sources -l2移除淘宝镜像 $ gem sources --remove https://rubygems.org/ 3装上目前能用的源 终端输 ...

  5. Android进阶之绘制-自定义View完全掌握(三)

    自定义View系列的第三篇博客,我们来学习如何实现自定义下拉框. 今天的程序,我们来实现这样的一个效果. 布局非常简单,我们直接开始编码. 修改activity_main.xml文件的代码. < ...

  6. 如何实现Web页面录屏?

    摘要: 很有意思的操作... 原文:web页面录屏实现 译者:frontdog Fundebug经授权转载,版权归原作者所有. 写在前面的话 在看到评论后,突然意识到自己没有提前说明,本文可以说是一篇 ...

  7. linux下安装oracle数据库--干货

    1.修改系统名称,关闭防火墙,selinux.2.挂载镜像,并写入开机自动挂载.挂载点为/mnt/yummount -t iso9660 -o,loop /soft/Centos6.iso /mnt/ ...

  8. React 语法基础(一)之表达式和jsx

    react负责逻辑控制 reactdom负责渲染 本节知识点 有 1)变量的使用,简单使用. 1==>jsx中的注释 {/* */} 2===> 简单的渲染 app.js ps==> ...

  9. sed命令总结

    目录 1.概述 2.查 1.打印整行(一或多) 2.正则打印包含关键字的行 2.增 3.删 4.改 5.后向引用 6.结合 7.练习 我叫张贺,贪财好色.一名合格的LINUX运维工程师,专注于LINU ...

  10. 201871010121-王方《面向对象程序设计(Java)》第一周学习总结

    项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这个作业的要求在哪里 https://www.cnblogs.com/wf-001128/ 作 ...