あなたの書いた

JavaScriptコードは

どのように動いているのか

2020/04/30

古川 亘 / HN: 古都こと

 

フロントエンドエンジニア

@株式会社トップゲート 大阪事業所

ゲーム / プログラミング

TypeScript / React / React Native / Rust / Go

趣味:

技術:

ブログやってます

 https://sbfl.net/blog

Twitter

今日話す内容

(function main(t) {
  [...t].forEach((d) => console.log(summarize(d.innerText)));
})(document.querySelectorAll('div'));

こういうわけわからんのを

倒す!!!!!

プログラムは

命令の列挙

const a = 1;
const b = 2;
const c = a + b;

console.log(c);

プログラムは

命令を並べただけ!

const a = new Array(5).fill(0).map((_, i) => i);

🙂

const a = 1 + 2;

🤨

🤪

const x = ((a) => (b) => a + b)(1)(2);

ほんとぉ?

もうちょっと

掘り下げていこう

式と文と評価

コードの解釈フェーズ

字句解析(Lexical Analysis)

構文解析(Syntax Analysis)

意味解析(Semantic Analysis)

細かい処理挟みつつ

バイトコードや機械語に

各フェーズの説明

  1. 字句解析(Lexical Analysis)
    • コードを記号や識別子などのトークンに分解する
  2. 構文解析(Syntax Analysis)
    • 得られたトークンの並びを文法的に解釈
  3. 意味解析(Semantic Analysis)
    • 文法は正しくても意味は正しいか?
    • そのキーワードはそこで使えるか?型は正しいか?

各フェーズの説明

  1. 字句解析(Lexical Analysis)
    • コードを記号や識別子などのトークンに分解する
  2. 構文解析(Syntax Analysis)
    • 得られたトークンの並びを文法的に解釈
  3. 意味解析(Semantic Analysis)
    • 文法は正しくても意味は正しいか?
    • そのキーワードはそこで使えるか?型は正しいか?

JavaScriptの構文

  • 「式(Expression)」「文(Statement)」
    • リテラル式、二項演算式、関数式、関数呼び出し式
    • 変数宣言文、関数宣言文、if文、for文、ブロック文
    • などなど
  • コードは「文」の組み合わせでできている
    • 「文」の中に「式」が含まれることがある
    • という認識で今のところはおk
    • この「文」が俗にいう「命令」というわけ
const a = 1 + 2;

console.log(a);

式と評価

  • 式は実行時に評価(evaluation)される
    • 「計算」と言い換えるとわかりやすいかも
  • 評価されると値になる
    • 逆に「評価されるものが式」という覚え方でも可
  • 評価の例
    • 「1 + 2」は「3」に評価される
    • 「console.log('Hi')」は「undefined」に評価される
      • ※戻り値がないため
const a = 1 + 2;
const a = 3;
console.log(a);
undefined;

※console.logに戻り値はないためundefinedになる

式と式文

  • コードは「文」の組み合わせ
  • でも例外的に「式」は「文」になれる
    • これを「式文」という
    • 逆(文 → 式)は基本的に無理
const a = 1 + 2;   // 代入文
1 + 2 + 3;         // 式だけど文として扱える
console.log('Hi'); // これも式だけど文

式の評価順序

  • 式の評価はひとつずつ順番に行われる
  • 「1 + 2 + 3」という式
    • まずは「3 + 3」に評価されてから「6」に評価
  • 「1 < x < 5」が動かない理由がこれ
    • 先に「1 < x」が評価されて「true < 5」になる
  • JavaScriptでは左から右、上から下の評価順番
    • 直感的でわかりやすいはず
    • このあたりは言語による

評価戦略

  • 正格評価(Strict Evaluation)
    • 式が現れた時点で評価する
    • 世のほとんどのプログラミング言語はこれ
    • JavaScriptも正格評価
  • 遅延評価(Lazy Evaluation)
    • 評価はできるだけ先延ばしにする
    • 評価しないと進行できなくなってから評価する
    • Haskellなどはこれ

コードを「式」で認識する

  • 式レベルでコードを読めるようになると世界が広がる
    • このコードはどう動いているのか?
    • 評価の順番は?
    • 副作用の影響は?
  • 新しい文法やAPIが導入されてもあまり混乱しなくなる
    • 結局のところ式か文になるので
  • どれだけ複雑なコードでも単純な式評価を繰り返すだけ

評価の例

const add = (a, b) => a + b;

console.log(add(1, add(2, 3)));
const add = (a, b) => a + b;

console.log(add(1, add(2, 3)));

四角で囲っているのが式

関数呼び出し式では

引数の式を左から順番に評価する

const add = (a, b) => a + b;

console.log(add(1, add(2, 3)));

今回はconsole.logの引数はひとつだけなので

add(1, add(2, 3)) を評価する

const add = (a, b) => a + b;

console.log(add(1, add(2, 3)));

addの引数は2つあるので左から評価する

1 → 1(リテラルなのでそのまま)

add(2, 3) → 評価する

const add = (a, b) => a + b;

console.log(add(1, add(2, 3)));

addの引数は2つあるので左から評価する

2 → 2(リテラルなのでそのまま)

3 → 3(リテラルなのでそのまま)

 add(2, 3)からadd(2, 3)が得られるので評価する

const add = (a, b) => a + b;

console.log(add(1, 5));

add(2, 3)を評価すると5になる

add(1, 5)が得られる

これを評価する

const add = (a, b) => a + b;

console.log(6);

add(1, 5)を評価して6が得られる

console.log(6)を評価する

const add = (a, b) => a + b;

undefined;

console.log(6)を評価する

コンソールに6が出力される

console.log自体は何も返さないのでundefinedに評価される

評価の例 その2

const x = ((a) => (b) => a + b)(1)(2);
const x = ((a) => (b) => a + b)(1)(2);

関数式

関数呼び出し式

const x = ((b) => 1 + b)(2);

関数式

関数呼び出し式

const x = ((a) => (b) => a + b)(1)(2);
const x = 3;

関数式

関数呼び出し式

const x = ((b) => 1 + b)(2);

まとめ

まとめ

  • コードは命令の列挙 → 文(or式文)の列挙
  • 「式」と「評価」という概念
    • 式は値に評価される
    • 基本的に左から順番にひとつずつ評価
  • 式レベルでコードを読めると理解が深まる
    • 難しいコードも読めるようになるかも
    • 「なんか知らんけど動いた」からの脱却
  • 式に目を向けてみよう!

参考文献

参考文献