JavaScript Tips – スクリプト単位のスコープ

関数はスクリプト・ローカル

関数はスクリプト間で影響を与えない。同じ関数名を異なるスクリプトで用いても干渉しない。

以下のコードのうち外部スクリプトの内容は、それより上の二つのスクリプトの内容と同じ。スクリプトごとにmain()関数を実行しているが、それぞれのスクリプトで定義されたmain()関数が実行され、エラーや干渉は起きない。

グローバル変数は干渉

以下のコードは、スクリプト間のグローバル変数の挙動を示している。

上記コードの実行結果は以下のとおり。

  • あるスクリプトでグローバル定義された変数は、それ以後のスクリプトに残される
  • それ以後のスクリプトで、重ねてvar定義は可能で、その後はそれ以降の定義による

外部ファイルの注意点

クラスなどの資源を共有化するために、スクリプトを外部ファイルとする場合の注意点。

  • 読み込みと実行
    • head要素内でスクリプトファイルを読み込む場合、script要素を記述した順番に読み込まれる
    • スクリプトファイルが読み込まれた時点で実行される
  • グローバル変数
    • グローバル変数を定義していた場合、スクリプトファイル読み込み時に定義される
    • 同じグローバル変数を異なるファイルで重複定義した場合、最後に読み込まれたスクリプトの定義結果が反映される(変数の重複宣言を参照)
  • クラス
    • ローカルなインスタンス名は同じ名前でも干渉しない
    • グローバルなインスタンスで重複定義されたオブジェクトは、最後に定義されたものが残る

以下に、クラス定義ファイルとインスタンス定義ファイルを別ファイルに分けた例を示す。

【HTMLファイル】

【クラス定義ファイル】

【インスタンス定義ファイル】

【別のインスタンス定義ファイル】

上記の実行結果と挙動は以下の通り。

  • head要素で3つのjsファイルが読み込まれる
  • 読み込まれた時点で、グローバルな変数とオブジェクトが定義される
  • 以降、ボタンを押すごとに、それぞれの関数でローカルな変数とオブジェクトが定義・表示される
  • グローバルな変数・オブジェクトについては、最後に定義されたinstance2.jsの定義内容

変数、オブジェクトとも、グローバル・スコープのものについては、head要素内で最後に読み込まれたinstance2.jsの内容が保持される。読み込み順を入れ替えて最後にinstance1.jsを置くと、結果も変化する。

 

JavaScript – その他

処理時間の計測

処理時間の計測には、console.time()とconsole.timeEnd()を用いることができる。

コールスタック・オーバーフロー

再帰のネストが深すぎると、コールスタック・オーバーフローが発生する。

これを回避する方法の一つとして、setTimeout()関数による方法がある。

 

JavaScript – Timer

概要

JavaScriptのタイマ関連の実装。

  • Dateオブジェクトによる経過時間の計測
  • setInterval()による等時間間隔の起動
  • setTomeout()による等時間間隔の実行キュー登録

Dateオブジェクト

Dateオブジェクトのミリ秒単位の値を用いる方法。

DateオブジェクトのvalueOf()メソッドでミリ秒単位の現在時刻を取得し、これを用いて時間を判定する。これに続く処理を純粋に遅延させることになる。

なお、valueOf()メソッドの代わりに、+newオペレータを用いることができる。

setInterval()

起動

指定した時間間隔で関数を呼び出す。他の処理とは無関係に呼び出されるため、関数の処理内容が重い場合には、処理が終わる前に呼び出されてしまうことがある。

setInterval()関数の第1引数に繰り返し実行したい関数を、第2引数に繰り返し間隔をミリ秒単位の数値で指定。関数の指定方法としては、以下の3通り。

文字列で記述 関数名を文字列として記述。その際、関数の後の()も付けて記述
直接記述 関数名をクォート/ダブルクォートで囲まず直接記述。関数の後ろの()はつけない
無名関数を使って記述 function(){}の中にターゲットとなる関数を記述

 

引数がある場合

繰り返し実行する関数が引数を持つ場合、文字列で引数を記述するか、無名関数を使って記述する。

停止

タイマーの起動と停止を制御するには、起動時にsetInterval()を変数で参照し、そのオブジェクトに対してclearInterval()で停止をかける。

setTimeout()

基本形

