Javascript 関数 各種関数一覧、普通関数・classとの違いも

普通関数、無名関数、アロー関数、即時関数、コンストラクタ関数、再帰関数、純粋関数、高階関数、クロージャ―関数など、多様な関数の形式や動作の違いがあるので、後から参照できるように全て1ページにまとめました。 (更に追加:静的メソッド、ジェネレータ関数、非同期関数)

関数の基本形(普通関数)

  • function 名前(){コード}で宣言する。
  • 最後の}の後ろに『;』は不要。
  • return xxxx;には『;』が必要。
  • Javascriptの関数の宣言は、下で説明している無名関数でなければ、呼び出し場所が上でも下でも構わない。(『巻き上げ:winding』と呼ぶ。)
function 関数名(引数1,引数2,...) {
   // 関数内の記述
  return 結果`;
}

function hello(a) {
  return `Hello ${a}`;
}

無名関数

  • 通常関数にはfunctionの後に名前があるが、名前はなく、変数(myfunc)に関数を代入して使う。(Javascriptは変数に関数も代入できる。)
  • 通常の関数と異なるのは、使用するより上に書かなければならない事。
console.log(myfunc(123,3));  // 宣言より前でも構わない。
//この下が無名関数
const myfunc = function (a,b) {
  return a*b;
}
console.log(myfunc(123,3));  // 宣言より後でも構わない。

無名関数の他の代入例

別の関数の引数として記述できる。 イベントリスナーに無名関数を使用できる。

window.onload = function() {
    console.log('ロードされました!');
  };

アロー関数

  • 『function』が無くなって『=>』が付く書き方がシンプルな形。
  • 無名関数、即時関数、Callback関数の時によく使用される。
  • 引数が1つの時、()を省略できる。
  • 1行の時は{}もreturnを省略できる。
  • 本体内でyieldを使用できない。
  • function()関数との違いは色々あるので、要確認。(thisの違い、コンストラクタにならない、argumentsを持たない等他)
// (引数1,引数2,...) => {
//     関数内の記述
//     return 結果`;
// }

(a,b)=> {
  return a*b;
}

//アロー関数を使った無名関数。
const myfunc = (a,b) => {
  return a*b;
}
//アロー関数を使った即時関数。
((a,b)=> {
  console.log(`結果: ${a*b}`);
})(100,200);
//引数が1個の時、()を省略できる。。
const myfunc = name => {
  console.log(`Hellow ${name}`);
}
//1行の時は{}もreturnを省略できる。
const myfunc = (a,b) => a*b;
//これを利用してReact ComponentでJSXの場合
const myfunc = (a,b) => <h1>Hello, world</h1>;

コンストラクタ関数

  • 元々、JavascriptはHTMLの補完となる簡単なフロントエンドの言語だったのでclassがなく、そのclassの代替えとして関数を継承するために出来たのが、コンストラクタ関数だと思います。
  • 仕様はコンストラクタ関数も普通の関数同じ。
  • 違いはthis.プロパティ名で新しいインスタンスに実体プロパティを構築するように記述した関数かどうか。 (constructor:構築する人・モノ)
  • this.プロパティ名があると、newを実行した時に、新しいインスタンス(コンストラクタ関数から継承したオブジェクト)に、参照でなく新しいインスタンス内に実体プロパティを構築する。(参照は参照元の値を変更すると、その変更が反映されてしまう。)通常、普通の関数は実行が完了したら内部のメモリを破棄するが、このインスタンスの実体プロパティは実行が終わっても値を保持しており、更新もでき、その値を後実行する際に使用できる。 通信レスポンス等、時間差がある場合に便利。
  • newは更に、新しいインスタンスに、prototypeプロパティに定義しているメソッドやプロパティへの参照リンクを構築する。
  • Javascriptの関数は変数に入れると、変数に入れた時点での値は保持するが、後程変更する事はできないが、コンストラクタ関数で生成したインスタンスはプロパティをいつでも変更する事ができ、その変更した値で再度実行する事が出来る。
  • 私見ですが、classを使った方が動作も同じで書き方も簡単で、superも使えるので便利ですが、過去のコードを読み解く際には、コンストラクタ関数も知っておいた方がベターと思い調べました。
const func1 =function(name,kind){
  this.name = name;    //this.nameでnameプロパティを定義している、コンストラクタ関数
  this.kind=kind;
  console.log('wow1');
}
const func2 =function(name,kind){
                     // thisが無い普通の関数
  console.log('wow2');
}

//コンストラクタ関数と普通の関数にもprototypeプロパティにfunc3メソッドを定義。
func1.prototype.func3=function(){console.log(this.name)}; 
func2.prototype.func3=function(obj){console.log(obj.name)};

const func_c = new func1('apple','fruits'); //newが初期値としてfunc_c.name='apple', func_c.type='fruits'を構築する。
                                            //newがfunc_cにfunc3の参照リンクが[[Prototype]]を構築する。
const func_d = new func2('orange','fruits'); //newでthisが無いので何も構築しない。
                                            //newがfunc_eにfunc3の参照リンクが[[Prototype]]は構築する。

console.log(func_c.name);   //-> apple
console.log(func_d.name);   //-> undefined   func_d.nameは構築されていない為。
func_c.func3();             //-> apple    内部に保持したfunc_c.name。
func_d.func3();             //-> TypeError: Cannot read properties of undefined (reading 'name')
func_d.func3({name:'cherry',kind:'food'}); //-> cherry  外から値を渡せば動作できるが、内部に値を保持できない。

即時関数

  • 無名関数を()の中に入れた関数。
  • 関数内の変数は関数外で使用できないので、スコープを限定したい場合よい。
  • 即時関数の場合は末尾に『;』が必要。
(function (a,b) {
  console.log(`結果: ${a*b}`);
})(100,200);
//-> 結果: 20000

再帰関数 (recursive function)

関数内から自分自身の関数を呼び出す関数。 無限ループとなりやすいので自分自身で関数を終了させる等の対策が必要となる。 特に以下2点を必ず確認する事。

  • 停止条件を必ず入れる事。
  • 停止条件になるよう毎ループ状態が変化する事。
  • 関数である自分自身を呼び出す度に関数が持つコンテキストのスタックが積みあがるので、returnの自分自身を呼び出すのは2回や3回等の複数呼び出すのでなく、1回にするよう引数で工夫する事。
  • コードを短く書けたりするが、無限ループになりやすく危険なので使用には要注意。
const fibonacchi= (n, a0 = 0, a1 = 1) => {
  if (n === 1) return a1;    // 停止条件
  return fibonacchi(n-1, a1, a0 + a1);  // n-1 でループ状態が毎回変化する
}
console.log(fibonacchi(20));  //-> 6765

純粋関数

  • グローバル変数を使用せずに、引数だけを使い出力の為だけの処理をして、
  • 引数が同じ場合、返り値は同じになる関数の事。(参照透過性)
    ランダムな値を返したり、WebAPIやDBにアクセスしている場合は同じ値を返さないので純粋関数でない。 (その他、ネットワークリクエスト、ファイル書込み、グローバル変数変更等も純粋関数ではない。) WebAPIやDBやネットワークリクエストやファイル書込みは、関数の外部で何らかの処理が実行されていて、同じ引数から異なる結果が返る。
  • 引数等を変化させない。(副作用が無い)
    オブジェクトが引数で参照渡しになっている場合は、元のオブジェクトの値も変化させてしまう為、純粋関数でない。 

純粋関数はバグが少なく、デバッグがしやすいので、出来るだけ純粋関数となるように心がける。

また純粋関数の場合、メモ化で高速化が図れる。

メモ化 (cacheして高速化)

同じ引数なら同じ結果を返す純粋関数の場合、時間がかかる処理の場合、値が同じなら前回の結果をキャッシュした内容を返す事により高速化する。

function measure(callback, loop=1, ...arg) {
  const start = performance.now();
  for (let i=0; i<loop; i++) {
     callback(...arg);
  }
  result =(performance.now()-start).toPrecision(5);
  console.log('time:', result,'ms');
  return result;
}

const a = num => {
  if (!a.memo) {
    a.memo = {}
  }
  if (num in a.memo ===true) {
    return a.memo[num];
  } else {
    for (let i = 0; i < 1_000_000_000; i++) {   // 10億回計算
      a.memo[num] = num*1000/500*2000/4000;
    }
    return a.memo[num];
  }
}

function myFunc(c){
  console.log(a(c));
}
measure(myFunc,1,1);
measure(myFunc,1,2);
measure(myFunc,1,3);
measure(myFunc,1,2);
//結果
1
time: 820.45 ms
2
time: 892.47 ms
3
time: 358.66 ms
2
time: 0.71360 ms   // cacheしたので上で同じ様に2を入れた時に892.47msかかっていたのが短くなった

副作用 (=外部への影響)

  • returnで値を返す以外の関係ない操作は全て副作用。
  • またJavaScriptの場合、プリミティブ以外の値は、値でなく参照が渡される為、以下のように引数がオブジェクトであれば、グローバルのオリジナルも書き換えてしまう。
function func1(obj) {
  obj.name = 'ichiri'
  return obj
}

let a = { name: 1 };
console.log(1,a);

let b = func1(a);
console.log(2,a);
console.log(3,b);
//結果
1 { name: 1 }
2 { name: 'ichiri' }
3 { name: 'ichiri' }

でも別のオブジェクトを用意すると副作用無しになる。

function func1(obj) {
  let obj2 = Object.assign({},obj);
  obj2.name = 'ichiri'
  return obj2
}

let a = { name: 1 };
console.log(1,a);

let b = func1(a);
console.log(2,a);
console.log(3,b);
//結果
1 { name: 1 }
2 { name: 1 }
3 { name: 'ichiri' }

高階関数

関数の引数に別の関数が入っている関数の事。 ある処理で暗号化する場合、異なる暗号化関数を引数に渡して処理をする場合など。(forEach()、reduce()、map()、filter()、sort()も引数にcallbackがあり高階関数。)

const crypto    = require('crypto');

function getHash(str,func){
  return func(str)
}

const encrypt = (str, enc='sha256') => {
  const hash = crypto.createHash(enc);
  hash.update(str);
  return hash.digest('hex')
}

console.log(getHash('ichiri',encrypt));
//-> e774172c0c0027556f7cce3fa2a8378c26086f964bc50066a30375ea439b9d7d

クロージャ―関数

  • closure(閉鎖、囲まれた):内部に閉鎖 (closeの名詞) されたと言う意味。
  • 変数を関数の中に閉じ込めて、関数内では使えるが、外から読み書きできなくして保護するもの。
  • ECサイトの買い物かご情報などをオブジェクト毎に持たせたりする場合や、
  • 大きなプロジェクトで他の人のプログラムから書き換えられないようにする場合等に便利。
  • 下のコードの例では、func2に()=>{ }の部分とa=10として閉じ込められる。
  • func2()を実行したら、閉じ込められた()=>{ }を、a=10から1ずつ増やしていく。
  • func2内の()=>{ }とaは既にfunc1から別物としてfunc2内に存在してる。 このaは関数(クロージャ―)の中に閉じ込められてた変数。 
  • aは、classのプロパティの様に値を保持するが、読み書きできないところがclassのプロパティと異なる。
function func1() {
  var a = 10;
  return ()=>{
      a += 1;
      console.log(a);
  };
};

const func2 = func1();
func2();  //-> 11
func2();  //-> 12
func2();  //-> 13

コールバック関数

  • 引数で渡して他の関数で実行して貰う関数の事。
  • 下の例では、cal1とcal2はfunc1で実行して貰っている。
  • コールバック関数のメリットは、func1を作る場合にどんな計算をしなければならないか決めなくても良い。 もし2つの異なる計算を呼ぶ場合、func1とfunc2と2度記述するか、ややこしい分岐で実行しないといけない。 コールバック関数だと、そのどちらも不要。
const func1=function(a, b, callback){
  let c = callback(a,b);
  console.log(c);
}

function cal1(a,b){
  return a * b;
}
function cal2(a,b){
  return a + b;
}

func1(10,20,cal1); //-> 200
func1(10,20,cal2); //-> 30r

タイマーもコールバック関数を使っている

  • タイマー関数の第一引数はコールバック関数。
  • 時間の前にカンマがあるのは、第一引数がコールバック関数で時間は第二引数なので、間の区切りでカンマがある。(私は長らく不思議に思っていました。)
  • まずらわしかったのは、第一引数がコールバック関数を直書きしてあるから、コールバック関数に思えなかったからでした。
//setTimeout(callback, time);
setTimeout(() => {console.log('Done');}, 500);
//上記は以下と同じ
const func1 = () => {console.log('Done');}
setTimeout(func1, 500);

ジェネレータ関数 function*

  • function*はGeneratorオブジェクトを返すジェネレータ関数。
  • ジェネレータ関数はPromiseと組み合わせると非同期プログラミングの強力なツールとなる。
  • next()でvalueとdoneのプロパティを含むオブジェクトを返す。(IteratorResultオブジェクト)
  • yieldキーワードの左に書かれた値をvalueに入れて、done:falseにして返す。
  • yieldキーワードの数だけnext()で値(value)を取り出せる。 下の例 g-1ではyieldキーワードは4個なので、next()4回目まで値(value)を取る出せる。 next()5回目以降は、valueはundefinedとなり、done(完了)がtrueとなる。
  • クロージャ―関数同様、まずconst gen = gFunc(1); でiに1を入れてジェネレータオブジェクトを作っておく。
  • gen.next().valueとすると値だけ取り出せる。
例 g-1
function* gFunc(i) {
  yield i;
  yield i + 10;
  yield i - 1;
  console.log("stop,",i);
  yield i + 3;
}
const gen = gFunc(1);
console.log(gen);
for (let i = 0; i < 10; i++) {
  console.log(i,gen.next());
}
  結果  
Object [Generator] {}
0 { value: 1, done: false }
1 { value: 11, done: false }
2 { value: 0, done: false }
stop, 1
3 { value: 4, done: false }
4 { value: undefined, done: true }
5 { value: undefined, done: true }
6 { value: undefined, done: true }
7 { value: undefined, done: true }
8 { value: undefined, done: true }
function* gFunc2(i) {
  while (i < 5) {
    yield i;
    i++;
  }
}

const iter = gFunc2(0);
for (let i = 0; i < 10; i++) {
  console.log(iter.next());
}
  結果  
{ value: 0, done: false }
{ value: 1, done: false }
{ value: 2, done: false }
{ value: 3, done: false }
{ value: 4, done: false }
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
{ value: undefined, done: true }
function* gFunc2(i) {
  while (i < 5) {
    yield i;
    i++;
  }
}

const iter2 = gFunc2(-2);
for (const val of iter2){
  console.log(9,val);
}
for (const val of iter2){
  console.log(8,val);
}
  結果  
9 -2
9 -1
9 0
9 1
9 2
9 3
9 4
  • for of はdoneがtrueになるまで実行して、valueだけ抜き出す。
  • 一度doneがtrueになると、二度目のループでは呼び出せない。

next()に引数を入れて渡す方法

  • step = yieldとすると、next()の引数に入れた値がstepに渡される。
  • しかしこの値が使えるのは、次のnext()時。
function* gFunc(...arg) {
  let step;
  let [i,j] =arg;
  yield i;
  yield i + 10 + j;
  step = yield ++i;
  if(step) {
    i+=step;
  }
  yield i + 3;
  yield step;
  console.log("end",i, step);   //iもjもstepも覚えている
}
const gen = gFunc(1,2);
for (let i = 0; i < 6; i++) {
  console.log(i,gen.next(100));
}
  結果  
0 { value: 1, done: false }
1 { value: 13, done: false }
2 { value: 2, done: false }
3 { value: 105, done: false }
4 { value: 100, done: false }
end 102 2 100
5 { value: undefined, done: true }

whileでカウンターに

  • startの値も覚えているのでインクリメントできる。
function* gFunc(start, end) {
  while (start < end) {
    yield ++start;  //start++にすると0から
  }
}
const gen = gFunc(0,10);

const t1 = setInterval(()=>{
  let a = gen.next();
  (a.done)? clearInterval(t1): console.log(a.value);
},500)
  結果  
1
2
3
4
5
6
7
8
9
10

yield*

  • イテラブルオブジェクトを順番に処理する。
function* gFunc() {
  yield* [1, "v", 3]; // 配列はイテラブル
  yield* "ichiri";     // 文字列はイテラブル
}

const gen = gFunc();
console.log(gen);
const t1 = setInterval(()=>{
  let a = gen.next();
  (a.done)? clearInterval(t1): console.log(a);
},500)
  結果  
{ value: 1, done: false }
{ value: 'v', done: false }
{ value: 3, done: false }
{ value: 'i', done: false }
{ value: 'c', done: false }
{ value: 'h', done: false }
{ value: 'i', done: false }
{ value: 'r', done: false }
{ value: 'i', done: false }

非同期関数 async function

  • JavaScriptはマルチスレッドも可能ですが、意図的にマルチスレッドを使用しなければ、基本的にシングルスレッドで実行されます。
  • しかしネットワークリクエストやファイル読書やタイマー処理など時間がかかる処理があります。
  • それらの処理が完了するまで次の行を実行出来なれば、全体のプログラム実行が遅くなります。 例えば、『マウスが動かないとか』、『画面表示が変わらない(動画が止まる)』等。
  • なので、時間がかかる処理は全て非同期関数や非同期APIと言われ、バックグラウンドで並列に処理され、その間JavaScriptは次の行の実行を進めます。 バックグラウンドでの処理が終わった順番にキューに入れられ(push)、イベントループで順番が来た時にキューから取り出され(pop)結果が実行されます。
  • async functionは非同期関数で、Promiseを返すPromise-baseの関数。
  • キューにはPromise-base用のマイクロタスクキューと古い非同期API用のタスクキューがあり、マイクロタスクキューの方が早く実行されるのと、Promise-baseは記述がcallback-hellにならないので、Denoの非同期処理はほとんどがPromise-baseとなっていて、NodeもPromise-baseへの移行を進めています。 例えば、XHRHttpRequestは古い非同期APIですが、axiosやfetchはPromise-baseになっています。
  • Promise-baseはECMAScript2015に出てきた比較的新しい定義ですが、今後は更にPromise-base化が進むと予想されます。
  • async functionはPromise-baseの非同期関数の定義で内部でawaitを使えます。
  • 下の例は古い非同期APIのsetTimeout()をPromise化(Promisification)してasync function内でawaitを使って順番に実行しています。
  • タイマーの場合、順序が違っていても問題にならないかもしれませんが、ファイル読み出し完了前やHTTP requestでデータ受信完了前に次の処理が実行されると、プログラムが正しく動作しません。 そんな時、Promise-baseでawaitを使って分かりやすく順序制御ができます。 awaitで待っている間はその関数は環境runtimeによってバックグラウンドで並列処理されているので、他の処理は止まりません。
function wait(time) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(`${time}ms`), time);
  });
};

