🗂

【初心者向け 098】JSの高段関数、spread 構文、短絡評価、非同期処理

2023/10/07に公開

高階関数 (higher-order function)

関数をパラメーターにしたり、関数をリターンする関数を高段関数といいます。
またパラメータになる関数をcall back関数といいます。

JSのArrayはIterate機能が内装され、for文、コードの長さを減らせるAPIが様々あります。

基本型


arrays.function(callbackFn(value,index,array));

forEach

const nums = [1,2,3,4];
for (let i = 0; i < nums.length; i++) {
  console.log(nums[i]);
}

nums.forEach(function(num) {
  console.log(num);
});

//nums.forEach((num)=>{console.log(num)});

filter()

条件に合う要素を選び、新しい配列を作成

const seasons = ['春','夏','秋','冬','夏'];
const result = seasons.filter((item) => item.name === '夏');
console.log('result :' result);
result : ['夏'、'夏']

map()

条件に合う要素を選び、既存要素の値を操作して新しい配列を作成

const nums = [1, 2, 3, 4, 5];

const result = nums.map((num) => {
  if (num % 2 == 1) {
    return num * 2;
  } else {
    return num;
  }
});
console.log(result);
[2,2,6,4,10]

sort()

条件にそって、既存の配列を文字列に整列、numberの場合が追加コードが必要です。
defaultは昇順です。

const nums = [1,5,4,2,3];

const result1 = nums.sort((a,b) => a-b);
const result2 = nums.sort((a,b) => b-a);

[1,2,3,4,5]
[5,4,3,2,1]

reduce()

配列の値を一つにします。

const nums = [1,2,3,4,5];

const result1 = nums.reduce((sum,num)=>{
  sum+=num
  return sum;
  }
},0)

//nums.reduce((sum,num)=>((sum+=num),0);

spread(...), destructure(分割代入)

spread

すべてのIterableはspread演算子を活用することができます。
spread(...)は一つずつパラメータを入れるのではなく、...を付けることでパラメータの数を可変になり、より柔軟になります。

function add(f,s,...nums){
  console.log(nums);
}

add(1,2,3,4,5,6);
[3,4,5,6]
const nums1 = [1,2]
const nums2 = [5,6]
let arr = [...num1,3,4,...num2];
console.log(arr);
[1,2,3,4,5,6]

このようにSetを...spread 構文を活用して、重複を除去した
配列に作成することもできます。

const fruits = ['🍌', '🍎', '🍇', '🍌', '🍎', '🍑'];
//  ['🍌', '🍎', '🍇', '🍑']

console.log([...new Set(fruits)]);

destructure(分割代入)

const point = [5,10];
const arr = point;
arr[0];
arr[1];

本来ならこのようにindex番号で配列の要素を参照しますが、indexに名前を付け意味があることにすることができます。

const point = [5,10]
const [x,y] = point;
x; //5
y; //10

また、分割代入をという名前どおり、データーをグループ化することもできます。

const arr = [1,2,a,b,c];
const [first,second,...alphabets] = arr;
console.log(first);
console.log(second);
console.log(others);

このように、関数に適用することも可能です。

const ironman = { name: 'Tony', age: 50, job: 'ceo' };
function display({ name, age, job }) {
  console.log('이름', name);
  console.log('나이', age);
  console.log('직업', job);
}
display(ironman);
이름 Tony
나이 50
직업 ceo

const { name, age, job, home = 'new york' } = ironman;
console.log(name);
console.log(age);
console.log(job);
console.log(home);
Tony
50
ceo
new york

Symbol()

const korean = 'yuri';
const japanese = 'yuri';
console.log(korean === japanese) //true
const korean = Symbol('yuri');
const japanese = Symbol('yuri');
console.log(korean === japanese) //false

Symbolはマップと使われう場合が多く、唯一な値を与えます。
上の場合、call by valueにより、同じ値を持っていることになり、
trueになりますが、Symbolの場合、両方'yuri'という値を持っていますが、falseになります。yuriという値をもっていますが、korean 유리 とjapaneseのゆりは違いますね。

具体的な原理はよくわかりませんが、おそらくjavaのハッシュコードのように 同じ値であれ、結局変数korean,japaneseは値のみ同じである違うお皿ですね。
Symbolによってこのような論理的な同等性ではなく、物理的な同等性を区別するものではないかと思います。
同じyuriですが、韓国人のyuri , 日本人のyuriで同じyuriでも異なることになります。

万が一、韓国人であるが、日本人であろうかyuriはyuriだろう!
そう思う人もあるはずです!
その時はSymbol.for()メソッドを活用しましょう!

const korean = Symbol.for('yuri');
const japanese = Symbol.for('yuri');
console.log(korean === japanese) //true

このように同じ名前でSymbolの値を利用したい場合、Symbol.for()を活用すれば、Global Symbol Registryに登録され、グロバール変数のように使うことができ、Symbol.keyForメソッドで確認することもできます。

★短絡評価★

&&

if文の中、Booleanタイプではなく、値を割り当てる場合は前の値がTruthy なら後ろがfalseがどうであるが評価せずに次の変数の値を割り当てます。つまり評価が省略されます。既存の&&とはまったく反対です。

つまり、Truthy、falsyが基準です!!!

これ、、、Javaから勉強した自分としてはあり得なかったですね、、

let a = 1;
let b = 0;
let result = a&&b;   //truthy & falsy 
console.log(result);
0

null or undefinedを確認

let cat; //{name:kitty};
const name = cat.name
console.log(name);

