深入浅出ES6(一):你真的了解箭头函数吗

前言
这个系列主要是说明ES6的新特性,从2015年到现在,es6出来也有挺长一段时间了,在项目中也在普遍使用这些特性.,网上的写es6的文章也大把, 但我感觉可能还是停留在会用的阶段,,至于为什么要这么用, 又为什么会出现这个特性,解决了什么样的问题,这些都有些一知半解. 所以,打算抽时间去了解es6未知的一面.
这篇文章,就先从用的最多的箭头函数开始->

箭头函数的由来
为什么叫箭头函数?
因为,它的定义用的是一个箭头.
那为什么要用一个箭头?
先来看一段代码:

  <script language="javascript">
    <!--
      document.bgColor = "brown";  // red
    // -->
    </script>

有没觉得很奇怪?在注释里边的代码也有效?当然,如果知道注释风格的代码的人,应该可以理解.

箭头符号在JavaScript诞生时就已经存在,当初第一个JavaScript教程曾建议在HTML注释内包裹行内脚本,这样可以避免不支持JS的浏览器误将JS代码显示为文本。

老式浏览器会将这段代码解析为两个不支持的标签和一条注释,只有新式浏览器才能识别出其中的JS代码。

为了支持这种奇怪的hack方式,浏览器中的JavaScript引擎将<!--这四个字符解析为单行注释的起始部分,我没开玩笑,这自始至终就是语言的一部分,直到现在仍然有效, 这种注释符号不仅出现<script>标签后的首行,在JS代码的每个角落你都有可能见到它,甚至在Node中也是如此。

碰巧,这种注释风格首次在ES6中被标准化了,但在新标准中箭头被用来做其它事情。

箭头序列–>同样是单行注释的一部分。古怪的是,在HTML中–>之前的字符是注释的一部分,而在JS中–>之后的部分才是注释。

你一定感到陌生的是,只有当箭头在行首时才会注释当前行。这是因为在其它上下文中,–>是一个JS运算符:“趋向于”运算符!

function countdown(n) {
while (n –> 0) // “n goes to zero”
alert(n);
blastoff();
}

上面这段代码可以正常运行,循环会一直重复直到n趋于0,这当然不是ES6中的新特性,它只不过是将两个你早已熟悉的特性通过一些误导性的手段结合在一起。你能理解么?通常来说,类似这种谜团都可以在Stack Overflow上找到答案。

当然,同样地,小于等于操作符<=也形似箭头,你可以在JS代码、隐藏的图片样式中找到更多类似的箭头,但是我们就不继续寻找了,你应该注意到我们漏掉了一种特殊的箭头。
当然,同样地,小于等于操作符<=也形似箭头,你可以在JS代码、隐藏的图片样式中找到更多类似的箭头,但是我们就不继续寻找了,你应该注意到我们漏掉了一种特殊的箭头。

<!-- 单行注释
--> “趋向于”操作符
<= 小于等于
=> 这又是什么?
=> 到底是什么?我们今天就来一探究竟。

首先,我们谈论一些有关函数的事情。

函数表达式无处不在
JavaScript中有一个有趣的特性,无论何时,当你需要一个函数时,你都可以在想添加的地方输入这个函数。

举个例子,假设你尝试告诉浏览器用户点击一个特定按钮后的行为,你会这样写:

$("#confetti-btn").click(

jQuery的.click()方法接受一个参数:一个函数。没问题,你可以在这里输入一个函数:

$("#confetti-btn").click(function (event) {
  playTrumpet();
  fireConfettiCannon();
});

对 于现在的我们来说,写出这样的代码相当自然,而回忆起在这种编程方式流行之前,这种写法相对陌生一些,许多语言中都没有这种特性。1958年,Lisp首 先支持函数表达式,也支持调用lambda函数,而C++,Python、C#以及Java在随后的多年中一直不支持这样的特性。

现在截然不同,所有的四种语言都已支持lambda函数,更新出现的语言普遍都支持内建的lambda函数。我们必须要感谢JavaScript和早期的JavaScript程序员,他们勇敢地构建了重度依赖lambda函数的库,让这种特性被广泛接受。

令人伤感的是,随后在所有我提及的语言中,只有JavaScript的lambda的语法最终变得冗长乏味。

// 六种语言中的简单函数示例
function (a) { return a > 0; } // JS
[](int a) { return a > 0; }  // C++
(lambda (a) (> a 0))  ;; Lisp
lambda a: a > 0  # Python
a => a > 0  // C#
a -> a > 0  // Java


箭袋中的新羽
ES6中引入了一种编写函数的新语法

// ES5
var selected = allJobs.filter(function (job) {
  return job.isSelected();
});
// ES6
var selected = allJobs.filter(job => job.isSelected());

当你只需要一个只有一个参数的简单函数时,可以使用新标准中的箭头函数,它的语法非常简单:标识符=>表达式。你无需输入function和return,一些小括号、大括号以及分号也可以省略。

(我个人对于这个特性非常感激,不再需要输入function这几个字符对我而言至关重要,因为我总是不可避免地错误写成functoin,然后我就不得不回过头改正它。)

如果要写一个接受多重参数(也可能没有参数,或者是不定参数、默认参数、参数解构)的函数,你需要用小括号包裹参数list。

// ES5
var total = values.reduce(function (a, b) {
  return a + b;
}, 0);
// ES6
var total = values.reduce((a, b) => a + b, 0);

我认为这看起来酷毙了。

正如你使用类似Underscore.js和Immutable.js这样的库提供的函数工具,箭头函数运行起来同样美不可言。事实上,Immutable的文档中的示例全都由ES6写成,其中的许多特性已经用上了箭头函数。

那么不是非常函数化的情况又如何呢?除表达式外,箭头函数还可以包含一个块语句。回想一下我们之前的示例:

// ES5
$("#confetti-btn").click(function (event) {
  playTrumpet();
  fireConfettiCannon();
});

这是它们在ES6中看起来的样子:

// ES6
$("#confetti-btn").click(event => {
  playTrumpet();
  fireConfettiCannon();
});

这是一个微小的改进,对于使用了Promises的代码来说箭头函数的效果可以变得更加戏剧性,}).then(function (result) { 这样的一行代码可以堆积起来。

注意,使用了块语句的箭头函数不会自动返回值,你需要使用return语句将所需值返回。

小提示:当使用箭头函数创建普通对象时,你总是需要将对象包裹在小括号里。

// 为与你玩耍的每一个小狗创建一个新的空对象
var chewToys = puppies.map(puppy => {});   // 这样写会报Bug!
var chewToys = puppies.map(puppy => ({})); //

用小括号包裹空对象就可以了。

不幸的是,一个空对象{}和一个空的块{}看起来完全一样。ES6中的规则是,紧随箭头的{被解析为块的开始,而不是对象的开始。因此,puppy => {}这段代码就被解析为没有任何行为并返回undefined的箭头函数。

更令人困惑的是,你的JavaScript引擎会将类似{key: value}的对象字面量解析为一个包含标记语句的块。幸运的是,{是唯一一个有歧义的字符,所以用小括号包裹对象字面量是唯一一个你需要牢记的小窍门。


箭头函数与this

既然要说箭头函数, 自然避不开this了
了解es5的人,应该知道, 在不同地方使用this,它的指向也有所不同, 但总归来讲, 实际上this总是指向最后调用它的对象.

举个例子:

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); //undefined
            console.log(this); //window        }
    }
}
var j = o.b.fn;
j();

上面这个例子中,fn函数始终没有执行,直到执行j();而j()又是通过window来调用,所以最后输出的this是window.
总的来说, this的调用方式和指向有以下几种:

函数调用方式和this指向:
  (1)直接调用:函数内部this指向全局window2) 通过对象使用点来调用:函数内部this指向调用对象
  (3) 触发事件调用函数:函数内部this指向调用触发事件的对象
  (4) 以new的方式来调用:函数内部this指向本次函数执行时对应的一个匿名对象。
  (5) 通过call的方法来间接调用方法:函数内部this指向call方法的第一个参数(自己指定this)。

这篇文章的目的不是要解析this, 如果对this不清楚的童鞋,可以参考这篇文档: this 指向详细解析(箭头函数)

看到this在这么多调用方式下的指向都不同, 有没有被绕晕? 相信肯定也有很多童鞋跟我一样,踩过this的坑. 而箭头函数的存在, 就避免了这种指向问题,在箭头函数中, this总是指向词法作用域, 也就是外层的调用者.

