知ってそうで知らなかったJavascript
最近いろいろvueとかreactとか触っているときにそもそも自分はJavascriptがわかっているのかと不安になったので、以下のチュートリアルをやってみた。なんとなくわかるけど、明確に言語化できていなかったところをまとめた。
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になっているらしい。
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_ORANGE
と colorOrange
で記法を分ける。
`` 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が足される。
演算子の優先順位
演算子には優先順位があり、それにしたがって演算される。
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