- 什么是箭头函数?看下面的语法。
- 为什么要用箭头函数?一个字:短。
- 1 箭头函数最大的优点就是简短。
- 2 箭头函数不能用作构造函数。适用于那些本来需要匿名函数的地方。
- 3 引入箭头函数有两个方面的作用:更简短的函数并且不绑定this。
1. 箭头函数语法
- 标准形式:块体
(参数1, 参数2, …, 参数N) => { 函数体 }
var arrowFunc1 = (x, y, z) => {
if (x > 0){
return y + z;
}else{
return y -z;
}
}
a = arrowFunc1(1, 2, 3);//5
b = arrowFunc1(-1, 2, 3);//-1
- 如果函数体只有一个表达式,可以写成:简写体,省略 return
(参数1, 参数2, …, 参数N) => 表达式
// 你看看,多么简洁
var arrowFunc = (x, y) => x + y;
a = arrowFunc(1, 2);//3
- 如果只有一个参数,可以写成:
单一参数 => {函数体}
单一参数 => 表达式
var arrowFunc = x => x * 2;
a = arrowFunc(2);//4
- 如果没有参数,应该写成一对圆括号。
() => {函数体}
() => 表达式
var arrowFunc = () => console.log('hello world');
arrowFunc(); //hello world
- 支持剩余参数和默认参数
(参数1, 参数2, …rest) => {函数体}
(参数1 = 默认值1,参数2 = 默认值2, …, 参数N = 默认值N) => {函数体}
- 支持参数列表解构
- ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
var arrowFunc = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
a = arrowFunc(); // 6
2. 深入理解箭头函数
- 更短的函数
var materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
// 使用普通函数: 求每个元素的长度
a = materials.map(function(elem){
return elem.length;
}); //[8, 6, 7, 9]
// 使用箭头函数: 求每个元素的长度
a = materials.map(elem => elem.length); //[8, 6, 7, 9]
// 你看看,你看看,多么简洁。简洁明了,
// 节省打字时间,节省阅读时间,节省理解时间。
- 不绑定this
- 在箭头函数出现之前,每个新定义的函数都有它自己的 this 值。
- This 被证明是令人厌烦的面向对象风格的编程。(我倒是很喜欢 this,看见引用类属性而不加 this 的代码,我就想给加上。)
- this 确实会带来一些问题,比如下面的内部函数中的 this 和外面的 this 分别指向不同的对象。但是我们希望它们指向同一个对象。
function Person() {
// Person() 构造函数定义 `this`作为它自己的实例.
// 也就是说,这个 this 指向 Person() 的实例。
this.age = 10;
console.log("this.age=", this.age); //输出 10
setInterval(function growUp() {
// 在非严格模式, growUp()函数定义 `this`作为全局对象,
// 与在 Person()构造函数中定义的 `this`并不相同.
// 也就是说,这个 this 指向全局对象而不是 Person() 的实例,所以计算结果是 NaN
this.age++;
console.log("this.age=", this.age);
}, 1000);
}
a = new Person();
- 在ECMAScript 3/5中,通过将this值分配给封闭的变量(局部变量),可以解决this问题。
- 还记得 2008 年第一次遇到 this 引起的 bug 时,查了半天,函数闭包中的 this 居然和外面的不一样。看了高手的代码,才发现使用下面这一招就解决了。
- 大家习惯用: var self = this;
function Person() {
var that = this;//在 ActionScript 中,我经常这么干
that.age = 0;//让局部变量 that 指向 this
setInterval(function growUp() {
// 回调引用的是`that`变量, 其值是预期的对象.
// 也就是说,内部函数可以访问父级函数的局部变量,这样就保证了同一个 that 指向同一个 this
that.age++;
}, 1000);
}
- 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。因此,在下面的代码中,传递给setInterval的函数内的this与封闭函数中的this值相同:
- 箭头函数很好地解决了 this 问题,this 就是 this,只有一个 this,不必再为不同的 this 头疼了。
- 不绑定 this,也就是说作用域不把 this 据为己有,而是继承作用域链上一层的 this,大家共用一个 this 。
function Person(){
this.age = 0;//这个 this 指向 Person 实例
console.log("Person() this.age=", this.age);
// 下面的 this 继承父级函数的 this 也指向 Person 实例
setInterval(() => {
this.age++;
console.log("箭头函数 this.age=", this.age);
}, 1000);
}
a = new Person();
console.log("全局对象 this.age=", this.age);//这个 this 指向全局对象
/** 输出如下
Person() this.age= 0
全局对象 this.age= undefined
箭头函数 this.age= 1
箭头函数 this.age= 2
箭头函数 this.age= 3 每秒输出一条
*/
- 通过 call 或 apply 调用箭头函数,thisArg 会被忽略
var adder = {
base: 1,
add: function(a){
// 箭头函数 this 能访问到上层的 base
var f = v => v + this.base;
return f(a);//返回的是箭头函数的计算结果
},
addThruCall: function(a){
var f = v => v + this.base;
var b = {
base: 2
};
// 通过 call() 调用箭头函数 f 指定参数 thisArg 的值是 b,
// 普通函数的 this 会指向 b, 但是箭头函数会忽略 b 仍然用作用域链上层的 this
// 箭头函数中的 this.base 指向 adder 中的 1 而不是 b 中的 2
return f.call(b, a);
},
};
//通过 . 语法调用,则 this 指向 adder
console.log(adder.add(1)); // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2
- 箭头函数不绑定 arguments 对象
- 也就是说,在箭头函数内调用的 arguments 对象并不是箭头函数自身的,而是外层函数的。
- 或者说,箭头函数没有自身的 arguments 对象。
function foo(n) {
//普通函数内部的箭头函数:arguments[0] 是 n
var f = () => arguments[0] + n;
return f();//不是返回箭头函数 f 而是返回 f 的计算结果
}
console.log(foo(3));//输出:6
// 不在普通函数内部,箭头函数调用 arguments 对象报错
var arr = () => arguments[0];
arr();
- 如果确实需要实现箭头函数与外层函数参数对象的分离,那么最好使用剩余参数(rest parameter)。
function foo(n) {
// 使用剩余参数实现箭头函数与外层函数参数对象的分离
// args[0] 是箭头函数的第一个剩余参数 2,而不是 n
var f = (...args) => args[0] + n;
return f(2);
}
console.log(foo(1));//输出:3
- 像函数一样使用箭头函数
- 箭头函数本来就是函数,但它是特殊的函数,所以有些特殊行为:不绑定 this,不绑定 arguments 。
var obj = {
i: 10,
// 箭头函数的 this 指向哪里? App 实例(调用方所在的类)
b: () => console.log(this.i, this),
// 普通函数的 this 指向哪里? obj(调用方)
c: function(){
console.log(this.i, this);
}
}
//调用方法函数 b() 输出异常,c() 输出正确。
obj.b(); // undefined App {props: Object, context: Object…}
obj.c(); // 10 Object {i: 10}
- 非方法函数用箭头函数来定义,是最佳选择。方法函数不要用箭头函数定义,否则 this 指向错误。
var obj = {
a: 10
};
// 默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。
Object.defineProperty(obj, 'b', {
// 一个给属性提供 getter 的方法,当访问该属性时,该方法会被执行,
// 方法执行时没有参数传入,但是会传入this对象(由于继承关系,这里的 this 并不一定是定义该属性的对象)。
get:() => { // 使用箭头函数,this 指向哪里?App 实例(调用方所在的类)。
// 我们不希望这样,那么就应该用普通函数。
console.log(this.a, typeof this.a, this); // undefined "undefined" App {props: Object…}
return this.a+10;
}
});
Object.defineProperty(obj, 'c', {
get:function () { // 使用普通函数,this 指向调用方 obj 这才是我们想要的
console.log(this.a, typeof this.a, this); // 10 "number" Object {a: 10}
return this.a+10;
}
});
a = obj.b; // a= NaN
b = obj.c; // b= 20
- 箭头函数的 prototype 属性:箭头函数原型
var Foo = () => {console.log('箭头函数 Foo')};
console.log("Foo.prototype=", Foo.prototype);
// Foo.prototype= Foo {} constructor: Foo() __proto__: Object
Foo();// 箭头函数 Foo
// 下面这样调用,和直接调用 Foo() 效果相同
Foo.prototype.constructor(); // 箭头函数 Foo
- 返回对象字面量:要用圆括号把对象字面量包起来
var func = () => ({foo:1});
a = func(); // a= Object {foo: 1}
// 用这种简单的语法返回对象字面量是行不通的。
var func2 = () => { foo: 1 }; // Failed to compile.
b = func2();
- yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。
- 箭头函数在参数和箭头之间不能换行。
- 解析顺序
- 虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊运算符优先级解析规则。
var callback;
callback = callback || function(){};
// foo = foo || () => {}; // Failed to compile.
callback = callback || (() => {});
// 很多情况下,需要这种写法,用于处理默认情况
callback();
// 有时会这样写
callback && callback();
- 空的箭头函数返回 undefined
// 什么情况下会用到空的箭头函数呢?
var empty = () => {};
a = empty(); // a= undefined
- 立即执行函数表达式 IIFE
// 前面是箭头函数定义,加上括号就是执行
a = (() => '箭头函数')();// a= 箭头函数
- 详解 javascript 立即执行函数表达式(IIFE) https://www.cnblogs.com/zichi/p/4401755.html
- 使用立即执行函数锁住变量保存状态。
- 立即执行函数在模块化中也大有用处。用立即执行函数处理模块化可以减少全局变量造成的空间污染,构造更多的私有变量。
- 使用箭头函数实现数组过滤、映射。。。简洁明了
var arr = [3, 1, 2, 8, 1, 12, 21];
//元素求和:前驱值 + 当前值 = 结果,作为下次运算的前驱值
var sum = arr.reduce((a, b) => a + b);// 48
// 过滤,留下奇数,不会自动去重
var even = arr.filter(v => v % 2 == 1); // [3, 1, 1, 21]
// 映射:所有元素 2 倍
var double = arr.map(v => v * 2); // [6, 2, 4, 16, 2, 24, 42]
- 实现更简明的 promise 链
- 什么是 Promise ?Promise 其实很简单,就是一个处理异步的方法。
- 为什么要用 Promise ?如果用传统的回调函数来处理异步,存在缺陷:
- 随着业务逻辑变复杂,回调层级会越来越深。
- 代码耦合度比较高,不易修改。
- 每一步操作都需要手动进行异常处理,比较麻烦。不科学。自动化才科学,才靠谱。
- Promise 的链式调用与中止 https://segmentfault.com/a/1190000007598894
- 使用 promise 编码之前,可以先思考两个问题。一是如何链式调用,二是如何中止链式调用。
// promise 链式调用开头,生成一个 promise 对象
function start(){
return new Promise((resolve, reject) => {
resolve('家里蹲大学欢迎您!');
reject('婴儿不能上大学');
})
}
start().then(data => { //执行 start() 函数返回的 promise
console.log("result of start: ", data);
return Promise.resolve('大一过关'); // 返回一个 promise 对象 p1
}).then(data => { //执行 p1
console.log("result of p1: ", data);
return Promise.reject('大二失败'); // 返回一个 promise 对象 p2
}).then(data => { //执行 p2
console.log("result of p2: ", data);
return Promise.resolve('大三过关'); // 返回一个 promise 对象 p3
}).catch(ex => { // 捕获异常
console.log('ex: ', ex);
return Promise.resolve('大二重修'); // 返回一个 promise 对象 p4
}).then(data => { //执行 p4
console.log('result of p4: ', data);
})
/** 上面的代码最终会输出:
result of start: 家里蹲大学欢迎您!
result of p1: 大一过关
ex: 大二失败
result of p4: 大二重修
*/
你看看,你-看-看,使用箭头函数多么简明!!!
- 箭头函数也可以使用条件(三元)运算符:
// 定义箭头函数 simple: 条件表达式作为函数体
var simple = a => a > 15 ? 15 : a;
a = simple(16);
b = simple(10);
console.log("a=", a,"b=", b); // a= 15 b= 10
// 定义箭头函数 max
let max = (a, b) => a > b ? a : b;
a = max(1,2);
b = max(4,3);
console.log("a=", a,"b=", b); // a= 2 b= 4
- 箭头函数内定义的变量及其作用域
// 常规写法
var greeting = () => {
let now = new Date();
return (((now.getHours() > 22) ? '睡了吗?' : '嘎哈呢?') + '老铁');
};
a = greeting();
console.log("a=", a); // a= 嘎哈呢?老铁
// console.log("now=", now); //标准的 let 作用域:'now' is not defined
- 参数括号内定义的变量是局部变量(默认参数)
var greeting = (now=new Date()) => (now.getHours() < 22 ? '揍嘛里?' : '睡喽迈?') + '老乡';
a = greeting();
console.log("a=", a); // a= 揍嘛里?老乡
// console.log(now); // 编译失败:'now' is not defined
- 函数体内{} 用 var 定义的变量是局部变量
var greeting = () => {
var now = new Date();
return (now.getHours() > 22 ? '睡了吗?' : '嘎哈呢?') + '老铁';
};
a = greeting();
console.log("a=", a); // a= 嘎哈呢?老铁
// console.log(now); // 编译失败:'now' is not defined
- 箭头函数也可以使用闭包:
- 标准的闭包函数
function foo(){
var i=0;
// 返回一个闭包函数:函数内的函数
return function bar(){
// 可以访问函数 foo() 内的局部变量
return (++i);
};
};
a = foo(); //得到一个闭包函数:bar()
b = a(); //得到计算结果:1
c = a(); //得到计算结果:2
- 箭头函数体的闭包
// 定义箭头函数,返回一个箭头函数:闭包。一行代码实现,够简洁吧?
var subtract = (i=100) => {return () => --i};
a = subtract(); // 得到一个闭包函数:() 能访问 subtract() 的局部变量 i
b = a(); // 得到计算结果:99
c = a(); // 得到计算结果:98
// 还能更简洁,但是可读性略差。可以用来装逼。。。
var subtract2 = (i=100) => () => --i;
a = subtract2();
b = a(); // 得到计算结果:99
c = a(); // 得到计算结果:98
- 箭头函数递归
// 定义箭头函数,接收参数 x ,递归调用
var fact = (x) => x === 0 ? 1 : x * fact(x - 1);
a = fact(4); // 24