知ってそうで知らなかったJavascript

最近いろいろvueとかreactとか触っているときにそもそも自分はJavascriptがわかっているのかと不安になったので、以下のチュートリアルをやってみた。なんとなくわかるけど、明確に言語化できていなかったところをまとめた。

javascript.info

Strict mode

what is strict mode

長い間Javascriptは、互換性の問題が発生することなしに、発展してきた。新しい機能・仕様が言語に追加されても、古い機能は変わらなかった。このメリットは、既存のコードを壊さないことである。だがデメリットとして、過去のJavascriptの開発者のミスが言語にずっと残ってしまうという問題があった。それを解決するのが、strict mode である。

2009年、ES5がリリースされた。そのとき新しい機能の追加や既存のコードの修正が行われた。それ以前のコードを動かすため、デフォルトだとES5リリース時の殆どの改善はオフになっている。したがって、ES5で改善された仕様を有効化するために、use strictを使う必要がある。

'use strict'
num = 5; // the variable "num" is created if didn't exist
alert(num); // 5

ok

'use strict'
num = 5; // the variable "num" is created if didn't exist
alert(num); // 5

エラーになる。

一言

どうせ今の時代( 2018/04 )ES6で書いてコンパイルするから過去のコードを読むために知っておく必要はあるが、use strictを使うことはないだろうと感じた。どうやら、ES6は最初からstrict modeになっているらしい。

stackoverflow.com

colorOrange or COLOR_ORANGE, 定数の命名におけるcamel caseとsnake caseの違い

snake case

const COLOR_RED = "#F00";
const COLOR_GREEN = "#0F0";
const COLOR_BLUE = "#00F";
const COLOR_ORANGE = "#FF7F00";


let color = COLOR_ORANGE;
alert(color); // #FF7F00

camel case

const pageLoadTime = /* time taken by a webpage to load */;

定数ということは値が決して変わらない(再代入されない)ということだ。だが、ものによっては実行まで値がわからないものがある。 したがって、実行前から値がわかっていて今後その値が変わらないものと、実行時に値が決まり今後その値が変わらないもので、COLOR_ORANGEcolorOrange で記法を分ける。

`` backtickについて

ES6以前は、文字列内で変数展開ができなかったので、以下のように書くことしかできなかった。

let hoge = "hoge"
'foo' + hoge + 'fuga'

だが、ES6になって、以下のように変数展開できるようになった。

`foo${hoge}fuga`

一言

知ってた。

null とundefinedとNaN

null

何もない、空という意味。"nothing", "empty" or "value unknown"

undefined

値が定義されていない。"value is not assigned"

もし変数が宣言されていて、何も値が代入されていなければ、その変数はundefinedになる。

let x;
alert(x); // shows "undefined"

NaN

計算のエラー。

alert( "not a number" / 2 + 5 ); // NaN

typeof

typeof演算子は、引数の型を返す。

typeof null // "object"

nullの型判定でobjectが返るのは、Javascriptの仕様のエラー。

Number(undefined) // NaN
Number(null) // 0
Number(true) // 1
Number(false) // 0
Number("hoge") // NaN
Number("") // 0

false, null, "" の場合のみ0, trueで1, "hoge", undefined だとNaNになる。

Boolean(0) // false
Boolean(null) // false
Boolean(undefined) // false
Boolean(NaN) // false
Boolean("") // false
// any other value becomes true

Booleanだと基本空っぽいもの( 0, "", null, undefined, NaN )は、falseになる。ただ、Boolean("0") とかだと文字列になるからtrueが返る。

null とundefined の、0との比較

nullとundefinedの比較

alert( null == undefined ); // true
alert( null === undefined ); // false

何にも型変換しないので

nullと0との比較

alert( null > 0 );  // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true

なぜ(1)(2)がfalseになっているのに、(3)だけtrueになっているのか

理由

== と、>, <, >=, <= は動きが違うから。>, <, >=, <= は、null を数字に変換して0にして比較するけど、== は型変換しないから。

undefined と0の比較

alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)

(1)と(2)がfalseなのは、undefinedがNaNに変換されるから。 そして、NaNはすべての比較演算子でfalseを返す。 最後にfalseになっているのは、== でundefinedとイコールになるのはnullだけだから。

文字列と数値の変換

alert( 1 + '2' ); // '12' (string to the right)
alert( '1' + 2 ); // '12' (string to the left)
alert(2 + 2 + '1' ); // "41" and not "221"
alert( 2 - '1' ); // 1
alert( '6' / '2' ); // 3

文字列の足し算をすると片方の値は文字列に変換される。 引き算割り算は数値計算になる。

counter++, と ++counterの違い

  • increment/decrement は、変数にのみ使える。したがって、5++ みたいなのはエラーになる。
let counter = 0;
counter++;
++counter;
alert( counter ); // 2, the lines above did the same
let counter = 0;
alert( ++counter ); // 1

先に1が足される

let counter = 0;
alert( counter++ ); // 0

