Learning Advanced JavaScript#1

Learning Advanced JavaScriptという素敵なチュートリアルを見つけたので、頑張って理解してみる。

#90項目あるので抜粋です。

#2: Goal: To be able to understand this function:

このチュートリアルの目標は以下の関数を理解すること。

// The .bind method from Prototype.js 
Function.prototype.bind = function(){ 
  var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift(); 
  return function(){ 
    return fn.apply(object, 
      args.concat(Array.prototype.slice.call(arguments))); 
  }; 
};

#3: Some helper methods that we have:

以降のチュートリアルで使用するヘルパー関数。

assert( true, "I'll pass." ); 
assert( "truey", "So will I." ); 
assert( false, "I'll fail." ); 
assert( null, "So will I." ); 
log( "Just a simple log", "of", "values.", true ); 
error( "I'm an error!" );

#5: What ways can we define functions?

関数の定義方法。

//function文を使う
function isNimble() { return true; };

//関数リテラルを使う
var canFly = function() { return true; };

//インスタンスメソッドとして
window.isDeadly = function() { return true; };
log(isNible, canFly, isDeadly);

function文と関数リテラルの違い

  • function文では、関数名が必要
  • 関数リテラルは「式」で、関数名は必要ない

関数リテラルは、1回しか使わないような関数・名前を付ける必要がない関数に適している。
例えば、以下の様な使い方が出来るので、柔軟に利用できる。

//関数を定義して、変数に代入する
var func = function(x) { return x*x; };
//関数を定義して、別の関数に渡す
a.sort(function(a,b) { return a-b;});
//関数を定義して、すぐに呼び出す
var tensquared = (functionx(x) { return x*x; })(10);

#6: Does the order of function definition matter?

関数定義-呼び出しの順序

var canFly = function() { return true; };
window.isDeadly = function() { return true;} ;
assert( isNimble() && canFly() && isDeadly(), "Still works, even though isNimble is moved." ); 
function isNimble(){ return true; }

isNimble関数は定義する前に呼び出してもOK。

PASS Still works, even though isNimble is moved.

canFly()とwindow.isDeadly()は定義前に呼び出すとダメ。


function文を使った関数定義はコンパイル時に予め解析されている(実行はされない)ので、呼び出し前に定義されている必要はない。
canFly()、window.isDeadly()の様な関数リテラルは「式」なので、予め副作用を伴う式文-代入されている必要がある。
※「式」自体は、何もしない->式=評価して値が生成されるもの


ということでいいのだろうか・・・。

#15: What if we don't want to give the function a name?

var ninja = {
  yell: function(n){
    return n > 0 ? arguments.callee(n-1) + "a" : "hiy";
  }
};
assert( ninja.yell(4) == "hiyaaaa", "arguments.callee is the function itself." );

無名関数でも、arguments.calleeで関数自身を表せば、再帰関数を定義出来る。

#26: Different ways of changing the context:

function add(a, b){ 
  return a + b; 
} 
assert( add.call(this, 1, 2) == 3, ".call() takes individual arguments" ); 
assert( add.apply(this, [1, 2]) == 3, ".apply() takes an array of arguments" );

全ての関数に用意されているcall()/apply()メソッド。あるオブジェクトのメソッドであるかのように、関数を呼び出すことが出来る。
call()メソッドは、第1引数にこれから呼び出す関数の対象となるオブジェクト(this)、以降の引数は呼び出す関数の引数として渡される。
apply()メソッドは、呼び出す関数の引数を配列形式で指定する。

#27: QUIZ: How can we implement looping with a callback?

function loop(array, fn){ 
  for ( var i = 0; i < array.length; i++ ) { 
    fn.call(array, array[i], i);
  } 
} 
var num = 0; 
loop([0, 1, 2], function(value, i){ 
  assert(value == num++, "Make sure the contents are as we expect it."); 
  assert(this instanceof Array, "The context should be the full array."); 
});

function(value, i)の引数として、array[i]とiを渡して、fn.call()をループさせる。
value(array[])の中身は[0,1,2]、fn.callの第1引数はarrayなので

   1. PASS Make sure the contents are as we expect it.
   2. PASS The context should be the full array.
   3. PASS Make sure the contents are as we expect it.
   4. PASS The context should be the full array.
   5. PASS Make sure the contents are as we expect it.
   6. PASS The context should be the full array.

の結果が得られる。

#36: We need to make sure that the new operator is always used.

インスタンスを生成する場合は、new演算子を使う。
以下の様にすれば、new演算子を忘れてもインスタンスが生成される。

function User(first, last) {
  if( !(this instanceof User) ) {
    return new User(first, last);
  }
  this.name = first + " " + last;
}

var name = "Resig";
var user = User("John", name);

assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );

#37: QUIZ: Is there another, more generic, way of doing this?

#36を汎用的に利用できる様にする。

function User(first, last) {
  if( !(this instanceof arguments.callee) ) {
    return new User(first, last);
  }
  this.name = first + " " + last;
}

var name = "Resig";
var user = User("John", name);

assert( user, "This was defined correctly, even if it was by mistake." );
assert( name == "Resig", "The right name was maintained." );

Argumentsオブジェクトのcalleeプロパティは、現在実行中の関数を参照するので、これがUserのインスタンスかどうかを調べればよい。