ES6
允许使用箭头
(=>
)定义函数。箭头函数表达式
的语法
比函数表达式更简洁
,并且没有自己的this
,arguments
,super
或 new.target
。这些函数表达式
更适用于那些本来需要匿名函数
的地方,并且它们不能用作构造函数
。
(param1, param2, …, paramN) => { statements }
// 等同于
// function(param1, param2, …, paramN){ return {statements}; }
(param1, param2, …, paramN) => expression
// 等同于
// function(param1, param2, …, paramN){ return expression; }
// Parentheses are optional when there's only one parameter name:
// 当只有一个参数时,圆括号是可选的
(singleParam) => { statements }
singleParam => { statements }
// 等同于
// function(singleParam){ return expression; }
// The parameter list for a function with no parameters should be written with a pair of parentheses.
// 没有参数的函数应该写成一对圆括号。
() => { statements }
箭头函数
内定义的变量
及其作用域
(() => {
const { log } = console;
// 常规写法
const greeting_1 = () => {let now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
log(greeting_1()); //"Good evening."
log(now); // ReferenceError: now is not defined 标准的let作用域
// 参数括号内定义的变量是局部变量(默认参数)
const greeting_2 = (now=new Date()) => "Good" + (now.getHours() > 17 ? " evening." : " day.");
log(greeting_2()); //"Good evening."
log(now); // ReferenceError: now is not defined
// 对比:函数体内{}不使用var定义的变量是全局变量
const greeting_3 = () => {now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
log(greeting_3()); //"Good evening."
log(now); // Wed Apr 24 2019 23:02:07 GMT+0800 (中国标准时间)
// 对比:函数体内{} 用const定义的变量是局部变量
const greeting_4 = () => {const now = new Date(); return ("Good" + ((now.getHours() > 17) ? " evening." : " day."));}
log(greeting_4()); //"Good evening."
log(now); // ReferenceError: now is not defined
})()
// Parenthesize the body of function to return an object literal expression:
// 加括号的函数体返回对象字面表达式:
params => ({foo: bar})
// Rest parameters and default parameters are supported
// 支持剩余参数和默认参数
(param1, param2, ...rest) => { statements }
(param1 = defaultValue1, param2, …, paramN = defaultValueN) => {
statements }
// Destructuring within the parameter list is also supported
// 同样支持参数列表解构
const f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6
如果箭头函数
的代码块
部分多于一条语句
,就要使用大括号
将它们括起来,并且使用return
语句返回。
(() => {
const { log } = console;
const sum = (num1, num2) => { return num1 + num2; }
})()
鉴于 this
是词法层面上的,严格模式
中与 this
相关的规则都将被忽略。
(() => {
const { log } = console;
function Person() {
this.age = 0;
const closure = "123"
setInterval(function growUp() {
this.age++;
log(closure);
}, 1000);
}
const p = new Person();
function PersonX() {
'use strict'
this.age = 0;
const closure = "123"
setInterval(()=>{
this.age++;
log(closure);
}, 1000);
}
const px = new PersonX();
log(px);// PersonX {age: 0}
})()
严格模式
的其他规则依然不变。
由于 箭头函数
没有自己的this
指针,通过 call()
或 apply()
方法调用一个函数
时,只能传递参数(不能绑定this
),他们的第一个参数会被忽略
。(这种现象对于bind
方法同样成立)
(() => {
const { log } = console;
const adder = {
base : 1,
add : function(a) {
const f = v => v + this.base;
return f(a);
},
addThruCall: function(a) {
const f = v => v + this.base;
const b = {
base : 2
};
return f.call(b, a);
}
};
log(adder.add(1)); // 输出 2
log(adder.addThruCall(1)); // 仍然输出 2(而不是3)
})()
无参数箭头函数
在视觉上容易分析
(() => {
const { log } = console;
setTimeout( () => {
log('I happen sooner');
setTimeout( () => {
// deeper code
log('I happen later');
}, 1);
}, 1);
})()
(() => {
const { log } = console;
const elements = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
log(elements.map(function(element) {
return element.length;
})); // [8, 6, 7, 9]
// 上面的普通函数可以改写成如下的箭头函数
log(elements.map((element) => {
return element.length;
})); // [8, 6, 7, 9]
// 当箭头函数只有一个参数时,可以省略参数的圆括号
log(elements.map(element => {
return element.length;
})); // [8, 6, 7, 9]
// 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
log(elements.map(element => element.length)); // [8, 6, 7, 9]
// 在这个例子中,因为我们只需要 `length` 属性,所以可以使用参数解构
// 需要注意的是字符串 `"length"` 是我们想要获得的属性的名称,而 `lengthFooBArX` 则只是个变量名,
// 可以替换成任意合法的变量名
log(elements.map(({ "length": lengthFooBArX }) => lengthFooBArX)); // [8, 6, 7, 9]
})()
在箭头函数
出现之前,每个新定义的函数
都有它自己的 this
值(在构造函数的情况下是一个新对象
,在严格模式
的函数调用中为 undefined
,如果该函数被作为对象方法
调用则为基础对象
等)。This
被证明是令人厌烦的面向对象风格的编程。
(() => {
const { log } = console;
function Person() {
// Person() 构造函数定义 `this`作为它自己的实例.
this.age = 0;
setInterval(function growUp() {
// 在非严格模式, growUp()函数定义 `this`作为全局对象,
// 与在 Person()构造函数中定义的 `this`并不相同.
this.age++;
}, 1000);
}
const p = new Person();
log(p);// Person {age: 0}
})()
在ECMAScript 3/5
中,通过将this
值分配给封闭
的变量
,可以解决this
问题。
(() => {
const { log } = console;
function Person() {
const that = this;
that.age = 0;
setInterval(function growUp() {
// 回调引用的是`that`变量, 其值是预期的对象.
that.age++;
}, 1000);
}
})()
或者,可以创建绑定函数
,以便将预先分配的this
值传递到绑定的目标函数
(上述示例中的growUp()
函数)。
箭头函数
不会创建自己的this
,它只会从自己的作用域链的上一层继承this
。因此,在下面的代码中,传递给setInterval
的函数内的this
与封闭函数中的this
值相同:
(() => {
const { log } = console;
function Person(){
this.age = 0;
setInterval(() => {
this.age++; // |this| 正确地指向 p 实例
}, 1000);
}
const p = new Person();
log(p); // Person {age: 0}
})()
箭头函数不绑定Arguments
对象。因此,在本示例中,arguments
只是引用了封闭作用域内的arguments
:
(() => {
const { log } = console;
const arguments = [1, 2, 3];
const arr = () => arguments[0];
log(arr()); // 1
function foo(n) {
const f = () => arguments[0] + n; // 隐式绑定 foo 函数的 arguments 对象. arguments[0] 是 n
return f();
}
log(foo(1)); // 2
})()
在大多数情况下,使用剩余参数
是相较使用arguments
对象的更好选择。
(() => {
const { log } = console;
function foo(arg) {
const f = (...args) => args[0];
return f(arg);
}
log(foo(1)); // undefined
function foo(arg1,arg2) {
const f = (...args) => args[1];
return f(arg1,arg2);
}
log(foo(1,2)); //2
})()
箭头函数表达式
对非方法函数
是最合适的。如果试着把它们作为方法
时发生了什么。
(() => {
'use strict';
const { log } = console;
const obj = {
i: 10,
b: () => log(this.i, this),
c: function() {
log(this.i,this)
}
}
log(obj.b());
// undefined Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
// undefined
log(obj.c());
// 10 {i: 10, b: ƒ, c: ƒ}
// undefined
})()
箭头函数
没有定义this
绑定。
Object.defineProperty()
涉及Object.defineProperty()
的示例
(() => {
'use strict';
const { log } = console;
const obj = {
a: 10
};
Object.defineProperty(obj, "b", {
get: () => {
log(this.a, typeof this.a, this);
return this.a+10; // NaN
// 代表全局对象 'Window', 因此 'this.a' 返回 'undefined'
}
});
log(obj.b); // undefined "undefined" Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
})()
箭头函数不能用作构造器,和 new一起用会抛出错误。
(() => {
const { log } = console;
const Foo = () => {};
const foo = new Foo(); // TypeError: Foo is not a constructor
})()
箭头函数
没有prototype
属性。
(() => {
const { log } = console;
const Foo = () => {};
log(Foo.prototype); // undefined
})()
yield
关键字通常不能在箭头函数
中使用(除非是嵌套在允许使用的函数内)。因此,箭头函数
不能用作生成器
。
记住用params => {object:literal}
这种简单的语法返回对象字面量
是行不通的。
由于大括号
被解释为代码块
,所以如果箭头函数
直接返回一个对象,必须在对象字面量
外面加上括号,否则会报错。
(() => {
const { log } = console;
// 报错
// let getTempItem = id => { id: id, name: "Temp" };
// 不报错 但是没有取得指定返回值
let getTempItem_1 = id => { id: id };
// 不报错 可以取得指定返回值
let getTempItem_2 = id => ({ id: id, name: "Temp" });
log(getTempItem_1(123));// undefined
log(getTempItem_2(12));// {id: 12, name: "Temp"}
})()
上面代码getTempItem_1
中,原始意图是返回一个对象{ id: id }
,但是由于引擎
认为大括号
是代码块
,所以执行了一行语句id: id
。
这时,id
可以被解释为语句
的标签
,因此实际执行的语句
是id
;,然后函数
就结束了,没有返回值。
如果箭头函数
只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号
了。
(() => {
const { log } = console;
let fn = () => void doesNotReturn();
})()
箭头函数
在参数
和箭头
之间不能换行。
(() => {
const func = ()
=> 1;
// SyntaxError: expected expression, got '=>'
})()
虽然箭头函数
中的箭头
不是运算符
,但箭头函数
具有与常规函数
不同的特殊运算符优先级解析规则。
(() => {
const { log } = console;
let callback;
callback = callback || function() {}; // ok
callback = callback || () => {};
// SyntaxError: invalid arrow-function arguments
callback = callback || (() => {}); // ok
})()
箭头函数
可以有一个简写体
或常见的块体
。
在一个简写体
中,只需要一个表达式
,并附加一个隐式
的返回值
。在块体中,必须使用明确的return
语句。
(() => {
var func = x => x * x;
// 简写函数 省略return
var func = (x, y) => { return x + y; };
//常规编写 明确的返回值
})()
箭头函数
可以与变量解构
结合使用。
(() => {
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
return person.first + ' ' + person.last;
}
})()
箭头函数
使得表达更加简洁。
(() => {
const isEven = n => n % 2 === 0;
const square = n => n * n;
})()
上面代码只用了两行,就定义了两个简单的工具函数
。如果不用箭头函数
,可能就要占用多行,而且还不如现在这样写醒目。
箭头函数
的一个用处是简化回调函数。
(() => {
const { log } = console;
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x);
})()
另一个例子是
(() => {
// 正常函数写法
const result = values.sort(function (a, b) {
return a - b;
});
// 箭头函数写法
const result = values.sort((a, b) => a - b);
})()
rest
参数 #下面是 rest
参数与箭头函数结合的例子。
(() => {
const { log } = console;
const numbers = (...nums) => nums;
log(numbers(1, 2, 3, 4, 5));
// [1,2,3,4,5]
const headAndTail = (head, ...tail) => [head, tail];
log(headAndTail(1, 2, 3, 4, 5));
// [1,[2,3,4,5]]
})()
Using arrows to pierce the dark heart of computer science
箭头函数
有许多实际用途。还有一个可能用例:ES6 arrow
作为一种学习工具,可以发现深入了解计算本质
的东西。
1936
年,Alonzo Church和Alan Turing
独立开发了强大的计算数学模型
。
图灵称他的模型为a-machines
,但每个人都立即开始称他们为图灵机。教会写的是关于功能的。他的模型被称为λ演算。
(λ
是小写的希腊字母lambda
。)这项工作是Lisp
使用这个词LAMBDA
来表示函数的原因,这就是我们今天将函数表达式称为lambdas
的原因。
但什么是λ演算?什么是计算模型
应该是什么意思?
用几句话来解释很难,λ演算是最早的编程语言之一。
它不是设计是一门编程语言
,毕竟,存储程序计算机不会为十年或二十年,而是足够的简单
,精简
,语言的纯粹的数学思想,可以表达任何形式的一起走你希望做的计算。
教会希望这个模型
能够证明一般的计算方法
。
他发现他的系统只需要一件事:functions
。
想想这个说法有多么特别。如果没有对象
,没有数组
,没有数字
,没有if语句
,while循环
,分号
,赋值
,逻辑运算符
或事件循环
,就可以重建JavaScript
只能使用函数
从头开始执行的各种计算。
这是一个数学家可以使用Church
的λ
表示法编写的程序
的例子:
fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))
等效的JavaScript
函数如下所示:
var fix = f => (x => f(v => x(x)(v)))
(x => f(v => x(x)(v)));
也就是说,JavaScript
包含实际运行的λ演算的实现。
λ演算是用JavaScript
编写的。
关于Alonzo Church
和后来的研究人员对λ演算做了什么的故事,以及它如何悄悄地将自己暗示到几乎所有主要的编程语言,都超出了这篇文章的范围。
但是,如果你对计算机科学
的基础感兴趣,或者你只是想看看除了函数
之外什么都没有的语言可以做循环
和递归
之类的事情,那么你可能再看一下 Church numerals 和 fixed-point combinators。
借助ES6箭头
的优势,JavaScript
可以合理地声称是探索λ演算的最佳语言
。
更简明的Promise
链
(() => {
promise.then(a => {
// ...
}).then(b => {
// ...
});
})()
方便数组reduce
,filter
,和map
;
(() => {
const { log } = console;
// Easy array filtering, mapping, ...
const arr = [5, 6, 13, 0, 1, 18, 23];
const sum = arr.reduce((a, b) => a + b);
log(sum); // 66
const even = arr.filter(v => v % 2 == 0);
log(even); // [6, 0, 18]
const double = arr.map(v => v * 2);
log(double); // [10, 12, 26, 0, 2, 36, 46]
})()
(() => {
const { log } = console;
// 空的箭头函数返回 undefined
const empty = () => {};
log((() => 'foobar')());// foobar
// (这是一个立即执行函数表达式,可参阅 'IIFE'术语表)
})()
箭头函数
也可以使用条件(三元
)运算符
(() => {
const { log } = console;
const simple = a => a > 15 ? 15 : a;
log(simple(16)); // 15
log(simple(10)); // 10
let max = (a, b) => a > b ? a : b;
log(max(4, 5));// 5
})()
箭头函数
也可以使用闭包
(() => {
const { log } = console;
// 标准的闭包函数
function Add_1(){
let i = 0;
return function b(){
return (++i);
};
};
const v_1 = Add_1();
log(v_1()); //1
log(v_1()); //2
//箭头函数体的闭包( i=0 是默认参数)
const Add_2 = (i=0) => {return (() => (++i) )};
const v_2 = Add_2();
log(v_2()); //1
log(v_2()); //2
//因为仅有一个返回,return 及括号()也可以省略
const Add_3 = (i=0)=> ()=> ++i;
const v_3 = Add_3();
log(v_3()); //1
log(v_3()); //2
})()
(() => {
const { log } = console;
const fact = (x) => ( x==0 ? 1 : x*fact(x-1) );
log(fact(5)); // 120
})()