async function myAsyncFunc(){
  const start = performance.now();
  await wait(300);
  let result =(performance.now()-start).toPrecision(5);
  console.log('async time#1:', result,'ms');
  await wait(50);
  result =(performance.now()-start).toPrecision(5);
  console.log('async time#2:', result,'ms');
  await wait(20);
  result =(performance.now()-start).toPrecision(5);
  console.log('async time#3:', result,'ms');
  await wait(200);
  result =(performance.now()-start).toPrecision(5);
  console.log('async time#4:', result,'ms');
  await wait(100);
  result =(performance.now()-start).toPrecision(5);
  console.log('async time#5:', result,'ms');
};

function myFunc(){
  const start = performance.now();
  let result;
  setTimeout(()=>{
    result =(performance.now()-start).toPrecision(5);
    console.log('time#1:', result,'ms');
  },300);
  setTimeout(()=>{
    result =(performance.now()-start).toPrecision(5);
    console.log('time#2:', result,'ms');
  },50);
  setTimeout(()=>{
    result =(performance.now()-start).toPrecision(5);
    console.log('time#3:', result,'ms');
  },20);
  setTimeout(()=>{
    result =(performance.now()-start).toPrecision(5);
    console.log('time#4:', result,'ms');
  },200);
  setTimeout(()=>{
    result =(performance.now()-start).toPrecision(5);
    console.log('time#5:', result,'ms');
  },100);
};