setTimeout()の第1引数に実行したい関数を文字列で、第2引数に遅延時間をミリ秒で指定する。

上記の例では、以下のように推移する。

  1. “start”を表示
  2. setTimeout()を実行→3秒後にmain()関数が呼び出されるよう指定
  3. “end”を表示
  4. 約3秒後にmain()が実行され、”in main()”を表示

起動

関数を文字列とする方法

文字列で引数を分離する方法

無名関数を使う方法

タイマとしての利用

等時間間隔で実行したい関数で、setTimeout()において再帰的に自己を呼び出すことによってタイマーとして利用できる。

遅延時間後に実行キューに登録されることから、それぞれの関数の実行は重複することなく担保される。このため、setInterval()に比べて関数間の衝突は生じないが、実際の実行間隔は遅延時間より長くなる

停止

setTimeout()setInterval()と同じくタイマーIDを返し、これに対してclearTimeout()を実行することでタイマーを停止できる。

上記の例では、タイマーを止めるstop()関数が5秒後に実行されるようにし、counter()を開始している。counter()自身がが終了する前にstop()が実行され、counter()は停止する。

補足

実行キュー

setTimeout()は、指定した関数を指定した遅延時間後に実行キューに登録する。このため、setTimeout()実行後に時間がかかる処理に移った場合、その処理中に遅延指定した関数がキューに登録され、処理実行後に関数が実行される。

上記の例の場合、

  1. setTimeout()が実行され、3秒の遅延時間のカウント開始
  2. whileループに入る
  3. 3秒後、hoo()が実行キューに登録される
  4. 5秒後、whileループから抜ける
  5. 実行キューに登録されたhoo()が即時実行される

スタック・オーバーフローの回避

setTimeout()を用いるシーンの一つに、再帰定義のコールスタック・オーバーフローの回避がある。この場合、再帰呼び出しの度にsetTimeout()でラップし、待機時間をゼロとする。

これにより、関数はスタックではなく実行キューに並べられ、オーバーフローは発生しない。ただし、通常の再帰呼び出しに比べて大幅に実行時間が増加する。

たとえば以下の例では単純なループにかかる時間を測っており、

次の例では、再帰的にsetTimeout()で自己を呼び出すようにしているが、処理時間の乖離が大きい。

なお、この方法で関数をsetTimeout()に引き渡す際、引数まで含めて文字列で引き渡そうとするとUncaught ReferenceError: 引数 is not definedとエラーになる。

 

JavaScript – クラス

クラスの構築

コンストラクタ

JavaScriptではクラス定義はなく、コンストラクタを関数で定義する。

  • thisはこの関数が含まれるオブジェクト(のインスタンス)を指す
  • newによって新しいオブジェクトの無名インスタンスが生成される
  • 代入演算子(=)によって、変数が上記のインスタンスを参照する
  • その結果、thisは定義されたインスタンスを指す

コンストラクタで定義されていないプロパティでも、インスタンスごとに後から追加可能だが、これは可読性やクラスとしての一貫性を損なう。

インスタンスの識別

同じクラスのインスタンスへの参照が同一か異なるかを比較することができる。

この比較はthisを用いても可能。

メソッド

非効率的な方法

コンストラクタの中で、プロパティと同じようにthis.[関数名]と無名関数定義によってプロパティを定義可能。

ただし、thisで指定された対象はインスタンスごとに生成・保持されるため、上記コードではインスタンスの数分のメソッドが生成されてしまう。

prototypeによる方法

JavaScriptにおける全てのオブジェクトはObjectに由来する。すべてのオブジェクトはObject.prototypeからメソッドとプロパティを継承しているが、それらは上書きすることが可能。

インスタンスでメソッドを実行しようとした時にそのメソッドが存在しない場合、そのクラスのprototypeにメソッドを探しに行く。 新たに定義したクラスのprototypeプロパティに関数を定義しておくことで、全てのインスタンスが共通のprototype下のメソッドを利用することになる。

prototypeにメソッドを定義する方法の一つは、メソッド単位でクラスのprototypeに登録する方法と、オブジェクトリテラルでまとめて定義する方法がある。

メソッド単位で定義する方法

メソッドごとにクラスのprotottypeに一つずつ記述していく方法。

オブジェクトリテラルでまとめて定義する方法

クラスの初期設定時に連想配列で定義する。

