自習室

こもります

開眼!JavaScript:オブジェクトの振る舞いについて細かいとこメモ (2/3)

どんな本

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

(ECMA-262 3rd edition 準拠の) オブジェクトの振る舞いについて、こまかーいところまで拾って説明してくれている本。

最近仕事でJavaScriptを書くことがふえたので、がむしゃらに書いて動いてはいるけど、いまいちよく分かっていないことが多いなぁ、と感じていたので、冷静に一度勉強してみようと思い手に取りました。

この記事は前回の続きで、自分のためのメモです。

前回:開眼!JavaScript:オブジェクトの振る舞いについて細かいとこメモ (1/3) - 自習室

4 関数 (Function() )

JavaScriptにはいくつかの関数の使い方があるが、一番基本的な形は、「独自のスコープを持った実行可能な文の集まり」

Function() コンストラクタは使わない

var addFunction = new Function('num1', 'num2', 'return num1 + num2');
// eval()をつかって最後の文字列の引数を解釈し、関数を生成する

console.log( addFunction(1, 2) ); // 出力:3

// 普通はこう(関数式)
var addFunction = function(num1, num2) {
  return num1 + num2;
}
console.log( addFunction(1,2) ); // 出力:3

return 文が無くても、undefined を返す

var sayHi = function() { console.log( "return nothing?" ); }
sayHi(); // 出力:"return nothing?" と書いた上で、undefined を出力する

関数もオブジェクトである。第一級オブジェクトとして扱われる関数を第一級関数という

第一級オブジェクト(関数)は以下の様な性質を持つ

  • 変数やデータ構造に格納することが出来る
    • 関数はオブジェクトのプロパティ(メソッド)になったり、あるスコープで単体の変数として存在できる
  • サブルーチンのパラメータとして渡すことが出来る
    • 関数の引数に関数を指定できる
  • サブルーチンの戻り値になることが出来る
    • 関数を返す関数がある

関数定義時の引数の数を超える(または不足する)引数を渡すことが出来る

var func = function( num1, num2 ) {
  return num1 + num2;
}

console.log( func(2, 3, 1) ); // 出力:5 (最後の引数1は無視される)

this プロパティ

this はすべての「関数」に渡され、実行中の関数を保持しているオブジェクトを参照する。たとえばその関数がオブジェクトのメソッドとして呼ばれている場合は、this は親であるオブジェクトの参照となる。

// ブラウザでの実行時、グローバルでの this は window オブジェクト指す
var myGlobalFunc = function(){
  console.log( this );
}
console.log( myGlobalFunc() ); //出力:window

thisについては後で更に詳しく。

arguments プロパティ

二つ前の項で「引数の数を超える引数を渡すことができる」と書きましたが、arguments で確認出来る。

var func = function( num1, num2 ) { // 二つの引数しか定義していないが
  console.log( arguments );
  return num1 + num2;
}

console.log( func(2, 3, 1) ); 
// 出力:[2, 3, 1] 渡した引数すべてにアクセス出来る
// 出力:5 

arguments.callee で、引数を受けた主、、、、つまり関数自身 を参照する

var foo = function() {
  console.log( arguments.callee );
}();
// 出力:function () { console.log(arguments.callee); }  という文字列

これで再帰関数を組めそうだが、ECMA-262 5th Edition の Strict modeでは arguments.callee() の使用は禁じられている。代わりに、関数自分自身が、自分の関数名を呼び出す方法がある。

// 関数名を定義することで、再帰を行う
var countDown = function countDown( num ) {
  console.log( num );
  num--;
  if( num < 0 ) return false;
  countDown( num );
};

countDown( 5 );
// 出力: 5, 4, 3, 2, 1, 0

関数宣言は巻き上げられる。関数式で定義した関数は巻き上げられない

関数宣言の場合の巻き上げ

var speak = function() {
  sayYo();
  // 関数の巻き上げが起き、呼び出すことが出来る。
  // しかしこの時点では num が未定義なので、while文は回らず、 "yo" は出力されない
  // 出力:
  // num = undefined
  // finished

  var num = 5;
  function sayYo(){
    while(num > 0) {
      console.log("yo");
      num--;
    }
    console.log( "num = " + num );
    console.log( "finished" );
  }

  sayYo();

  // ここではnumが定義済なので、while文が回る
  // 出力:
  // yo yo yo yo yo
  // num = 0
  // finished
}();

関数式の場合は

(function() {
  sayYo();

  var sayYo = function() {
    console.log( "yo" );
  }
})();
// 出力: TypeError: undefined is not a function
// sayYo() がundefined扱いになります。

apply() と call()

特殊な実行の仕方。greet.runGreet() 関数を、 paul から呼んでいる物として実行する。

var greet = {
  runGreet: function() {
    console.log( this.name + " says " + arguments[0] + " " + arguments[1] );
  }
}

var paul = { name: "paul"};

greet.runGreet( "Ho", "Ho");
greet.runGreet.call( paul, "Good-bye", "Hello");
greet.runGreet.apply( paul, ["Good-bye", "Hello"]);

無名関数

無名関数は、引数として他の関数に渡すために利用される。たとえばコールバック関数の登録。

無名関数を即時実行するパタン

(function( msg ) {
  console.log( msg );
})( "hello world" );
// 出力 hello world

5. グローバルオブジェクト

グローバルプロパティとグローバル変数

グローパルプロパティは削除可能、グローバル変数は削除不可能。基本的には、グローバルではないオブジェクトのプロパティとそのスコープにおける変数も、同じ関係。

var a = 10; // グローバル変数
b = 100; // グローバルプロパティ(windowオブジェクトのプロパティ)

delete a; // 削除できない。 DontDelete属性。 falseを返す
delete b; // 削除できる trueを返す

console.log( a ); // 出力:10
// console.log( b ); // 出力 ReferenceError: b is not defined

グローバルオブジェクトは暗黙的に参照される

// webブラウザ上で実行する
var foo = { //まずこの時点で、暗黙的にwindowオブジェクトに格納されている。 window.foo と同じ。
  fooMethod: function() {
    alert('foo' + 'bar');  // 下の行と全く同じ処理
    window.alert('foo' + 'bar'); // 上の行と全く同じ処理
  }
}

foo.fooMethod(); // 下の行と全く同じ処理
window.foo.fooMethod(); // 上の行と全く同じ処理

明示的にグローバルオブジェクトを指定する場合、計算コストが「ほんのわずか」上昇するらしいです。

最後に

まだ終わりません。つづきます。