看个例子:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};

上面这个例子中,fn()并没有得到我们预期的日期, 而是出错,因为在fn()函数中,this.birth并不是指向当前obj,而是指向函数的调用者window, 因此birth是undefined,

现在让我们用箭头函数来改造这个函数:

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
        return fn();
    }
};
obj.getAge(); // 27

在箭头函数中, this指向的是外层调用者obj,因此最后拿到了birth这个参数.

另外,在箭头函数中,我们不再需要这种hack写法:

var that = this;

关于箭头函数的案例
上面应该大致说明白了箭头函数吧?
那么现在,就来说说它的使用.

改造开始

首先,我们从一个例子开始,在 ES5 中,我们一般是这么书写的。

var sum1 = function(num1, num2) { return num1 + num2; };

那么,改造成箭头函数,它是什么样子呢?

var sum2 = (num1, num2) => { return num1 + num2;};

小括号内的参数列表和花括号内的代码被 => 分隔开了。这个就是箭头函数的魅力,箭头函数使得表达更加简洁,从而简化了我们的代码。

如果一个表达式的代码块, 只是 return 后面跟一个表达式,那么还可以进一步简化。

var sum3 = (num1, num2) => num1 + num2;

如果某个方法只含有一个参数。

console.info(“=> ES5 写法”);
var curf1 = function(v) {
return v;
};

我们甚至可以省略小括号。

console.info(“=> ES6 写法”);
var curf2 = v => v;

如果某个方法没有参数。

console.info(“=> ES5 写法”);
var f1 = function() {
return “梁桂钊”;
};

我们仍可以提供一对空的小括号,如同不含参数的

console.info(“=> ES6 写法”);
var f2 = () => “梁桂钊”;

补充一个例外,如果箭头函数直接返回一个对象,必须在对象外面加上括号。

console.info("=> ES5 写法");
var f3 = function() {
return {
real_name: "梁桂钊",
nick_name: "LiangGzone"
}
};
console.log(f3());

console.info("=> ES6 写法");
var f4 = () => ({real_name: "梁桂钊",nick_name: "LiangGzone"});
console.log(f4());


关于解构
我们还可以使用到 ES6 解构赋值特性。ES5 写法,之前是这样的。

var f5 = function(person) {
return person.first + ' ' + person.last;
}

使用到 ES6 解构赋值特性后,就更加好理解了。

const f6 = ({ first, last }) => first + ' ' + last;

关于回调函数
我们经常使用回调函数,之前的常规的做法。

console.info("=> ES5 写法");
var x1 = [1,2,3].map(function (x) {
return x * x;
});
console.info(x1);

那么,现在我们可以进行改造。

console.info("=> ES6 写法");
var x2 = [1,2,3].map(x => x * x);
console.info(x2);

rest参数结合

没有使用箭头函数,之前,我们的代码可能长这样子。

console.info("=> ES5 写法");
var x3 = function(...nums){
return nums;
}
console.info(x3(512, 1024));

那么,现在我们可以进行改造。

console.info("=> ES6 写法");
var x4 = (...nums) => nums;
console.info(x4(512, 1024));

那么, 我们什么时候会用到箭头函数呢?
ES6 的箭头函数在微软的新版本中有使用, 他们也在 Babel,Traceur,和 TypeScript 得到实现, 等等

以上就是我对箭头函数的一点点见解, 如果有不对的地方, 欢迎指正!

参考文档:
(1) 深入浅出ES6(七):箭头函数 Arrow Functions: https://blog.csdn.net/hqh642134542/article/details/78809951
(2).你看懂“箭头函数”了么? https://www.cnblogs.com/libin-1/p/5995457.html
(3).this 指向详细解析(箭头函数): http://www.cnblogs.com/dongcanliang/p/7054176.html
(4) 极客学院-箭头函数: http://wiki.jikexueyuan.com/project/es-six-deeply/arrow-functions.html
(5) 廖雪峰-箭头函数:
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001438565969057627e5435793645b7acaee3b6869d1374000
(6) MDN-箭头函数: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
(6) 阮一峰-箭头函数issue: https://github.com/ruanyf/es6tutorial/issues/150

猜你喜欢

转载自blog.csdn.net/qq_37210523/article/details/80976620