次のループで1が足される。

演算子の優先順位

developer.mozilla.org

演算子には優先順位があり、それにしたがって演算される。

a = 1 + 2, 3 + 4, 5 + 6 // a = 3, 7, 11
alert(a) // 11

カンマで複数の式を評価できるが、結果として返るのは、最後の値。 カンマは、= より順位が低い。

比較演算子

alert('a' > 'A'); // true
alert( 'Z' > 'A' ); // true
alert( 'Glow' > 'Glee' ); // true
alert( 'Bee' > 'Be' ); // true

大文字のAは小文字のaとはイコールじゃない。aの方が後。理由は小文字は内部のencoding table(Unicode)では、大文字より後にあるから。

alert( '2' > 1 ); // true, string '2' becomes a number 2
alert( '01' == 1 ); // true, string '01' becomes a number 1
alert( true == 1 ); // true
alert( false == 0 ); // true

== と === の違い

let a = 0;
alert( Boolean(a) ); // false

let b = "0";
alert( Boolean(b) ); // true

alert(a == b); // true!

上記2つの比較はtrueになる。なぜなら文字列が0に変換されるから。 ただ下記はfalseになる。

alert(a === b); // false!

== は、型を変換した値同士の比較だけど、=== は、型を変換することなく比較をして真偽値を返す。

alert( 0 == false ); // true
alert( '' == false ); // true
alert( 0 === false ); // false, because the types are different

論理演算子

  • ORは、trueをさがしにいく
alert( true || true );   // true
alert( false || true );  // true
alert( true || false );  // true
alert( false || false ); // false
result = value1 || value2 || value3;

左から実行していき、それぞれの項が、booleanに変換される。もしtrueがあれば、booleanに変換前の値を返す。すべてfalseだったら、最後の項を返す。

let x;

true || (x = 1);

alert(x); // undefined, because (x = 1) not evaluated
let x;

false || (x = 1);

alert(x); // 1
  • ANDは、falseをさがしにいく
alert( true && true );   // true
alert( false && true );  // false
alert( true && false );  // false
alert( false && false ); // false
result = a && b;
result = value1 && value2 && value3;

左から実行していき、それぞれの項が、booleanに変換される。もしfalseがあれば、booleanに変換前の値を返す。すべてtrueだったら、最後の項を返す。

// if the first operand is truthy,
// AND returns the second operand:
alert( 1 && 0 ); // 0
alert( 1 && 5 ); // 5

// if the first operand is falsy,
// AND returns it. The second operand is ignored
alert( null && 5 ); // null
alert( 0 && "no matter what" ); // 0
  • ANDは、ORの前に実行される
alert( 5 || 1 && 0 ); // 5

1 && 0 -> 0を返す。 5 || 0 -> 5を返す。

loop

while for のcounter 変数のスコープ

ループ内がスコープ範囲。

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // error, no such variable
let i = 0;

for (i = 0; i < 3; i++) { // use an existing variable
  alert(i); // 0, 1, 2
}

alert(i); // 3, visible, because declared outside of the loop

break

最初で条件判定したいときは、while() 最後で条件判定したいときは、do while() 中間で条件判定したいときは、while(true) + break

let sum = 0;

while (true) {

  let value = +prompt("Enter a number", '');

  if (!value) break; // (*)

  sum += value;

}
alert( 'Sum: ' + sum );

continue

for (let i = 0; i < 10; i++) {

  // if true, skip the remaining part of the body
  if (i % 2 == 0) continue;

  alert(i); // 1, then 3, 5, 7, 9
}

Labels for break/continue

深いネストからbreakするのに使える

labelName: for (...) {
  ...
}

for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // what if I want to exit from here to Done (below)?

  }
}

alert('Done!');

単なるbreakだと一階層上にbreakするだけで、グローバルにはbreakできない。

outer: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // if an empty string or canceled, then break out of both loops
    if (!input) break outer; // (*)

    // do something with the value...
  }
}
alert('Done!');
  • continueも上記のような使い方が可能。
  • labelはgotoではないので、以下のような使い方はできない。
break label;  // jumps to label? No.

label: for (...)

function

function name(parameters, delimited, by, comma) {
  /* code */
}

関数

  • 関数は関数外の変数にアクセスできる。でも逆は無理。
  • 関数は値を返す。でも値がなかったら、undefined を返す。

default 値の設定

function showMessage(from, text = "no text given") {
  alert( from + ": " + text );
}

showMessage("Ann"); // Ann: no text given

デフォルト値のサポートは古いJavascriptではされていなかったので、以下のようなコードを使って再現していた。

function showMessage(from, text) {
  if (text === undefined) {
    text = 'no text given';
  }

  alert( from + ": " + text );
}
function showMessage(from, text) {
  // if text is falsy then text gets the "default" value
  text = text || 'no text given';
  ...
}

return

function doNothing() {
  return;
}

alert( doNothing() === undefined ); // true

