0. 为什么会出现箭头函数?
1. 传统的 javascript 函数语法并没有提供任何的灵活性,每一次你需要定义一个函数时,你都必须输入 function () {}, 这至少会出现两个问题,ES6 箭头函数都圆满解决了它,
第一个问题:代码输入快了容易输错成 funciton 或者 functoin 或者其它,但是 => 这个玩意你要是再写错就只能说有点过分了。
第二个问题:节省大量代码,我们先不用管下面的 ES6 代码为什么这样的语法能实现同样的功能,我们就直观的感受一下代码量。
ES5 写法:
function addFive(num){return num+5;}
alert(addFive(10));
ES6 写法:
var addFive = num=>num+5;
alert(addFive(5));
没有 function、没有 return, 没有(),没有{}, 这些全变成了浮云,世界好清静。
从上面我们就可以看到,使用箭头函数不仅仅能够避免错误,同时还能让我们少一丢丢代码,当然实际工作中远比这个代码量节省更多。
一方面是因为积累效应,每一部分少一丢丢合起来就多了,一方面是它还有更能节省代码和大幅提高工作效率的场景。
接下来我们就说说今天的主角 – 箭头函数。
ES6 标准新增了一种新的函数:Arrow Function(箭头函数),也称“胖箭头函数”,允许
使用“箭头”(=>)定义函数,是一种简写的函数表达式。
1、箭头函数语法
在 ES5 中我们实现一个求和的函数:
var sum = function(x, y) {return x + y}
要使用箭头函数,可以分两步实现同样的函数功能:
首先使用 => 来替代关键词 function
var sum = (x, y) => {return x + y}
这个特性非常好!!!
前面我们已经说过用 => 来替代关键词 function 就意味着不会写错 function 了,这真是一个绝妙的设计思想!
其次,函数体只有一条返回语句时,我们可以省略括号 {} 和 return 关键词:
var sum = (x, y) => x + y
再夸张一点点,如果只有一个参数时,()可省略。
这是箭头函数最简洁的形式,常用于作用简单的处理函数,比如过滤:
// ES5
var array = ['1', '2345', '567', '89'];
array = array.filter(function (item) {return item.length > 2;});
// ["2345", "567"]
// ES6
let array = ['1', '2345', '567', '89'];
array = array.filter(item => item.length > 2);
// ["2345", "567"]
箭头函数的主要使用模式如下:
// 一个参数对应一个表达式
param => expression;// 例如 x => x+2;
// 多个参数对应一个表达式
(param [, param]) => expression; // 例如 (x,y) => (x + y);
// 一个参数对应多个表示式
param => {statements;} // 例如 x = > {x++; return x;};
// 多个参数对应多个表达式
([param] [, param]) => {statements} // 例如 (x,y) => {x++;y++;return x*y;};
// 表达式里没有参数
() => expression; // 例如 var flag = (() => 2)(); flag 等于 2
() => {statements;} // 例如 var flag = (() => {return 1;})(); flag 就等于 1
// 传入一个表达式,返回一个对象
([param]) => ({key: value});
// 例如 var fuc = (x) => ({key:x})
var object = fuc(1);
alert(object);//{key:1}
大家不要觉得好多啊, 好麻烦,其实这些根本不复杂。投入一次,受益终生。(怎么感觉我像卖保险的……),写一两次你就习惯新的写法了。
2、箭头函数中的 this
箭头函数内的 this 值继承自外围作用域。运行时它会首先到它的父作用域找,如果父作用域还是箭头函数,那么接着向上找,直到找到我们要的 this 指向。
我们先看一道经典的关于 this 的面试题:
var name = 'leo';
var teacher = {
name: "大彬哥",
showName: function () {function showTest() {alert(this.name);
}
showTest();}
};
teacher.showName();// 结果是 leo,而我们期待的是大彬哥,这里 this 指向了 window,我们期待指向 teacher
大家知道,ES5 中的 this 说好听了叫 ” 灵活 ”,说不好听就是瞎搞,特别容易出问题. 而且面试还非常爱考,工作更不用说了,经常给我们开发捣乱,出现不好调试的 bug,用 E 箭头函数解决这个问题就很得心应手了。
var name = 'leo';
var teacher = {
name: "大彬哥",
showName: function () {let showTest = ()=>alert(this.name);
showTest();}
};
teacher.showName();
箭头函数中的 this 其实是父级作用域中的 this。箭头函数引用了父级的变量词法作用域就是一个变量的作用在定义的时候就已经被定义好,当在本作用域中找不到变量,就会一直向父作用域中查找,直到找到为止。
由于 this 在箭头函数中已经按照词法作用域绑定了,所以,用 call 或者 apply 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略:
var obj = {
birth: 1996,
getAge: function (year) {
var b = this.birth; // 1996
var fn = (y) => y - this.birth; // this.birth 仍是 1996
return fn.call({birth:1990}, year);
}
};
obj.getAge(2018); // 22(2018 - 1996)
由于 this 已经在词法层面完成了绑定,通过 call 或 apply 方法调用一个函数时,只是传入了参数而已,对 this 并没有什么影响。因此,这个设计节省了开发者思考上下文绑定的时间。
3、箭头函数的特性
3.1 箭头函数没有 arguments
箭头函数不仅没有 this,常用的 arguments 也没有。如果你能获取到 arguments,那它
一定是来自父作用域的。
function foo() {return () => console.log(arguments)
}
foo(1, 2)(3, 4) // 1,2
上例中如果箭头函数有 arguments,就应该输出的是 3,4 而不是 1,2。
箭头函数不绑定 arguments,取而代之用 rest 参数…解决
var foo = (...args) => {return args}
console.log(foo(1,3,56,36,634,6)) // [1, 3, 56, 36, 634, 6]
箭头函数要实现类似纯函数的效果,必须剔除外部状态。我们可以看出,箭头函数除了传入的参数之外,真的在普通函数里常见的 this、arguments、caller 是统统没有的!
如果你在箭头函数引用了 this、arguments 或者参数之外的变量,那它们一定不是箭头函数本身包含的,而是从父级作用域继承的。
3.2 箭头函数中不能使用 new
let Person = (name) => {this.name = name;};
let one = new Person("galler");
运行该程序,则出现 TypeError: Person is not a constructor
3.3 箭头函数可以与变量解构结合使用。
const full = ({first, last}) => first + ' ' + last;
// 等同于
function full(person) {return person.first + ' ' + person.last;}
full({first: 1, last: 5}) // '1 5'
3.4 箭头函数没有原型属性
var foo = () => {};
console.log(foo.prototype) //undefined
由此可以看出箭头函数没有原型。
另一个错误是在原型上使用箭头函数,如:
function A() {this.foo = 1}
A.prototype.bar = () => console.log(this.foo)
let a = new A()
a.bar() //undefined
同样,箭头函数中的 this 不是指向 A,而是根据变量查找规则回溯到了全局作用域。同样,使用普通函数就不存在问题。箭头函数中不可加 new,也就是说箭头函数不能当构造函数进行使用。
3.5 箭头函数不能换行
var func = ()
=> 1; // SyntaxError: expected expression, got '=>'
如果开发中确实一行搞不定, 逻辑很多,就加{},你就想怎么换行怎么换行了。
var func = ()=>{return '来啊!互相伤害啊!'; // 1. 加{} 2. 加 return
}
4、箭头函数使用场景
JavaScript 中 this 的故事已经是非常古老了,每一个函数都有自己的上下文。
以下例子的目的是使用 jQuery 来展示一个每秒都会更新的时钟:
$('.time').each(function () {setInterval(function () {$(this).text(Date.now());
}, 1000);
});
当尝试在 setInterval 的回调中使用 this 来引用 DOM 元素时,很不幸,我们得到的只是一个属于回调函数自身
上下文的 this。一个通常的解决办法是定义一个 that 或者 self 变量:
$('.time').each(function () {
var self = this;
setInterval(function () {$(self).text(Date.now());
}, 1000);
});
但当使用箭头函数时,这个问题就不复存在了。因为它不产生属于它自己上下文的 this:
$('.time').each(function () {setInterval(() => $(this).text(Date.now()), 1000);
});
箭头函数的另一个用处是简化回调函数。
// 正常函数写法
[1,2,3].map(function (x) {return x * x;});
// 箭头函数写法
[1,2,3].map(x => x * x);
当然也可以在事件监听函数里使用:
document.body.addEventListener('click', event=>console.log(event, this));
// EventObject, BodyElement
5、总结
5.1 箭头函数优点
箭头函数是使用 => 语法的函数简写形式。这在语法上与 C#、Java 8、Python(lambda 函数)和 CoffeeScript 的
相关特性非常相似。
非常简洁的语法,使用箭头函数比普通函数少些动词,如:function 或 return。
() => { ...} // 零个参数用 () 表示。x => {...} // 一个参数可以省略 ()。(x, y) => {...} // 多参数不能省略 ()。如果只有一个 return,{}可以省略。
更直观的作用域和 this 的绑定,它能让我们能很好的处理 this 的指向问题。箭头函数加上 let 关键字的使用,将会让我们 javascript 代码上一个层次。
5.2 箭头函数使用场景
箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如用在 map、reduce、filter 的回调函数定义
中,另外目前 vue、react、node 等库,都大量使用箭头函数,直接定义 function 的情况已经很少了。
各位同学在写新项目的时候,要不断的琢磨箭头函数使用场景、特点,享受使用箭头函数带来的便利,这样才能更快地成长。