myAsyncFunc();
setTimeout( _ => myFunc() ,1000);
  結果  
async time#1: 310.41 ms
async time#2: 370.85 ms
async time#3: 401.80 ms
async time#4: 606.15 ms
async time#5: 715.50 ms
time#3: 34.875 ms
time#2: 51.233 ms
time#5: 114.44 ms
time#4: 208.14 ms
time#1: 301.02 ms

静的関数、静的メソッド

  • staticキーワードを使ってインスタンス化しなくてもclass名で直接呼び出せるメソッドを静的メソッドや静的関数と呼ぶ。
  • 普通のclassのメソッドはnewでインスタンスを作って、そのインスタンスで呼び出す。
  • 最初にnewかstaticメソッドかstaticプロパティが呼ばれたら、static { }内が実行される。
  • staticプロパティは静的プロパティと言われる。

class myClass {
  static sProp = 100;
  static sMethod() { return 'static method'}
  Prop = 200;
  Method() { return 'method'}
  static { console.log('Class static init block1') }
  static { console.log(this.sMethod(),"<<BLOCK2>>") }
}

console.log(myClass.sProp);
console.log(myClass.sMethod());

const a = new myClass();
console.log(a.Method());
  結果  
Class static init block1
static method <<BLOCK2>>
100
static method
method

