06月16, 2017

JavaScript: 为初学者介绍箭头函数

原文:https://hackernoon.com/javascript-arrow-functions-for-beginners-926947fc0cdc

alt

上周我发表了一篇为初学者介绍 this 关键字的博文。这篇文章中没有涉及到的主题之一是箭头函数。这个主题只是因为太大,没法在那篇文章中讲解,所以本文在这里做一个补充。请继续阅读,学习有关箭头函数的基础知识!

好处 #1: 更短的语法

下面我们先看一个普通的函数:

function funcName(params) {   
    return params + 2; 
}

funcName(2);
// 4

上述代码预示了创建箭头函数的两个原因之一:更短的语法。完全相同的函数可以被表示为只有一行代码的箭头函数:

var funcName = (params) => params + 2

funcName(2);
// 4

很棒。这个示例显然是极端简化的,不过但愿也能阐明我的观点。下面我们稍微更深入地看看箭头函数的语法:

(参数) => { 语句 }

如果没有参数的话,我们就像下面这样表示箭头函数:

() => { 语句 }

当只有一个参数时,圆括号是可选的:

参数 => { 语句 }

最后,如果要返回一个表达式,就要把大括号删掉:

参数 => 表达式

// 等价于:

function (参数){
  return 表达式;
}

好了,现在你知道了语法,来个示例怎么样?打开 Chrome 开发者控制台 (Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),并键入如下代码:

var double = num => num * 2

如你所见,我们正把一个箭头函数赋值给变量 double。这个箭头函数只有一个参数 num。因为只有一个参数,所以我们就可以省略括住参数的圆括号。因为我们想返回 num * 2 的值,所以也省略了括住要返回的表达式的大括号。下面我们调用该函数,看看其结果:

double(2);
// 4

double(3);
// 6

好处 #2: 不绑定 this

在继续之前,你应该很好地理解 this 关键字及其工作机制。如果想学习,或者需要复习,请在继续前阅读[我的这篇博文](https://hackernoon.com/javascript-the-keyword-this-for-beginners-fb5238d99f85)。

与普通函数不同,箭头函数不需要绑定 this,而是词法绑定 this(即,this 保持它在原始上下文中的含义)。

有个示例应该会让这更清楚一些。在控制台中,创建一个构造器函数,然后创建它的一个实例:

function Counter() {
  this.num = 0;
}

var a = new Counter();

从上一篇文章你应该知道,构造器函数中 this 的值被绑定到正在新创建的对象,在本例中,就是 a 对象。这就是为什么我们输出 a.num 会得到 0 的原因。

console.log(a.num);
// 0

如果想把 a.num 的值每秒钟增加该怎么办呢?我们可以用 setInterval() 函数。setInterval() 是一个会在设定的毫秒数之后重复调用另一个函数的函数。下面我们把它加到 Counter 函数中:

function Counter() {
  this.num = 0;

  this.timer = setInterval(function add() {
    this.num++;
    console.log(this.num);
  }, 1000);
}

上面的代码除了加了一个变量 this.timer,并把它设置为等于 setInterval 函数外,其它的看起来与之前没什么两样。每隔1000微秒(即1秒)代码就会执行一次。this.num 会加一,然后被输出到控制台上。下面我们试一试,在控制台中创建再次创建 Counter 的一个实例:

var b = new Counter();
// NaN
// NaN
// NaN
// ...

如你所见,函数会每秒都会输出到屏幕上一次。不过这结果不是我们想要的。NaN(Not a Number)一直被输出。那么是哪里出错了呢?首先,运行如下代码终止烦人的输出:

clearInterval(b.timer);

我们再回去看看代码。setInterval 函数不是在一个声明过的对象上调用的,也不是用 new 关键字调用的(只有 Counter() 函数是)。而且最后,我们没有用 callbindapplysetInterval 只是一个普通函数。实际上,setIntervalthis 的值被绑定到了全局对象上!下面我们通过输出 this 的值来验证一下这个推测:

function Counter() {
  this.num = 0;

this.timer = setInterval(function add() {
    console.log(this);
  }, 1000);
}

var b = new Counter();

你会看到,window 对象每秒被输出一次。通过执行如下代码清除掉时间间隔:

clearInterval(b.timer);

回到原始函数。它之所以输出 NaN,是因为 this.num 引用的是 window 对象上的 num 属性(而 window.num 是不存在的),而不是我们刚创建的 b 对象(b.num)。

那么我们该如何纠正呢?用箭头函数!我们需要一个不绑定 this 的函数。用箭头函数的话,this 就会一直是其上下文的原始绑定。下面把原始 Counter 函数中的 setInterval 用箭头函数替换:

function Counter() {
  this.num = 0;

  this.timer = setInterval(() => {
    this.num++;
    console.log(this.num);
  }, 1000);
}

var b = new Counter();
// 1
// 2
// 3
// ...

我们会看到,控制台开始输出递增的数字 - 它起作用了!Counter 构造器函数创建的原始 this 绑定被保留下来了。在 setInterval 函数内,this 依然被绑定到了我们新创建的 b 对象上!

我们可以用如下代码清除掉时间间隔:

clearInterval(b.timer);

为证明这种机制,我们再试在箭头函数内输出 this。我们将在 Counter 函数中创建一个名为 that 的变量。然后如果 setInterval 函数中的 this 的值等于父函数 Counter 中的 this(通过 that)的值,那么就会输出 true

function Counter() {
  var that = this;

this.timer = setInterval(() => {
    console.log(this === that);
  }, 1000);
}

var b = new Counter();
// true
// true
// ...

不出所料,每次都会输出 true!再次用如下语句清除时间间隔:

clearInterval(b.timer);

总结

希望本文能帮助大家看到箭头函数的两个主要好处:

  1. 语法更短
  2. 不绑定 this

声明一下,箭头函数涉及的知识比本文所解释的要多。但是本文应该已经为深入学习打下了很好的基础。

本文链接:http://www.xiaojichao.com/post/arrowfunction.html

-- EOF --

Comments