この方法は、新規にメソッドを定義する場合に一回のみ使用するもので、後から同じ方法でメソッドを追加しようとしした場合、最初の定義内容がすべてオーバーライドされる。

ただ、メソッド定義を敢えてダイナミックに変更するのでなければ、この方法はメソッドの定義のあり方として明快ともいえる。

メソッド定義の注意点

クラスの生成はコンクトラクタの前後を問わないが、メソッドを実行する段階では、prototypeへのメソッドの登録が実行済みでなければならない(記述位置の前後関係ではなく、関数呼び出しも含めた実体上の実行順序)。

C++やJavaなどコンパイラが全体の宣言・定義をパースする場合は問題ないが、インタプリタ系のJavaScriptは、定義=宣言と実行の順序については厳しい。

継承

子クラスのprototypeに親クラスを登録することで継承でき、親クラスのプロパティ、メソッドも利用可能となる。

これは厳密な意味でのクラスの継承というよりは、そのような挙動をするためのprototypeチェーンに組み込んだというところ。

注意点

子クラスのメソッドの定義は必ず親クラスの継承の後で行うこと。継承の際にprototypeの内容が上書きされてしまうため、継承の前にメソッド定義すると子クラスのメソッドが参照されなくなってしまう(下記の例では、”instance.setAge is not a function”とエラーになる)。

子クラスのメソッド定義でオブジェクトリテラルを使ってはいけない。継承の後にこれを行うと、親クラスのメソッドがすべて上書きされてしまう。

 

JavaScript – 変数・スコープ

変数のスコープ

グローバルスコープ/ローカルスコープ

  • var宣言で定義された変数は、その関数内のローカルスコープ
  • var宣言なしで定義された変数は、どこで定義されてもグローバルスコープ

ブロックスコープ

JavaScriptにはブロックスコープの概念はない。

関数の中のif{}やfor{}など複数のブロックで同じ変数名を用いると、関数内で共通の変数になる。

特に、for文のループカウンタなどの一時的な変数は、不用意にvar宣言なしで用いると、予期しないところでグローバルに影響を与えてしまう可能性がある。

関数の引数のスコープ

関数の引数はvarなしで宣言するが、スコープは関数内ローカルとなる。

  • グローバル領域でn = 1を定義
  • f( n )で引数を引き取った後、関数内ではn == 1
  • 関数内でnをインクリメントし、n = 2
  • 関数の結果はf( n ) = 2
  • その後、グローバル領域でn == 1で変化していない

変数操作のタイミング

グローバル変数への操作は、スクリプトがの実行の際に行われる。htmlファイル内であれば、そのファイルが読み込まれた時。別のjsファイルの場合はそれが読み込まれた時。

ローカル変数への操作は、その関数が実行された時。

以下の例では、htmlファイル読み込み時にグローバル変数が定義・参照され、その後ボタンを押すたびにmain()関数が実行され、ローカル変数が定義・参照される。

 

JavaScript – データ型・演算

 

データ型

文字型

リテラル

文字列のリテラルを表すには、対になったダブルクォート(“)かシングルクォート(‘)で囲む。

エスケープ文字

\n NewLine(改行文字)
\f フォームフィード
\b バックスペース
\r キャリッジリターン
\t タブ
\’/\” シングルクォート/ダブルクォート
\\ バックスラッシュ
\0nn/\0onn 8進数
\xnn 16進数nnによる文字コード指定(ex. ‘A’=’\x41’)
\unnnn Unicode文字(ex. ‘あ’=’\u3042’)

数値型

JavaScriptでは、内部的にすべての数値が浮動小数点として扱われる。

整数値の表現

10進数 数字の列で指定。先頭の数字は0(ゼロ)以外。
8進数 先頭が0(ゼロ)である数字の列で指定。
16進数 先頭”0x”に続く0~9, a~fの列で指定、
  • 8進数、16進数では、負の値を表すことはできるが、小数部分はなく、浮動小数点表記は使用できない。

以下の例は、10進、8進、16進の各数値の演算結果をconsole.logに出力する。

浮動小数点数の表現

以下の4つの表現は全て同じ値を表す。

  • 0.0001
  • .0001
  • 1e-4
  • 1.0e-4

論理型(Boolean)

trueとfalseの二値。なお、比較式において以下の評価はすべてfalseとして解釈される。

  • 0
  • null
  • undefined
  • 空の文字列