オブジェクトにメソッド定義

const obj = {
  method1: function() { 
    // 基本形メソッド定義
  },
  func2: () => {
    // アロー関数メソッド定義
  },
  myFunc3() {
    // 短縮記法メソッド定義
  },
};

アロー関数と普通の関数の違い

this固定

通常function関数の場合は、新しいオブジェクトを生成するとき、このオブジェクトが新たな親オブジェクトになるのでfunction関数内の新しいthisを生成する。 アロー関数は、関数が生成された際に読み込んだ親オブジェクトをthisとして固定してしまう。 以下の例で、アロー関数は親オブジェクトのwindowオブジェクトに固定してしまうが、新しいオブジェクト生成の時読み込むfunction関数、新しオブジェクトを親オブジェクトとしてthisを持つ。

window.sample_item1='apple';
window.sample_item2=1;
const myFunc = function() {
    console.log('myFunc :',this.sample_item1);
    console.log('myFunc :',this.sample_item2);
};
const myArrow = ()=> {
    console.log('myArrow:',this.sample_item1);
    console.log('myArrow:',this.sample_item2);
};
//両方とも呼び出し元のオブジェクトであるwindowの値を参照している。
myFunc();                         //-> myFunc : apple
                                   //-> myFunc : 1
myArrow();                        //-> myArrow: apple
                                   //-> myArrow: 1