この場合、nameはundefinedになっており、アクセスしようとしたので、node.jsから実行すれば、app crashedでサーバーが死にます

let cat; //{name:kitty};
const name = cat && cat.name
console.log(name);
undefined

しかし、&&の原理を利用し、「前のやつがtruthyなら && ここを割り当てる/呼び出す」ができず、前のfalsyなら、
アクセスのではなく、undefinedをそのまま値として投げます。

let cat = {name:kitty};
const name = cat && cat.name
console.log(name);
kitty

catがtruthyなら「前のやつがtruthyなら && ここを割り当てる/呼び出す」の原理によって、後ろのcat.nameの値を代入します。

falthy処理

今回はfalthyの原理によって、default parameterの代わりに、falthyを処理します。

//기본값을 설정
function nanpa(message) {
  const text = message || 'shut up';
  console.log(text);
}
nanpa();
'shut up'

「|| 前のやつがfalsyなら || ここを割り当てる/呼び出す」原理により、'shut up'を割り当てます。

Optional Chaning

let john = { age: 15 };
const age = john && john.age;
console.log(age);

let john2 = { age: 15 };
const age2 = john2?.age;
console.log(age);

先ほど&&で処理した値を?.という演算子で処理することも可能です。
仮に関数の場合はパラメータにundefinedを入れられる可能性が高いですね。

let john3 = { age: 15, game:{name:fifa23} };

function getName(obj) {
  const gameTitle = obj && obj.game && obj.game.name;
  console.log(gameTitle);
}
getName(john3);

objもobj.gameもtruthyならobj.game.nameを割り当てます。

fifa23

しかし、コードも長くなり、何かのミスで

let john3 = { age: 15, game:{name:fifa23} };

function getName(obj) {
  const gameTitle =obj.game.name;
  console.log(gameTitle);
}
getName();

この場合はundefinedにアクセスので、またプログラムが中止されます。

let john3 = { age: 15, game:{name:fifa23} };

function getName(obj) {
  const gameTitle = obj?.obj.game?.obj.game.name;
  console.log(gameTitle);
}
getName(john3);

&&をより簡単に確認することができます。

??(nullとundifinedのみ処理)

let point = 0;
console.log(point || 10);

この場合、pointである0はfalsyになり、意味のある数字なのにdefault valueに処理されます。

let point = 0;
console.log(point ?? 10);
0

??でこのようなケースを処理することができます。

非同期処理

JavaScriptの場合、基本的にSing context thread言語ですので、一つの作業しか処理できないのですが、WEB BrowserのAPIなどから提供する非同期処理関数を作成することができます。

setTimeout(callback関数, ms)も代表的な非同期処理関数です。

https://velog.io/@titu/JavaScript-Task-Queue말고-다른-큐가-더-있다고-MicroTask-Queue-Animation-Frames-Render-Queue

このような仕組みで、stackメモリが暇な時、EventLoopがTask Queにあるcall back関数を持ってきます。

Promise(同期を非同期に)

非同期作業を処理するオブジェクトとして、Call back hellといういわゆる複雑なcall back関数のあつまりを簡単により分かりやすく処理することができます。
Promiseのパラメータにはresolve, reject関数をマラメターにするcall back関数になります。

resolve: Promiseオブジェクト内で成功時に行われる関数を意味します。
then: resolve関数(成功時)の後、呼び出される関数内容です。

reject: Promiseオブジェクト内で失敗時に処理される領域で、Errorを投げたりすることが可能です。
catch: reject関数時、実行された後、関数表現式で呼び出すことができます。

function timeDelay(seconds) {
  return new Promise((resolve, reject) => {
    if (!seconds || seconds < 0) {
      reject(new Error('seconds is weird'));
    }
    setTimeout(resolve, seconds * 1000);
  });
}

runInDelay(2)
  .then(() => console.log('time delay success!'))
  .catch(console.error)
  .finally(() => console.log('finish!!'));

成功したらresolveの後、thenが、失敗したらrejectの後、catchが行われ、最後には成功しても、失敗してもfinallyを行う。

https://kkangdda.tistory.com/77

こちらは韓国のサイトですが、Promise、MacroTaskQue,MicroTaskQueなどがちゃんと整理されています!

setTimeoutはMacroTaskQueです!

Promise chaining

先ほどの方法はPromiseのコンストラクタから関数を入れて、resolveならthenに、rejectならcatchにPromiseオブジェクトと関数をリターンする形ですが、そもそもPromiseにアクセスしたり、あまりにも抽象的で理解することが難しいと思います。

幸い、Promiseにはresolve()とreject()というstatic methodがあるので、より直観的に理解することができます。

function countOne(){
   return Promise.resolve(`zero => one`);
} 

function countTwo(num){
  new Promise.resolve(`${num}=>two`);
}

function countThree(num){
  new Promise.resolve(`${num}=>three`);
}


countOne()
.then((one) => return countTwo(one))
.then((two) => return countThree(two))
.then((three) => console.log(three));

one
two
three

コードは以下のようにrefactoringすることができます。

Before

countOne()
.then((one) => return countTwo(one))
.then((two) => return countThree(two))
.then((three) => console.log(three));

>callback関数に入れる(one),(two),(three)パラメータと呼び出す関数に入れるパラメターがが同じなら、パラメータ省略することができます。

After

countOne()
.then(return countTwo)
.then(return countThree)
.then(console.log;

return文のブルックから一つの命令文のみある場合はreturnを省略することができます。

finish

countOne()
.then(countTwo)
.then(countThree)
.then(console.log);

Discussion