特殊な値

Null Null型にはnullという値が1つだけある。typeof演算子ではnull値はnull型ではなくObject型として扱われる。
 undefined  未定義の変数を参照した場合に返される。
 NaN  不適切な値で、以下の場合に返される。

  • 未定義の変数を含む数値演算
  • 文字列を含む数値演算
  • ゼロをゼロで除算した場合
 Infinity  正の無限大。計算結果が正の数値でオーバーフローした場合や、通常の正の数値をゼロで割った場合に返される。
 -Infinity  負の無限大。計算結果が負の数値でオーバーフローした場合や、通常の負の数値をゼロで割った場合に返される。

演算

代入演算子

代入演算子は、文字列/数値のリテラル/変数を変数に割り当てる。

文字列演算子

結合演算子

“+”演算子は2つの文字列を結合。

数値演算子

算術演算子

Javaと同じ算術演算子+、-、*、/、%(剰余)、インクリメント/デクリメント演算子++、–がある。

比較演算子

Javaと同じ演算子==、!=、<、>、<=、>=がある。

論理演算子

Javaと同じ演算子、&&、||、!(否定)がある。

複合代入

演算結果を代入する場合、複合代入演算子の短縮表記も可能。

条件演算子

==、!= 等しい/等しくない
===、!== 同一である/同一でない

たとえば文字列と数値を比較するとき、==を用いると文字列がNumber型に変換され比較される。

一方===では型変換は行われない状態で、厳密にその内容が等しいかどうかが比較される。

typeof演算子

引数の型に応じて、以下の文字列が返される。

数値 number
文字列 string
オブジェクト object
配列 object
関数 function
論理型 boolean
null number
NaN number
undefined undefined

文字列と数値が混在した演算

以下のルールに従う。

  • 数値同士の演算の結果は数値
  • 二項のうち少なくとも一つが文字列の場合、数値であっても文字列と評価し、結果は文字列
  • 先頭から演算を進め、数値が続く限りは数値演算を続け、一旦文字列演算になると以降は全て文字列演算として評価。
1 + 2 3
“1” + 2 “12”
1 + “2” “12”
1 + 2 + “3” “33”
1 + “2” + 3 “123”
“1” + 2 + 3 “123”

JavaScript – 記述位置

記述位置と記述方法

  • HTMLソース内に直接記述
  • HTMLソース内に関数を直接記述
  • HTMLからJavaScriptの外部ファイルを読み込み

HTML内への直接記述

HTMLソースに以下のように記述。

正確には<script type=”text/javascript”>だが、HTML5ではscript要素のtype属性はデフォルトでJavaScriptなので省略可能。

関数の直接記述

head要素内に以下を記述し、

body要素で以下のように記述することもできる。

外部ファイルの読み込み

例えば以下の内容のファイルを”test1.js”として準備。

保存する場所は、HTMLファイルと同じディレクトリにある”js”ディレクトリの下とする。

headセクションで以下のようにスクリプトファイルを読み込む。

そして、たとえば以下のように関数を呼び出して実行。

覚え書き

VistaとSugarSyncでの問題

Windows Vista上のSugerSyncと同期したフォルダ内でこれを実行したところ、alert内の文字が文字化けしてしまった。HTMLソースがUTF-8で、scriptタグの属性に明示的に”charset=UTF-8″を記述しても解消されない。また、スクリプトソースの<!–などのコメントを除き、出力文字を全て半角としても現象は変わらない(そもそもalertダイアログの冒頭に表示される日本語が文字化けしている可能性もある)。

これに対して、デスクトップ上に新たなフォルダをつくり、関係するHTMLファイルとCSSファイルを移動させて実行したところ、以下のような挙動。本現象が発生したVista+SugarSyncの不安定性に関しては、今しばらく調査が必要。

  • デスクトップ上に”dev”フォルダを作成し、SugarSyncの動機フォルダから必要なファイルとフォルダを移動
  • devフォルダ内のhtmlファイルを直接エディタで編集可能
  • しばらく編集、保存、ブラウザ表示を繰り返していると、htmlファイルにロックがかかる
  • devフォルダの名前を変更しようとしたところ、ロックがかかる
  • 表示中のブラウザを閉じるとロックがかからんくなったので、フォルダ名をdev→dvに変更
  • その後ロックがかからなくなる

2015-12-21.