console.log(window.sample_item2); //-> 1
var obj1 ={
    sample_item2:100,
    func:myFunc
}
var obj2 ={
    sample_item2:100,
    func:myArrow
}
window.sample_item2=3;
console.log(window.sample_item2); //-> 3
//windowオブジェクトを参照しておらず、obj1で宣言した変数がthisに入っている。
//親のwindowオブジェクトに影響を与えず、別Objectから値を与えてfunctionを実行できる。
obj1.func();                      //-> undefined  sample_item1 は親obj1での宣言がない。
                                   //-> 100
//windowオブジェクトの値を参照していて、アロー関数内で使うthisには反映されていない。
obj2.func();                      //-> apple
                                   //-> 3
console.log(obj2.sample_item2);  //-> 100
  • アロー関数は直書きの場合(myArrow)もオブジェクト内アロー関数(obj2.func)もwindowオブジェクトをthisとして参照する。
  • function関数は、親オブジェクトをthisにする。 直書きmyFuncは、window.myFuncnの省略形でwindowオブジェクトが親オブジェクトでthisとなる。
  • しかしfunction関数がオブジェクト内だと、obj1.funcなのでobj1を親オブジェクトでthisとなる。 その為、obj1.funcの場合は、obj1.itemを変更するとobj1.itemを参照する。
