関係演算子
関係演算子とは、2つの値の関係を判定し、その判定結果によって真偽(trueまたはfalse)を返します。
関係演算子の返す値は論理値になります。
関係演算子の種類は「等値演算子」「不等演算子」「同値演算子」「比較演算子」「in演算子」「instanceof演算子」等があります。
詳しくは一つ一つブログ記事化していきます。
関係演算子とは、2つの値の関係を判定し、その判定結果によって真偽(trueまたはfalse)を返します。
関係演算子の返す値は論理値になります。
関係演算子の種類は「等値演算子」「不等演算子」「同値演算子」「比較演算子」「in演算子」「instanceof演算子」等があります。
詳しくは一つ一つブログ記事化していきます。
単項算術演算子は、一つのオペランドに対して処理を行う。
具体的には「+」「-」「++」(インクリメント)「–」(デクリメント)等があります。
全て右結合性があり、演算の優先順位が高く処理されます。
オペランドを数値に変換し、変換した値を返します。
オペランドを数値に変換します。
変換結果の符合を反転します。
オペランドをインクリメントします。
オペランドは「変数」「配列の要素」「オブジェクトプロパティ」等の左辺値である必要があります。
また、他の言語と同様に「前置」と「後置」により処理が変わります。
前置の場合は、オペランドをインクリメントし、インクリメント後の値が評価結果になります。
後置の場合は、オペランドをインクリメントし、インクリメント前の評価結果になります。
オペランドとして左辺値をとします。
オペランドを数値に変換し、数値から1を減算します。
インクリメントと同様に前置と後置による処理の違いがあります。
前回までの演算子で、四則演算の中で「+演算子」だけはオペランドによって動きが変わるという説明を書きました。
+演算子は、オペランドが数値の場合は加算し、文字列の場合は連結を行います。
簡単な例として
var test = 10 + 20; console.log(test);
上記の場合は、結果が30と出力され、
var test = "10" + "20"; console.log(test);
上記の場合は、1020と出力されます。
上記の場合の他に、片方のオペランドが文字列で、もう片方のオペランドが数値の場合は、数値のオペランドが文字列に型変換できるオペランドの場合は文字列に変換されて連結されます。
オペランドのどちらかがオブジェクトの場合、基本型値への変換アルゴリズムが使われ、基本型値に変換されます。
基本型へ変換された後、片方のオペランドが文字列の場合は、もう片方のオペランドも文字列へ変換され、連結されます。
それ以外の場合は、両方のオペランドを数値に変換して加算処理されます。
javascriptの算術演算子についてまとめます。
基本的な算術演算子は、乗算、除算、剰余、加算、減算です。
その他は単項演算子、ビット演算子があります。
乗算、除算、剰余、加算、減算の中で、加算演算子の「+」は動作が他の演算子とことなります。
演算時のオペランドの型が数値の場合は加算され、文字列の場合は結合されます。
その他の乗算、除算、剰余、減算については、オペランドを数値として変換し演算を行います。
オペランドが数値に変換できない場合に「NaN値」に変換され、演算結果がNaNになります。
除算演算子はオペランド同士を割ります。
オペランドA / オペランドB
の場合はオペランドAをオペランドBで割ります。
このときに結果は浮動小数点数になります。
オペランドBが、0の場合、エラーにはならず結果は無限大になります。
ブラウザのコンソールで試してみると
Infinity
という結果になります。
剰余演算子の場合は、除算演算子と近いですが、演算結果はオペランドAをオペランドBで割った余りが出力されます。
例えば次の例の場合
var test = 100; console.log(test % 30);
出力結果は「10」になります。
オペランドが浮動小数点数の場合でも、結果は小数点を含む演算結果になっります。
javacriptの演算子には結合性という重要な考え方があるので、
結合性について試してみます。
演算子には結合の方向があり、左から右に演算を結合する場合と、
反対に右から左に結合する場合あります。
また、同じ優先順位の演算子の場合は、実行順を決定する場合に結合性の順で演算がされます。
例えば減算する演算子「-」の場合、値が3つある場合を試してみます。
var test; test = 100 - 20 - 10; console.log(test);
上記の結果は、まず100-20が演算され、その後で80-10が演算されます。
これは演算子の結合性が「左から右に結合している」という仕様に則っている為です。
演算子には結合性の仕様により演算される方向があることに注意が必要です。
演算子の評価順については、常に左から右に行われます。
例えば式「test = 100 – 20 – 10;」がある場合、まず評価されるのはtestというオペランドで、
次に100、20、10の順序で評価されていきます。
上記の例は固定の数値ですが、100、20、10の各オペランドが変数や関数の場合は、左から順に右に評価されていきます。
上記の式の場合、右辺に対して括弧をつけることで、演算の順序を変更することは可能です。
演算の順を変えることができても、評価の順は変わらないことに注意が必要です。
javascriptの演算子は他のプログラム言語と同様、演算子に優先順位があります。
演算子の優先順位は優先度が高いものから演算され、優先度の低い演算子は最後に演算されます。
簡単な例を書いてみます。
var test = 10 + 20 * 30; console.log(test);
上記は結果が610になります。
演算子の優先順位が「+」よりも「*」のほうが高い為です。
これを調整するには「()」を使って優先順位を決めることができます。(括弧のほうが演算の優先順位が高くなる為です)
var test = (10 + 20) * 30; console.log(test);
上記は結果が900になります。
括弧をつけることで計算式の括弧内が先に演算され、30 * 30が後から演算される為です。
プロパティアクセスと呼び出し式は、全ての演算子より優先順位が高くなります。
演算子の優先順位をコントロールするには、括弧を使って意図した演算になるようにプログラムを書きます。
前回の投稿ではオペランドの数について書きました。
今回はオペランドの型についてまとめます。
オペランドは通常、特定の型を示していて、演算子もまた特定の型を返します。
javascriptの演算子はオペランドの型を解釈して演算します。
例えば
var test = "10" + "20"; console.log(test);
という書き方は、結果「1020」を返します。(ここでは数値として加算されないことを確認)
また、次のように書くと
var test = "50" * "60"; console.log(test);
結果は「3000」を返します。
同じ文字列のオペランド(の型)同士の演算でも、演算子によって型の解釈が決定づけられます。
では先ほどの例を変更して、次のように書いてみます。
var test = 30 + 40; console.log(test);
結果は「70」になります。
ダブルクォートで囲んでいないオペランドの為、数値として解釈され、なおかつ演算子は加算演算子として解釈されました。
一番最初の例では、ダブルクォートで囲んだ場合は文字列として解釈されるので、演算子としては文字列連結として演算されたことになります。
このようにjavascriptには、オペランドの型によって働きが変わる演算子があります。
「<」演算子などはオペランドの型によって、数値の大小比較をしたり、アルファベット順で比較したり、解釈が動的に変化します。
javascriptの演算子について、まとめます。
具体的な演算子の種類についてはリファレンスを確認することをおすすめします。
演算子はオペランドの数が重要になります。
オペランドとは、被演算子と呼ばれ、数式を構成する要素のうち、演算の対象となる値や変数、定数などを指します。
このオペランドの数が2つ必要な時、二項演算子と呼びます。
たとえば下記の加算演算子や乗算演算子の場合はオペランドが2つです。
console.log(10 + 10); console.log(20 * 20);
二項演算子とは別に、単項演算子と呼ぶ演算子もあります。
一つのオペランドのみで構成され、演算がされるものです。
例えば下記のようなものです。
var test = 10; console.log(++test); console.log(-test); console.log(!test); console.log(typeof(test)); delete test;
また、二項演算子、単項演算子の他に三項演算子もあります。
これは様々なプログラムのソースコードを見ていると出てくる場合があります。
演算子だけで書くと「? :」ということになりますが、少しわかりづらいので、簡単な例を書きます。(便宜上、コンソールログに出力しています)
var test = 15; //三項演算子 console.log(( test > 10 ) ? 'over' : 'under');
上記の場合はtestという変数の値を「?」の前の式で判定し、
真の場合は「:」の前のオペランド、
偽の場合は「:」の後のオペランドとして演算されます。
プログラマや開発プロジェクトによって好みが分かれる書き方ですが、1行でシンプルに書けるメリットがあります。
オブジェクト生成式とは、javascript内で新規のオブジェクトを生成して
オブジェクトのプロパティを初期化します。
具体的には次のように書きます。
new オブジェクト();
上記の「オブジェクト」と記載している箇所は生成したいオブジェクト名を書きます。
空のオブジェクトを生成してみます。
new Object();
これをコンソールログで出力すると、次のようになります。
console.log(new Object()); //出力結果 Object { }
もう一つ例を挙げて書いてみます。
例えばDateオブジェクトを生成する場合は、次のように書くこともできます。
new Date();
また、括弧を省いて書くことも可能です。
new Date;
こちらも同様にコンソールログに出力してみます。
console.log(new Date()); //上記の出力結果 Date 2018-08-17T16:16:52.405Z console.log(new Date); //上記の出力結果 Date 2018-08-17T16:16:52.405Z
どちらの書き方も同じオブジェクト生成がされている為、出力結果は同じになります。
オブジェクト生成時に生成したオブジェクトに関連するコンストラクタの挙動が重要になります。
コンストラクタについては別投稿として、改めて調べて書きます。
呼び出し式とは、あまり聞きなれない名前の式ですが、
javascript内で定義された関数を呼び出す時の式です。
具体的には次のようになります。
//Math.maxという関数を呼び出す Math.max(10, 20, 30); //自ら定義したsampleという関数を呼び出す sample(100); //テスト用配列を定義 var test = [200, 202, 201]; //配列に対しソート関数を呼び出す test.sort();
上記の例のように関数を呼び出す際の式のことを呼び出し式と呼びます。。
呼び出し式が評価される順番は、まず関数が評価され、次に引数式が評価されます。
関数式が呼び出し不可能な場合は例外になります。
呼び出し式の関数を自ら定義した場合は、return文を使うことで関数内部の値を返すことができます。
このreturn文で返された値が呼び出し式の値となります。
もし、定義した関数の中でreturn文を書かないで関数を呼んだ場合はundefinedになります。
//自ら定義したsampleという関数を呼び出す function sample2(a) { } sample2(100); //結果を確認する為、コンソールに出力する。結果は「undefined」になる console.log(sample2(100));
呼び出し式がプロパティアクセス式の場合はメソッドが呼び出されます。
メソッド呼び出しの場合、プロパティアクセスの対象となるオブジェクトや配列がthis引数の値になります。
thisの値は関数本体で利用が可能です。
javascriptのオブジェクトのプロパティにアクセスする方法について、試してみます。
オブジェクトのプロパティ値にアクセスする時、プロパテアクセス式が評価されます。
式が評価されるタイミングで、オブジェクトのプロパティの値か配列の要素として解釈されます。
具体的には「式.識別子」「式[式]」のように書きます。
「式.識別子」の場合は、式がオブジェクト、識別子がプロパティの名前です。
「式[式]」の場合は、式がオブジェクト(配列)になり、括弧で囲った式でプロパティや配列のインデックスを書きます。
実際に下記のようなコードを書いて試してみました。
//検証用オブジェクト1 var test = {a:100, b:200}; //オブジェクト内にオブジェクトがある例 var hoge = {c:300, d:{e:400, f:500}}; //オブジェクトプロパティ式 console.log(test.a); console.log(hoge.d.e); console.log(hoge.d.f); //プロパティaにアクセス(このような書き方も動作可能) console.log(test["a"]);
上記の場合、検証用オブジェクト1というオブジェクトを用意し、
そのオブジェクトに対し、プロパティにアクセスした結果を出力しています。
出力結果は次のようになります。
100 400 500
上記のようにプロパティアクセス式を使う場合は特に問題なく結果が出力されますが、
もしオブジェクトを定義していないものに対してプロパティアクセス式を使った場合、
例外エラーが発生します。
プロパティアクセス式は、まず式を見てから識別子を判別します。
逆に式となるオブジェクトが存在していて、アクセスする要素が無い場合も例外エラーが発生します。
オブジェクトとは別に配列にアクセスする場合も試してみます。
配列の要素にアクセスするには括弧を使ってアクセスします。
具体的には以下のように書きます。
//検証用オブジェクト2(配列の場合) var test2 = [10, 20, 30, 40, [50, 60]]; //配列の2番目の要素にアクセス console.log(test2[1]); //配列の5番目の要素の配列の2番目にアクセス console.log(test2[4][1]);
この場合の結果は次のようになります。
20 60
上記の結果のように、オブジェクト式の後に括弧が連続する場合は、2番目の式を評価して結果を出力します。
下記のように要素内に他の要素を持つオブジェクトに対してプロパティアクセス式を書いた場合について、試してみます。
下記のコードを書いて動作させたところ
//検証用オブジェクト1 var test = {a:100, b:200}; //検証用オブジェクト3(要素に他のオブジェクトを持つ場合) var test3 = [test, 20, 30, 40, [50, 60]]; //他のオブジェクトのプロパティにアクセス console.log(test3[0].b); //他のオブジェクトのプロパティにアクセス(括弧を使用) console.log(test3[0]["a"]);
出力結果は次のようになります。
200 100
正しくプロパティにアクセスできる場合に値が返り、結果が出力されます。
test3という変数(オブジェクト)に対して1つ目の要素に別オブジェクトがある場合、1つ目の括弧の後に括弧を書いて式を評価します。
上記のように、プロパティにアクセスする際、「.」でアクセスする方法と「[要素名]」でアクセスする方法の2つがあります。
二つのアクセス方法には、一見すると結果に違いはありませんが、
「.」識別子のほうはプロパティ名称が明確にわかっている場合に有効になります。
また、プロパティ名称が予約語の場合や、空白などの場合には「[]」の括弧を使ったプロパティアクセスをします。
javascriptの関数を定義する式のことを関数定義式と呼びます。
関数定義式も関数リテラルと呼ぶこともできます。
これまでに何度もサンプルコードを書いてきましたが、
具体的には、以下のコードのようになります。
var test = function(x) { return x * 10; }
上記の例は無名関数と呼ばれる関数( function(x) の部分)を定義し、変数のtestへ代入しています。
変数のtestは関数として動作します。
例えば test(10) というように呼び出せば100が返ってきます。
上記の関数定義式は一例にすぎず、プログラムの描き方によって関数定義も異なった形で記述します。
前回の投稿は、配列の初期化子について書きましたが、
同様にオブジェクト初期化について調べてみます。
配列の初期化子の書き方と、オブジェクト初期化子の書き方での違いは、
[]
の括弧を使う変わりに、次の中括弧を使います。
{}
例えば次のように書きます。
var test = {a:1, b:2} //呼び出す時は以下のように console.log(test.a);
オブジェクトリテラルは入れ子にして書くことも可能です。
オブジェクト初期化子の中に式がある場合、初期化子が評価されるタイミングで初期化子の中の式も評価されます。
オブジェクトリテラルのプロパティ名は文字列として解釈されます。
javascriptでインタプリタが評価して値を生成できるものを式になります。
式には多くの種類がありますが、最も単純な式を単項式と呼び、定数値(リテラル)、キーワード、変数参照があります。
リテラルは、以前の投稿にも記載したとおり、プログラム中に直接埋め込まれた定数値です。
具体的には「数値リテラル」「文字列リテラル」「正規表現リテラル」があります。
また、予約語「true」「false」「null」「this」も単項式として使えます。
javascriptを理解する上で重要な考え方としてスコープチェーンという考え方があります。
javascriptでの変数のスコープは、グローバル変数ではプログラム全体で有効になり、
ローカル変数は宣言された関数と、その関数内でネストされた関数内で有効になります。
実際の例として、以下のようなコードを書いて実行してみます。
//グローバル変数 var test = "1"; console.log("グローバル変数 : " + test); function scope() { //ローカル変数 var test = "2"; console.log("ローカル変数1 : " + test); function nest() { //ネストされた関数内のローカル変数(再定義は無し) console.log("ネスト内のローカル変数 : " + test); //関数内のローカル変数の値を返す return test; } //このタイミングで、関数から返された「3」を返す return nest(); } console.log("関数呼び出し : " + scope()); console.log("グローバル変数 : " + test);
実行した結果は次のようになります。
グローバル変数 : 1 ローカル変数1 : 2 ネスト内のローカル変数 : 2 関数呼び出し : 2 グローバル変数 : 1
先ほどの「ローカル変数は宣言された関数と、その関数内でネストされた関数内で有効」ということが上記の出力結果の「ネスト内のローカル変数 : 2」という出力結果になることで、理解できます。
上記の例のように、関数内で変数を宣言する場合や、グローバル変数を宣言する場合、
そのコード(ここでは変数の宣言)に関連づけられたスコープチェーンが存在します。
スコープチェーンはそのコードのに対してスコープ内で変数を定義するオブジェクトのリストと言えます。
ローカル変数aを宣言する際、次のような処理がされます。
・変数aのチェーンの先頭を調べる
・aというプロパティを持つ場合、その値が定義される
・もし、プロパティを持たなかった場合、チェーンの次のオブジェクトを探す
・次のオブジェクトでaというプロパティがあれば、その値が定義される
・もし、なければ、同様にチェーンの次のオブジェクトを探す
・最終的にプロパティが見つからなければ、ReferenceErrorとなる
ローカル変数ではなく、トップレベルのグローバル変数の場合は次のようになります。
・グローバル変数は一つだけ定義される
・関数1つに2つのスコープチェーンが存在する(1つ目のスコープチェーンは関数の引数、ローカル変数、2つ目はグローバルオブジェクト)
・ネストされた関数の場合、3つ以上のスコープチェーンになる
・ネストされた関数が定義された時、スコープチェーンが保持される
・ネストされた関数が呼び出された時、オブジェクトが生成される
・ローカル変数が保存される
・上記の生成されたオブジェクトをスコープチェーンに追加する
・関数呼び出しのスコープチェーンを表す新たなスコープチェーンを生成
簡易的に書きましたが、次のネストされた関数の場合と合わせて、全体像を理解することがポイントになります。
ネストされた関数の場合は、次のような動きになります。
その側の関数が呼び出されるたびに内側の関数が再び定義され、
外側の関数の呼び出しごとに、スコープチェーンが異なって動作します。
このスコープチェーンが異なって動作する。という考え方がjavascriptのクロージャの考え方につながってくるので重要です。
javascriptでグローバル変数を宣言する時、変数の定義がどのように振舞うのかを調べます。
グローバル変数を生成する際は、グローバルオブジェクトのプロパティが定義されます。
strictモード(‘use strict’)を使わない場合、宣言していない変数に値を代入した瞬間にグローバル変数が作られます。
宣言していない変数とは、具体的には次のような例になります。
//varを使わず、いきなり100を代入(グローバル変数) test = 100;
上記のように、varによる変数宣言をしない場合は、再定義が可能なプロパティとして生成されます。
また、逆の意味でvarによる変数宣言をする場合は、再定義が不可能なプロパティとして生成されます。
試しに以下のコードを書きます。
グローバル変数を宣言して生成する場合と、宣言なしで生成する場合の動きの違いがわかります。
//varを使ったグローバル変数へ代入 var test = 10; console.log(test); //varを使わず、グローバル変数へ代入 test2 = 20; console.log(test2); delete test; console.log(test); delete test2; console.log(test2);
再定義が可能かどうかを調べる為に、delete演算子を使って、それぞれの変数の削除を試みています。
実際に実行した結果は次のようになります。
10 20 10 ReferenceError: test2 is not defined
最後のtest2という変数はdelete演算子によって削除はできません。
これはvar宣言しない場合の変数宣言は再定義可能な(グローバルオブジェクトの)プロパティとして生成されるので、削除ができたということになります。
削除ができた為に、コンソールで出力しようとしたところ、「ReferenceError: test2 is not defined」というエラーになります。
簡単にまとめると
■var宣言したグローバル変数
再定義不可
■var宣言しないグローバル変数
再定義可能
(delete演算子による削除ができた)
ということになります。
Callオブジェクトという概念についてまとめます。
javascriptではグローバル変数はグローバルオブジェクトのプロパティとして解釈されます。
では、ローカル変数の場合はどうなるかと言いますと、関数の呼び出した時に変数オブジェクトが生成されます。
上記の関数を呼び出した時に生成される変数オブジェクトのことをCallオブジェクトと呼びます。
Callオブジェクトは次の情報が含まれています。
関数内のローカル変数の値
関数に渡された引数(名前と値)
引数情報を管理するオブジェクト
thisキーワード
このCallオブジェクトの考え方はスコープチェーンを理解する時に重要になります。
javascriptの言語仕様の話からは少し逸れますが、webアプリケーションを作成する際、画面遷移時に文字コードの制約がある場合の対応方法を記載します。
具体的にはタイトルの通り、HTMLファイルがUTF-8で書かれていて、画面更新時にはSJISでPOSTする方法です。
<form name="form1" method="post" action="http://xxxxxx/" accept-charset='Shift_JIS'> <input type="hidden" name="test" value="a"> <input type="submit" value="送信" onClick="buff=document.charset; document.charset='Shift_JIS'; document.form[0].submit(); document.charset=buff;"> </form>
送信ボタンのonClickに文字コードを指定して送信する処理を書くことで、更新時にのみ文字コードを変更して値を送信できます。
決済システム等を実装する際、相手側サーバのAPIがSJISしか受け取れない場合で、なおかつサーバ側言語のフレームワークの制約などで文字コードがSJIS以外で固定されているときに、このように書くケースがあります。
javascriptをブラウザで実行している際、アクセス時のURL等を取得する方法
console.log("location.href -> " + location.href); console.log("location.host -> " + location.host); console.log("location.pathname -> " + location.pathname); console.log("location.search -> " + location.search); console.log("location.protocol -> " + location.protocol); console.log("location.hash -> " + location.hash); console.log("location.hostname -> " + location.hostname);
locationオブジェクトを使い、任意のプロパティの値を使うと取得可能です。
あまり一般的な書き方ではないかもしれませんが、
javascriptのパイプを利用して、変数のデフォルト値を設定する書き方もあります。
var test = test || "abc"; console.log(test);
出力結果は「abc」となります。
通常ではこのような書き方はあまり見かけませんし、このような書き方はしませんが、動作としてデフォルト値のような振る舞いをします。
javascriptでパイプを2つ書く場合、論理和として判定され、左辺か右辺を両方判定し、論理和を返す動きをします。
この動きを利用して、関数内での変数のデフォルト値を設定するケースがあります。
javascriptならではの書き方かもしれませんが、以下のようなコードになります。
function x(test) { //引数の値がなかった場合、abcとする test = test || "abc"; console.log(test); }
変数の宣言について、繰り返して宣言した場合の動きを試します。
次のようなコードを書きます。
var test = 10; var test = 20; console.log(test);
この場合、特にエラーにならずに処理されます。
コンソールログへは20が出力されます。
次に、varで宣言されていない変数を呼び出してみます。
var test = 30; var test = 40; console.log(test2);
この場合は「ReferenceError: test2 is not defined」というエラーになります。
宣言されていない変数に対して、変数の呼び出しはNGです。
では、次のような、変数宣言をしていない変数に代入する場合を試してみます。
var test = 30; var test = 40; test3 = 50; console.log(test3);
この場合、上記のコードのみでしたら、エラーにはなりません。
変数test3への代入は問題なく行われ、コンソールログには50が出力されます。
また、javascriptにはstrict modeと呼ばれる、実行形態があります。
strict modeはECMAScript2015で導入されて、javascriptを実行するブラウザに厳密な解釈をさせる機能と言えます。
試しに上記のコードの例をstrict modeをつけて実行してみます。
'use strict'; var test = 30; var test = 40; test3 = 50; console.log(test3);
この場合は「ReferenceError: assignment to undeclared variable test3」というエラーになり実行が止まります。
ポイントとなる考え方はstrict modeにしない場合、なおかつvarをつけずに変数に代入した場合、エラーにはならなずに処理が続行されます。
この時に使用する変数はグローバルオブジェクトのプロパティして解釈されるので、注意が必要です。
また、グローバル変数のような振る舞いをすることになりますが、javascriptでは基本的にはvarを明記して変数宣言を行うようにします。
前回に続きjavascriptの変数宣言について調べます。
変数宣言はvarとletがありますが、宣言の方法を調べてみます。
まずは、変数を単体で宣言する場合は以下のように書きます。
var test; var a;
など、次に、一度に複数の変数を宣言する場合です。
//1行に書く var test, a;
次は変数の宣言と同時に値を代入する場合です。
var test = 100; var a = "1000"; //1行に書いて、なおかつ代入する var test = 100, a = "1000";
変数宣言はforループ文の内部でも宣言して使用します。
for (var i = 0; i < 100; i++) { console.log(i); }
javascriptでは、宣言された変数に値が代入されない場合は、undefinedと解釈されます。
また、変数を宣言する時に型を指定していません。
型の概念はありますが、明示的に宣言せず、プログラムの処理中に型が動的に決定づけられます。
javascriptにおける変数の宣言を詳しく調べてみます。
本ブログでも以前の投稿(https://propansystem.net/blog/2018/06/20/post-6640/)で、変数の宣言について触れていますが、そもそもjavascriptには歴史的な話としてECMAScript(https://ja.wikipedia.org/wiki/ECMAScript)という名称で標準化されており、ECMAScriptにはバージョンが策定されています。
正式にリリースされているECMAScript2015(2015年に策定)になり、バージョンを省略してES6と呼ぶこともあります。
ES6の前までのバージョンでは、主にvarで宣言していましたが、ES6からはletで宣言することも可能です。
両者の違いは変数スコープの違いがあります。
varの宣言は
以下のような挙動になります。
var test = 10; console.log(test); { var test = 20; console.log(test); } console.log(test);
出力結果は
10 20 20
となります。
varで宣言された変数はブラケットで囲んだ後の変数testが20に置き換わっています。
続いて、letでの宣言を試します。
let test2 = 30; console.log(test2); { let test2 = 40; console.log(test2); } console.log(test2);
出力結果は
30 40 30
になります。
letで宣言された変数はブラケットの後、変数testの値のが30に置き換わっていません。
このようにletでの宣言は厳密にブラケット内で使うことができ、コーディングの際の不具合の低減につながります。
ECMAScriptの策定については、2009年に策定されたEdition5から、2015年以降にEdition6に変わりました。
言語仕様が変わると、ブラウザがそれに対応しているかどうか、といった問題も出てくるので、
一概にjavascript(ECMAScript)のバージョンだけを意識してコーディングができるとも言い切れません。
開発環境や利用環境、全体を見てのコーディングが必要です。
javascriptのオブジェクトもまた、型変換ができます。
オブジェクトを型変換する場合、論理値への変換は全てtrueに変換されます。
オブジェクトから文字列、数値への変換はメソッドを呼び出して変換します。
変換するメソッドは
toString()
と
valueOf()
です。
toStringt()メソッドは各クラスの種類に応じた変換結果を文字列や数値を返します。
valueOf()メソッドについては、各オブジェクトに対しての基本型値に変換したものを返します。
まとめると、以下のようになります。
■オブジェクトを文字列に変換する
①対象となるオブジェクトがtoStringt()メソッドを持つ場合
javascriptはtoStringt()メソッドを呼び出す。
基本型値が返されたら、その値を文字列に変換した文字列を返す。
②toStringt()メソッドが定義されていない場合
基本型値が返されなかった場合は、valueOf()メソッドが定義されているかどうかで、動きが変わってきます。
定義されている場合は、valueOf()メソッドを呼び出します。
③toStringt()メソッドもvalueOf()メソッドも定義されていない場合
例外処理となり、エラーとなります。
■オブジェクトを数値に変換する
①valueOf()メソッドが定義されており、返りの値が基本型値の場合、基本型値を数値に変換して返します。
②toStringt()メソッドが定義されている場合、その値を返します。
③toStringt()メソッドもvalueOf()メソッドも定義されていない場合、例外処理となり、エラーになります。
javascriptで明示的な型変換をする場合を試してみます。
明示的な型変換は以下の関数を使います。
Boolean() Number() String() Object()
これらの関数をnew演算子を使わないで呼び出すと、明示的な型変換の動作をします。
次にサンプルコードを書きます。(便宜上consoleに出力しています)
//50 console.log(Number("50")); //false console.log(String(false)); //true console.log(Boolean("60")); //Number{5} console.log(Object(5));
コメント部分はそれぞれコンソールに出力された値です。
javascriptで、オペランド(被演算子)どうしの演算をする場合、動的な型変換が発生します。
例えば
文字列 + 数値
の場合は、片方の数値が文字列に変換されて、連結されます。
単項演算子の!を使う場合は、論理的に真偽を反転させる為、以下のようになります。
//false console.log(!10); //false console.log(!true); //true console.log(!false);
意味の無い実験かもしれませんが、単項演算子!で真偽を反転させたものに、文字列を連結して出力すると、
//falsetest console.log(!10 + "test"); //falsetest console.log(!true + "test"); //truetest console.log(!false + "test");
という結果になりました。
また、単項演算子!の真偽の反転は、2重につけることもできます。
試したところ、それぞれ以下のように出力されます。
//false console.log(!!false); //falsetest console.log(!!false + "test");
NumberクラスではtoString()メソッドがあります。
ある変数に数値を代入し、toString()メソッドを適用してみます。
この時、メソッドに記述する引数に注目します。
メソッドの引数は2進数から36進数まで、明示的に基数を指定することができます。
それぞれメソッド行末のコメントは実際の出力結果になります。
var test = 65; //引数を指定しない場合、10進数 console.log(test.toString()); //文字列としての「65」を出力 //2進数として指定 console.log(test.toString(2)); //1000001 //4進数として指定 console.log(test.toString(4)); //1001 //8進数として指定 console.log(test.toString(8)); //101 //10進数として指定 console.log(test.toString(10)); //65 //16進数として指定 console.log(test.toString(16)); //41 //30進数として指定 console.log(test.toString(30)); //25 //36進数として指定 console.log(test.toString(36)); //1t //(実験的に)37進数として指定 console.log(test.toString(37));
一番最後に実験的に37進数を指定すると
RangeError: radix must be an integer at least 2 and no greater than 36
というエラーになります。
また、Numberクラスには上の例(toString)の他、下記のメソッド(一例です)があります。
toLocalString toFixed toExponential toPercision
詳しく調べていくと、Numberクラスのprototypeには以下のメソッドがあります。
Number.prototype.toExponential() Number.prototype.toFixed() Number.prototype.toLocaleString() Number.prototype.toPrecision() Number.prototype.toSource() Number.prototype.toString() Number.prototype.valueOf()
全てのメソッドの意味と実行結果を確かることはしませんが、全てprototype経由でコールされている点に注意が必要です。
いくつかサンプルを書いてみます。(console.logのコメント部はそれぞれ演算結果です)
//ランダムな数字を代入 var test = 2482704.5681; //文字列に変換され、小数点は指定の桁数になる //2482705 console.log(test.toFixed(0)); //2482704.568 console.log(test.toFixed(3)); //2482704.568100 console.log(test.toFixed(6)); //数値が指数表現に変換される //2.5e+6 console.log(test.toExponential(1)); //2.4827e+6 console.log(test.toExponential(4)); //有効桁数が少ない場合、指数表現に変換される //2.483e+6 console.log(test.toPrecision(4)); //2482704.6 console.log(test.toPrecision(8)); //2482704.568 console.log(test.toPrecision(10));
prototypeの概念は今回の投稿内容とは別の領域になるので、別途詳しく掘り下げます。
簡単にいうと、javascriptの全てのオブジェクトにはprototypeを継承していて、最小のテンプレートという考え方ができます。
また、Numberのグローバル関数としては、次のようなものが定義されています。
Number.isFinite() Number.isInteger() Number.isNaN() Number.isSafeInteger() Number.parseFloat() Number.parseInt()
型変換の際に使う関数としては、parseIntとparseFloatになりますが、
parseIntは整数のみ解析できることに対し、
parseFloatは整数と浮動小数点数の両方を解析できます。
以下、簡単なサンプルを書いて試してみます。
var test = 2482704.5681; console.log(parseInt(test)); //2482704 var test = "24827テスト"; console.log(parseInt(test)); //24827 var test = "24827テスト"; console.log(parseFloat(test)); //24827 var test = 248.4587; console.log(parseInt(test)); //248 var test = -248.4587; console.log(parseInt(test)); //-248 var test = 0xFF; console.log(parseInt(test)); //255 var test = 0xff; console.log(parseInt(test)); //255 var test = 0xFD; console.log(parseInt(test)); //253 var test = "0.1"; console.log(parseInt(test)); //0 var test = "0.2"; console.log(parseInt(test)); //0 var test = ".2"; console.log(parseInt(test)); //NaN var test = "abcd.2"; console.log(parseInt(test)); //NaN var test = "abcd.3"; console.log(parseFloat(test)); //NaN
実行した結果、上記のようになります。
数値として解釈できない場合には、NaNという処理結果になります。
等値演算子で値が等しいかの判定が行われる時、型変換が実行されます。
例えば、下記のようなコードを書いて出力してみます。
(コメント部分は出力結果です)
//trueと出力 console.log(null == undefined); //trueと出力 console.log("50" == 50); //falseと出力 console.log(60 == false); //trueと出力 console.log(0 == false); //falseと出力 console.log("70" == false); //trueと出力 console.log("0" == false);
上記の実行結果で、trueと返るコードがあります。
等値による比較を行っていますが、それぞれ左辺と右辺で異なる型を書いていますが、比較するタイミングで型の変換が行われ、それを基に結果(true/false)が出力されていることがわかります。
例えば「0 == false」の比較では、右辺のfalseが比較時に一度0として変換された後に処理されているのがわかります。
等値演算子「==」による比較は上記にようになりますが、同値演算子の場合はどうなるでしょうか。
試しに、上記の例の比較部分を全て同値演算子に変更したコードを書いてみます。
//falseと出力 console.log(null === undefined); //falseと出力 console.log("50" === 50); //falseと出力 console.log(60 === false); //falseと出力 console.log(0 === false); //falseと出力 console.log("70" === false); //falseと出力 console.log("0" === false);
上記の結果、全てfalseとして判定されました。
同値演算子の場合は、動的に型変換が行われず、厳密に型が違う為にfalseを返す挙動になります。
javascriptでは、値の変換がおこる場合、演算子や文で必要とされる型への変換が動的に行われます。
グローバルオブジェクトについて
グローバルオブジェクトとは、javascriptが実行された時にどこからでも呼び出すことができるオブジェクト。
以下のものがあります。
グローバルプロパティ
undefined
Infinity
NaN
グローバル関数
isNaN()
perseInt()
コンストラクタ関数
Date()
RegExp()
String()
Object()
Array()
グローバルオブジェクト
Math
JSON
通常、webアプリケーションを作成する場合はブラウザベースでの開発になります。
ブラウザで実行される場合、Windowオブジェクトがグローバルオブジェクトとなります。
グローバルオブジェクトが生成される際、グローバル値が設定されます。
グローバル変数として宣言した変数はグローバルオブジェクトのプロパティとして解釈されます。
以下に例を書きます。
//グローバル変数を用意する var test_global = 10; //windowオブジェクトのプロパティとしてアクセス console.log(window.test_global); //出力を確認 //「10」と出力される
javascriptの予約語である
null
は値が存在しないことを表す値です。
次のようなコードを書いて実行してみた結果、
コンソールに出力しても当然、nullが返ります。
var test = null; console.log(test);
これをtypeof演算子を通して出力してみます。
console.log(typeof(test));
するとobjectという出力結果になります。
このnullとは対照的にundefinedという予約語もあります。
意味としては「値が存在しない」というnullと同じですが、初期化されていない変数の値、オブジェクトのプロパティが存在しない、配列の要素が存在しない場合の、未定義の値を指すときを表します。
nullは予約語ですが、undefinedは予約語ではないです。
undefinedに対してtypeof演算子を使ってみます。
console.log(typeof(undefined));
実行するとそのままundefinedが出力されます。
また、一度変数に入れて実行すると、下記の例では同様にundefinedが出力されます。
var test2 = undefined; console.log(test2); console.log(typeof(test2));
nullとundefinedを比較してみます。
以下のコードを書いて実行結果をみてみます。
var test_null = null; var test_undefined = undefined; if (test_null == test_undefined) { console.log("一致する"); } else { console.log("一致しない"); } if (test_null === test_undefined) { console.log("一致する"); } else { console.log("一致しない"); }
上記の実行結果はともに「一致する」という出力結果になります。
一度変数に入れた両者の値として解釈されるので、等値(==)と同値(===)の判定条件でも同じになりました。
この記事を書いている段階では、変数に代入した後にどう解釈されているのか、もう少し深く理解する必要があります。
では、次のような直接的に書いて両者を比較する場合はどうなるか、試してみます。
if (null == undefined) { console.log("一致する"); } else { console.log("一致しない"); } if (null === undefined) { console.log("一致する"); } else { console.log("一致しない"); }
この場合は、等値(==)の場合は「一致する」として、同値(===)の場合は「一致しない」という結果になりました。
nullをtypeof演算子でみるとobjectという出力結果になります。
objectという結果になりますが、nullにはプロパティやメソッドもありません。
次のようなコードを書いて実行してみました。
//何も表示されない null //存在しないメソッドを呼ぶ null.test();
結果、nullに対して存在しないメソッドを呼んでも
TypeError: null has no properties
という出力結果になります。
ここでは存在しないメソッドを呼びましたが、そもそもnullに対してはメソッドを追加することができない為、どのような場合でもTypeErrorになります。
javascriptの論理値について、プログラムを書いて確かめます。
他のプログラム言語ではboolean型として、書くケースが多い論理値です。
真か偽か、trueかfalseか、といった2つのみの値を持つ変数になりますが、javascriptは特有のケースがあります。
論理値のサンプルを書いてみます。
以下のコードを書いて実行させてみます。
var test = 10; if (test == 10) { console.log("一致する"); } else { console.log("一致しない"); }
実行すると「一致する」という結果になります。これは論理値の比較ではなく、変数の値の比較なので一致するという結果になります。
では、一度代入した変数そのものをif分で比較してみます。
var test = 10; if (test) { console.log("一致する"); } else { console.log("一致しない"); }
上記の場合は「test」という部分がtrueかfalseかを判定する論理値になっている状態です。
結果は「一致する」という出力になります。
上記のように式全体の場合はわかりやすい例と言えます。
では、次のような例を書いて試してみます。
var test2 = null; if (test2) { console.log("TRUE"); } else { console.log("FALSE"); }
この場合、test2という変数に対し、nullを代入しています。
その変数そのものをif分で判定すると、falseが返り、コンソールログ上の出力結果は「FALSE」となります。
このように、javascriptでは論理値でFALSEと判定される値があります。
以下の値はfalseになります。
undefined null 0 -0 NaN
また、次のように配列を記述した場合
var testarray = [1, 2, 3, 4]; if (testarray) { console.log("TRUE"); } else { console.log("FALSE"); }
この場合の実行結果は「TRUE」になります。
また、論理値にはtoString()メソッドが使え、このメソッドを使った時はtrueかfalseがそのまま文字列として変数などに格納されます。
javascriptで文字列に対して正規表現を使ってパターンマッチングすることを試してみます。
通常、javascriptではRegExpコンストラクタを使ってマッチングをします。
マッチする条件としては正規表現で書かれた内容を基にされます。
正規表現については、javascriptとは別カテゴリなので、別途詳しく掘り下げます。
正規表現の記載方法としてはperlのものと同様の書き方が有効なります。
javascriptでのパターンマッチングについて、一例を書きます。
//「パターン文字列」にマッチするかどうか /パターン文字列/ //対象文字列の先頭が「パターン文字列」にマッチするかどうか /^パターン文字列/ //0以外の数字にマッチするかどうか /[1-9][0-9]/ //大文字小文字区別なしにjavascriptにマッチ /\bjavascript\b/i
上記のようなパターンマッチが使えるので、次に実際にコードを書いてみます。
実際に次のようなコードを書いた場合の、実行結果をコメント部分に書いています。
//対象文字列 var check_string = "abcdefghijkh1234";
上記の対象文字列(変数)に対して、マッチするパターンを決めます。
//文字列「abc」にマッチするパターン var pattern = /abc/; //先頭の文字列が「abc」にマッチするパターン var pattern2 = /^abc/; //文字列「h」にマッチするか //(正規表現の最後の「/g」は文字列の最後まで検索を繰り返す var pattern3 = /h/g;
対象文字列とマッチするパターンを組み合わせ、マッチングを実行します。
//マッチするかどうか検証 //trueが返る console.log(pattern.test(check_string)); //trueが返る console.log(pattern2.test(check_string)); //文字列「h」がcheck_stringの何番目になるか console.log(check_string.search(pattern3)); //7が返る //文字列「h」がヒット、検索結果は Array [ "h", "h" ] として返される console.log(check_string.match(pattern3));
上記のコメント部分の結果が得られました。
パターンの記述方法や、マッチさせる方法はこの他にもあるので、実際にコーディングする場面が出てきたら追記します。
javascriptの文字列を操作する方法を試します。
文字列の連結は「+」で行います。
下記、例です。
var string_test = "test" + "1234";
上記の場合、変数string_testにはtest1234という文字列が代入されます。
文字列に対し、標準で装備されているメソッドを使うことができます。
下記、例です。(便宜上、console.log出力はしていません)
string_test.length(); string_test.charAt(); string_test.substring(2, 4); string_test.slice(1, 3);
等のメソッドがあります。ここではメソッドについては一部抜粋しているだけなので、詳しくはリファレンスサイト等を確認するとよいです。
上記のメソッドについては、変数内の文字列に対して、メソッドを実行した結果を返す点に注意が必要です。
メソッドを実行した後でも変数内の文字列は変わらないです。