はじめに
(この記事は本来先週のコンテンツでしたが、昨日書くのを忘れてしまいました。こっそりと補います)
最近仕事が少し多く、sort は通過しましたが、やはりやや忙乱です。しかし不管怎样、計画はそこにあり、最終的には一つ一つ完了します
無駄話はここまでにして、母の体が早く良くなることを願います~
##一.アロー関数简介
アロー関数(arrow function)は、C# 中の lambda 式です。Java8 もそれを豪華なランチに加えたと言われています。しかし不管怎样、JS は他の言語から優れた特性を取り入れています(例えば yield、class、デフォルトパラメータなど)。これらの特性の良し悪しは問わず、このこと自体が非常に良いことです(少なくとも私たちが使用しているのは活力に満ちたツールです)
Java は->アローを使用し、C# が使用するアローは JS と同じです:=>。このアローは「lambda 演算子」と呼ばれ、業界用語では"goes to"と読みます
lambda 式(アロー関数)は関数を定義する最も簡潔な方法と言われています。構文上ほとんど冗長な成分がありません。JS の弱タイプの特徴のため、JS 中の lambda 式は C# や Java 中のものよりさらに簡潔です(パラメータタイプ宣言が少ない)
一言で言えば、アロー関数は lambda 式であり、より簡潔な function 定義方式を提供します
##二.構文
arg => returnVal構文は関数を作成する最も簡潔な方式で、形参がarg、戻り値がreturnValの function を定義します
他の構文は以下の表の通り:
| 構文 | 等価コード | 意味 |
|---|---|---|
x => f(x) | function(x) {
return f(x);
} | y=f(x) |
(x, y)=>x + y; | function(x, y) {
return x + y;
} | y=f(x,y)=x+y |
(x, y)=>{g(x); g(y); return h(x, y);}; | function(x, y) {
g(x);
g(y);
return h(x, y);
} | g(x), g(y) y=f(x,y)==============h(x,y) |
()=>({}); | function() {
return {};
} | y={}
|
P.S. 第三列の「意味」は数学関数の意味を指し、lambda 式は元々数学者が作り出したものです
簡単な例は以下の通り:
// 簡単な例、匿名関数の定義を簡素化
var arr = [1, 3, 21, 12];
console.log(arr.map(x => 2 * x)); // [2, 6, 42, 24]
console.log(arr.sort((a, b) => a - b)); // [1, 3, 12, 21]
arr.forEach((item, index, arr) => {
if (index %2 == 0) {
console.log(item);
}
if (index == arr.length - 1) {
console.log(`last item is ${item}`);
}
});
複雑な例:
// 複雑な例
var app = {
cache: {},
ajax: function(url, callback) {
var self = this;
function req(url) {
var res = `data from ${url}`;
console.log(`ajax request ${url}`);
// cache res
self.cache[url] = res;
return res;
}
var data = req(url);
callback(data);
}
}
app.ajax('http://www.xxx.xx', function(data) {
console.log(`receive: ${data}`);
});
console.log(app.cache);
アロー関数で上記を書き換え:
// アロー関数で書き換え
var es6App = {
cache: {},
ajax(url, callback) {
var req = url => {
var res = `data from ${url}`;
console.log(`ajax request ${url}`);
// cache res
this.cache[url] = res;
return res;
}
var data = req(url);
callback(data);
}
}
es6App.ajax('http://www.q.xx', function(data) {
console.log(`receive: ${data}`);
});
console.log(es6App.cache);
that = this という必要な無駄話を消除しました。実際には一項の原則を遵守すればすべての that = this を消除できます。下文の注意事項中の 3.this について を参照
##三.特徴及び注意事項
###1.パラメータリストと戻り値の構文
1 個のパラメータの時、左側に直接パラメータ名を書き、0 個または複数のパラメータの時、パラメータリストは () で包む必要があります
関数本体が 1 条の文のみの時、右側の値は自動的に関数の戻り値になり、関数本体が 1 条を超える文の時、関数本体は {} で包む必要があり、かつ手動で return する必要があります
P.S. もちろん、容易に考えられるのは青紅皂白を問わず、() => {} をアロー関数の起手式とすることですが、このようにすることは推奨しません。なぜなら下一条で { は曖昧であると述べた通り、面倒をもたらす可能性があるからです
###2.曖昧な文字
{ は唯一1 つの曖昧な文字なので、オブジェクトリテラルを返す時は () で包む必要があります。否则ブロック文として解析されます
例えば:
var f1 = () => {};
f1(); // undefined を返す
// 等価于
// var f1 = function() {};
var f2 = () => ({});
f2(); // 空オブジェクト{}を返す
// 等価于
// var f2 = function() {return {};};
###3.this について
アロー関数は外囲作用域から this を継承します。that = this を避けるために、遵守する必要があります:オブジェクト上の直接関数属性値は function 構文を使用する外、他の関数はすべてアロー関数を使用
この規則は容易に理解できます。例は以下の通り:
// シーン 1
function MyType() {}
MyType.prototype.fn = function() {/*アロー関数を定義*/}; // アロー関数中の this は MyType タイプインスタンスを指す
// シーン 2
var obj = {};
obj.fn = function() {/*アロー関数を定義*/}; // アロー関数中の this は obj を指す
違いは function キーワードで定義された関数属性中で、該関数の this はこの関数属性が所属するオブジェクトを指します(匿名関数の this は global オブジェクトまたは undefined を指す)。说白了、function は新しい this を定義でき、アロー関数はできません。それは外層から 1 つの this を借りるしかありません。したがって、新しい this が現れる必要がある時は function で関数を定義し、外層の this を沿用したい時はアロー関数を使用します
###4.arguments オブジェクトについて
アロー関数には arguments オブジェクトがありません。なぜなら標準は [デフォルトパラメータ、可変パラメータ](/articles/デフォルトパラメータと不定パラメータ-es6 ノート 4/)、[パラメータ解構](/articles/destructuring(解構代入)-es6 ノート 5/) の使用を奨励しているからです
例えば:
// 一般関数
(function(a) {console.log(`a = ${a}, arguments[0] = ${arguments[0]}`)})(1);
// log print: a = 1, arguments[0] = 1
// 上面と等価のアロー関数
(a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`))(1);
// log print: Uncaught ReferenceError: arguments is not defined
これは関数が匿名かどうかとは無関係で、規則はアロー関数中では arguments オブジェクトにアクセスできない(undefined)ことです。以下の通り:
// 非匿名関数
var f = a => console.log(`a = ${a}, arguments[0] = ${arguments[0]}`);
f(2);
// log print: Uncaught ReferenceError: arguments is not defined
##四.題外話
ES6 アロー関数に関しては、上記のコンテンツで随心所欲に驾驭するのに十分です。下面我们扯点别的(面白いこと)
###1.JS 中でサポートするすべてのアロー
| アロー | 意味 |
|---|---|
| <!-- | 単行注釈 |
| --> | 行頭で単行注釈を表し、他の位置では「趋向于」を表す(n --> 0 は n-- > 0 と等価) |
| <= | 比較演算子、小于等于 |
| => | アロー関数 |
2 つの単行注釈構文を見て驚かないでください。歴史的原因ですが、現在すべてのブラウザがサポートしています。役に立ちません。冷知識でしょう
###2.lambda 演算と邱奇数
lambda 演算中で唯一の基礎データタイプは関数です。邱奇数(church numerals)は高階関数を使用して常見の基礎データタイプ(整数、ブール値、鍵値対、リストなど)を表します
自然数はすべて数字、邱奇数はすべて関数です。邱奇数の n は n 階関数で、f^n(inc, base) === f(inc, f(inc, ...f(inc, base))) です。すべての邱奇数は 2 つのパラメータを持つ関数です
どのように関数で自然数を表すか?コンテンツは比較的多いですが、ここに自然数集の小さい例を 1 つ与えます:
// 自然数集合を定義
var number = (function*(inc, base) {
var n = zero;
while(true) {
yield n(inc, base);
n = succ(n);
}
})(inc, 0);
for (var n of number) {
console.log(n); // 0, 1, 2, 3, 4
if (n > 3) {
break;
}
}
邱奇数で表した自然数集は以下の通り:
// 0, 1, 2
var zero = (inc, base) => base;
var one = (inc, base) => inc(base);
var two = (inc, base) => inc(inc(base));
// 後継関数を定義 f^n -> f^(n+1)
// succ = ln.lf.lx.f (n f x)
var succ = n => (inc, base) => inc(n(inc, base));
// 邱奇数集合を定義
var church = (function*() {
var fn = zero;
while(true) {
yield fn;
fn = succ(fn);
}
})();
// test
var [, , , three, four, five, six, seven] = church;
console.log(three(inc, 0)); // 3
console.log(four(inc, 0)); // 4
console.log(five(inc, 0)); // 5
console.log(six(inc, 0)); // 6
console.log(seven(inc, 0)); // 7
よく考えると世界は本当に奇妙だと発見します。これも可能??興味がある方は筆者の Demo を参照してください(減法、乗法と減法を実装しました)
###3.Y 組合せ子と関数型プログラミング
Y 組合せ子は匿名再帰関数を実装できます
Y 組合せ子は 1 つの関数です。以下の通り:
var Y = F => G(G), var G = slef => F(self(self))
その中に不動点の概念があります。不動点:若 F(f) = f なら、f は不動点です。Y 組合せ子中で、G(G) は不動点です
仮に現在階乗を求める再帰関数があるとします:
var fact = n => n === 0 ? 1 : n * fact(n - 1);
これは明らかに 1 つの匿名再帰ではありません。fact は関数名で、それを再帰呼び出して階乗を計算します。ではどのように 1 つの匿名の再帰関数を実装するか?これは可能でしょうか?
Y 組合せ子で一発すればよいです。以下の通り:
// Y 組合せ子を定義
// var Y = F => G(G);
// var G = self => F(self(self));
var Y = F =>
((g => g(g))
(g =>
(F((...x) =>
g(g)(...x)))));
// 匿名再帰で階乗を求めるを実装
var yFact = Y(f =>
n => n === 0 ? 1 : n * f(n - 1));
console.log(yFact(5)); // 120
奇妙でしょう?関数型プログラミング中には各種の類似した奇妙な変換があります。FP の理解コスト、実行効率は問わず、これらの変換自体がいくつかの面白い研究する価値のあるものです。思考にもう少し空間を与え、cpu を走らせましょう
##五.まとめ
lambda 式の極限の簡潔さは魅力的で、関数を定義するのは数式を書くようなものです。関数型プログラミングをサポートする言語は本来こうあるべきです
参考資料
-
APIO 講稿——関数型プログラミング:byvoid 先輩の講稿、汗顔
-
『ES6 in Depth』:InfoQ 中文站が提供する無料電子書籍
コメントはまだありません