//以下の結果はブラウザでnon-strictモードで実行の場合。
//nodeJSで実行の場合やstrictモードの場合は結果が異なる。
var item='apple';
console.log(window.item === item); //ブラウザで実行の場合はtrue

const myFunc = function() {
    console.log(this.item);
};
const myArrow = ()=> {
    console.log(this.item);
};
let obj1 = {
    item: 'ichiri',
    func: myFunc
  }
let obj2 = {
    item: 'Mike',
    func: myArrow
  }
myFunc();                       //-> apple
myArrow();                      //-> apple
obj1.func();                    //-> ichiri
obj2.func();                    //-> apple

item='banana';  
myFunc();                       //-> banana
myArrow();                      //-> banana
obj1.func();                    //-> ichiri
obj2.func();                    //-> banana
obj1.item='John'; 
obj2.item='Max'; 
obj1.func();                    //-> John
obj2.func();                    //-> banana
myFunc();                       //-> banana
myArrow();                      //-> banana

別の例 『Developer.mozilla.org』から引用しています。 分かりやすかったのでこれも記しておきます。

var obj = { // 新しいスコープを作成しない
    i: 10,
    b: () => console.log(this.i, this),
    c: function() {
      console.log(this.i, this);
    }
}
obj.b(); // prints undefined, Window {...} (or the global object)
obj.c(); // prints 10, Object {...}