何も返さない return は、return undefined と同じ。( undefinedを返してるのと同じ )

関数定義と関数表現

関数定義

function sayHi() {
  alert( "Hello" );
}

関数表現

let sayHi = function() {
  alert( "Hello" );
};
function sayHi() {
  alert( "Hello" );
}

alert( sayHi ); // shows the function code

上記を実行すると、

function sayHi() {
  alert( "Hello" );
}

が返る。

let sayHi = function() {
  alert( "Hello" );
};

alert( sayHi ); // shows the function code

上記を実行すると

function () {
  alert( "Hello" );
}

が返る。

sayHi というのは、() がついてないので、関数を実行したわけではない。多くのプログラミング言語では関数名で実行されるが、Javascriptはそうではない。

Javascriptでは、関数も値なので、値として対処しなければならない。

上記の例が示しているのは、sayHi という変数に、文字列でソースコードが代入されているということである。

sayHi() という風にすればコードを実行できる。ただそれでも値なので、以下のようなことが出来る。

function sayHi() {   // (1) create
  alert( "Hello" );
}

let func = sayHi;    // (2) copy

func(); // Hello     // (3) run the copy (it works)!
sayHi(); // Hello    //     this still works too (why wouldn't it)

上記で起こっていることとしては、まず関数が作られ、sayHiという変数に代入される。(2)で、sayHiという変数の中身(ソースコード)がfuncという変数にコピーされる。もし、func = sayHi() だったら sayHi() の実行結果が funcに代入される。sayHiのソースコードではなく。

また、これは以下のように書いても同様の動きをする。

let sayHi = function() { ... };
let func = sayHi;

;を関数定義につけなくていいのはなぜか?

function sayHi() {
  // ...
}

let sayHi = function() {
  // ...
};

if {}, for {}, と同様に、code blockとして、function sayHi() {} をあつかっているから。

関数表現の方は、コードブロックではなく、値を変数に入れている文なので ; が必要。

コールバック

値として関数が、使われる例。

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

function showOk() {
  alert( "You agreed." );
}

function showCancel() {
  alert( "You canceled the execution." );
}

// usage: functions showOk, showCancel are passed as arguments to ask
ask("Do you agree?", showOk, showCancel);

この例でいうところの ask がコールバック関数・コールバックと呼ばれるものである。

function ask(question, yes, no) {
  if (confirm(question)) yes()
  else no();
}

- function showOk() {
-   alert( "You agreed." );
- }

- function showCancel() {
-   alert( "You canceled the execution." );
- }

- // usage: functions showOk, showCancel are passed as arguments to ask
- ask("Do you agree?", showOk, showCancel);

+ ask(
+   "Do you agree?",
+   function() { alert("You agreed."); },
+   function() { alert("You canceled the execution."); }
+ );

上記のように短く書くこともできる。 ask(...) の中で呼ばれている関数は名前が無い、無名である。 これらの関数は ask(...) の外からはアクセスできない。なぜなら変数に代入されているわけではないから。

関数表現と関数宣言の違い

関数表現は、その関数が実行されて使用可能になるときに作られる。 それに対して関数宣言はすべてのscript/code blockから使用可能である。Javascriptはscriptを実行する前に関数宣言を探しに行き関数を作り、それからscriptを実行する。

sayHi("John"); // Hello, John

function sayHi(name) {
  alert( `Hello, ${name}` );
}
sayHi("John"); // error!

let sayHi = function(name) {  // (*) no magic any more
  alert( `Hello, ${name}` );
};

逆に言えば関数宣言は、そのblock内からじゃないと使用できない。

let age = 16; // take 16 as an example

if (age < 18) {
  welcome();               // \   (runs)
                           //  |
  function welcome() {     //  |
    alert("Hello!");       //  |  Function Declaration is available
  }                        //  |  everywhere in the block where it's declared
                           //  |
  welcome();               // /   (runs)

} else {

  function welcome() {     //  for age = 16, this "welcome" is never created
    alert("Greetings!");
  }
}

// Here we're out of curly braces,
// so we can not see Function Declarations made inside of them.

welcome(); // Error: welcome is not defined

それに対して関数表現だと、

let age = prompt("What is your age?", 18);

let welcome;

if (age < 18) {

  welcome = function() {
    alert("Hello!");
  };

} else {

  welcome = function() {
    alert("Greetings!");
  };

}

welcome(); // ok now

上記のように、変数に代入されているので、block外からも使用できる。

結局どっちを利用したらいいのか。

上記に合わせてケースバイケース

Arrow関数

  • (...args) => expression は、評価して結果を返す。
  • (...args) => {expression} は、bracketのおかげで複数の文を関数内に書くことができる。ただなんらか値を返す必要がある。
let func = (arg1, arg2, ...argN) => expression

は、以下と一緒

let func = function(arg1, arg2, ...argN) {
  return expression;
}

Reference from

https://javascript.info https://developer.mozilla.org/ja/docs/Web/JavaScript/Strict_mode