call, apply, bindのthisの指定(無視される)

エラーにはならず無視される

コンストラクタを持たない (newができない)

  • コンストラクタはnewで作るが、アロー関数はコンストラクタを持たないのでTypeErrorとなる。
  • またnewが出来ないので.prototypeプロパティを持たない。
let foo = function(name, age){
  this.name = name;
  this.age = age;
};
let getInfo1 = new foo('Mike', 28);
console.log(getInfo1.name, getInfo1.age);   //-> Mike 28
let hoge = (name, age)=>{
  this.name = name;
  this.age = age;
};
let getInfo2 = new hoge('John', 32);       //->TypeError: hoge is not a constructor
console.log(getInfo2.name, getInfo2.age);  //->

argumentsを持たない

  • 普通の関数の場合、宣言した引数以上の数の引数が渡されたら、argumentsで読み込むことができるが、アロー関数の場合は使えない。
  • アロー関数内で、argumentsよ呼び出すと、アロー関数はargumentsを持っていないので引数を取り込まないが、関数外のargumentsを取ってくる。 タイミングによりargumentsは変化しているので、アロー関数内では不確かなargumentsは実際使用してはいけない。
function myFunc1(a, b, c) {
  console.log(a,b,c);
  console.log(arguments);
}
myFunc1(1, 2, 3, 4, 5,[1,2,3],{name:'ichiri'});  
//アロー関数の場合でなく、普通の関数の場合の例
//-> [arguments]{
  '0': 1,
  '1': 2,
  '2': 3,
  '3': 4,
  '4': 5,
  '5': [ 1, 2, 3 ],
  '6': { name: 'ichiri' }
}

default exportが一行で書けない

自分のLibraryを作成する場合、default exportを一行で書けるfunction宣言の方が便利。

default export function myFunc(){} // 一行で便利

メソッドチェーンが出来る関数やメソッドを作れない

アロー関数は、thisを持たないのでメソッドチェーンが出来る関数やメソッドには使用できない。

関数がclassのインスタンスの様に記憶される

  • 関数は実行したメモリを持たないので、後から実行した結果になるのが基本。
  • しかし、Javascriptは変数に関数を代入できるので、その時の必要な変数もそれぞれの変数内で記憶される。 これがJavascriptの大きな特徴。
  • 下の例では、変数a1にfunc1のreturnの関数と、{a:100}がクロージャ―として記憶される。 また、変数a2にfucn1のreturnの関数と、{a:50}がクロージャ―として記憶される。(下の、VScodeのキャプチャ参照)
let msg = "円です";
function func1( a ){
    return ( )=>{ console.log( a + msg ); };
}
let a1 = func1(100);
let a2 = func1(50); 
a1( );  //-> 100円です。
a2( );  //-> 50円です。

おまけ1 引数の初期値 2番目だけ変更したい時

  • 関数の引数をオブジェクトで定義。
  • 関数を呼ぶ際、オブジェクトで特定のキーに値を設定する。
function test( { a: a = 1 , b: b = 2 , c: c = 3 } ){  //引数をオブジェクトで定義。
    console.log(a, b, c);
}
test( { b:4 } );          //-> 1,4,3
test( { c:4, d:5. e:6 } ) //-> 1,2,4  キーに無いものは無視される。オブジェクトの必要な部分だけを渡すのに便利。

おまけ2 メソッドチェーンの作り方

  • メソッドにreturn this;を付けるだけ。
const obj = {
  value:10,
  func1: function(){
      this.value = this.value * 2;
      return this;              // メソッドチェーンで接続するにはthisを返す。
  },
  func2: function(){
      this.value = this.value * 3;
      return this.value;
  },
};

console.log(obj.func1(obj.value));                   //-> { value: 20, func1: [Function: func1], func2: [Function: func2] }
console.log(obj.func1(obj.value).value);             //-> 20     上の行の.valueが読み出される。
console.log(obj.func1(obj.value).func2());           //-> 60    
//func2()の引数は無くても、func1のreturn this;のthisが引き継がれる。 上の行の20がthisに入っているので、this.value*3で60となる。
console.log(obj.func1(obj.value).func1());           //-> { value: 40, func1: [Function: func1], func2: [Function: func2] }
console.log(obj.func1(obj.value).func1(obj.value));  //上と同じ結果

console.log(obj.func2(obj.value));                   //-> 30
console.log(obj.func2(obj.value).func1());           //-> TypeError: obj.func2(...).func1 is not a function
console.log(obj.func2(obj.value).func1(obj.value));  //-> TypeError: obj.func2(...).func1 is not a function
//func2はreturn this.value;なのでオブジェクトを返さない。 func1はobj.valueが必要なのでthisが必要。
// 以下の様にすると問題なく計算できる。
console.log(obj.func2(obj.value));                   // ->30
console.log(obj.func1(obj.value));                   // ->{ value: 60, func1: [Function: func1], func2: [Function: func2] }

コメント