Chapter 6. Building Reusability with JavaScript Functions
■6.0 Introduction
関数を使う方法には- function(){}のようなfunctionキーワードを使った関数宣言
- 匿名関数(function(){)()} or new Functionを使った関数コンストラクター
- 関数リテラル or function expression
■6.1 Creating a Block of Reusable Code
関数名は動作を明示的にしたものにするのがベストプラクティス。テーブル内の数を計算する関数ならsumTableValuesみたいにするとか。
■6.3 Passing Complex Data Objects to a Function
文字列や数値などを引数として渡した時(Scalar arguments)は、それらのコピーが関数内で使われるので元の値には反映しない。しかし、配列などの複合オブジェクト(complex object)を引数として関数に渡したときに、その関数内で引数に対して変更を加えた場合、関数から出た後も変更は適応される。
これは引数となった複合オブジェクトに参照を持っているためである。
以下の例でitemsに変更が反映して、sepには反映してないのが分かる。
var items = new Array('apple','orange','cherry','lime');
var sep = '*';
concatenateString(items,sep);
alert(items);// apple,orange,cherry,lime,apple*orange*cherry*lime*
alert(sep); // *
function concatenateString(strings, separator) {
var result="";
for (var i = 0; i < strings.length; i++) {
result+=strings[i] + separator;
}
// assign result to separator
separator = result;
// and array
strings[strings.length]=result;
}
■6.4 Creating a Dynamic Runtime Function
new Functionを使った動的な関数生成は実行されるまでどう動くのかが分かりません。関数リテラル使った方がよいよ
■6.5 Passing a Function As an Argument to Another Function
関数宣言は関数リテラルを変数に入れることでもできる。
function otherFunction(x,y,z) {
x(y,z);
}
// use a literal function:
var param = function(arg1, arg2) { alert(arg1 + " " + arg2); };
otherFunction(param, "Hello", "World");
// 下と同様な意味:
otherFunction(function(arg1,arg2) { alert(arg1 + ' ' + arg2); }, "Hello","World");
また関数リテラルにも名前を付けることができるが、その関数名は内側からしかアクセスできない。*1
var param = function inner() { return typeof inner; }
alert(param()); // prints out "function"
alert(inner()); // nner is not defined
で、これの何が便利のか普通にfunctionキーワードを使った関数宣言でもいいのではないかと思うかもしれないが、再帰を実装する際に便利だとか。(何かこの辺よく分かってない*1 : ただしIE様は外からも参照できるのと、デバッグのためにリテラル関数にも名前を付ける人はいる。
■6.6 Implementing a Recursive Algorithm
再帰的なアルゴリズム関数リテラルで再帰的なものを実装してみると以下のような感じで、通常の関数宣言と同じように実装できます。
var reverseArray = function(x, indx, str) {
return indx == 0 ? str : reverseArray(x, --indx, (str += " " + x[indx]));;
}
var arr = new Array('apple', 'orange', 'peach', 'lime');
var str = reverseArray(arr, arr.length, "");
p(str); //=> lime peach orange apple
var arr2 = ['car', 'boat', 'sun', 'computer'];
str = reverseArray(arr2, arr2.length, "");
p(str); //=> computer sun boat car
再帰でよく出てくるフィボナッチ数列
var fibonacci = function (n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
フィボナッチ数列は再帰なしでも行えるが、再帰でやった方が高速に計算でき、コードもシンプル。だがメモリを喰う。
■6.7 Create a Function That Remembers Its State
関数内でのローカル変数を保持したいと思ったときにどうするかという話。わざわざグローバル変数を作るのは愚作なので、いわゆるクロージャーの話が出てくる。
クロージャとは、「関数自身が定義された環境を、ローカル変数も含めて持ち運ぶことのできる仕組み (またはそうした関数自体) 」の事
JavaScriptクロージャを完全理解!スコープチェインを知る(後編) - page2 - builder by ZDNet Japan
例だと、関数の中に関数があって1度目に実行したときに渡した引数を記憶した関数を返してくれる。
function greetingMaker(greeting) {
function addName(name) {
return greeting + " " + name;
}
return addName; // 関数オブジェクトを返す
}
// Now, create new partial functions
var daytimeGreeting = greetingMaker("Good Day to you");
var nightGreeting = greetingMaker("Good Evening");
// if daytime
alert(daytimeGreeting(name));
// if night
alert(nightGreeting(name));
いわゆるクロージャー(function closure)とスコープの話。ここではまさに「内部関数とクロージャはほぼ同義」というのが理解しやすい。
こっから2ページぐらいクロージャーの説明入ります。
IEにはクロージャーで循環参照するとメモリリークの例が出てきたけど、
XPのSP3で解消してるし、2000/XP SP2はサポート終了したし無視して大丈夫かな。
一応、本にもnewer IEは解消したので平気だけどクロージャー扱う時は少し慎重になった方が良いよと書いてあった。クロージャーはメモリを食べるのでと。
クロージャーについて詳しくは(ここで既に2Pも割いてるのに)Chapter 16で再度やる。
■6.8 Improving Application Performance with a Generalized Currying Function
カリー化について。Dustin Diaz氏によるカリー関数をベースに解説
function curry (fn, scope) {
var scope = scope || window;
var args = [];
for (var i=2, len = arguments.length; i < len; ++i) {
args.push(arguments[i]);
};
return function() {
var args2 = [];
for (var i = 0; i < arguments.length; i++) {
args2.push(arguments[i]);
}
var argstotal = args.concat(args2);
return fn.apply(scope, argstotal);
};
}
function diffPoint (x1, y1, x2, y2) {
return [Math.abs(x2 - x1), Math.abs(y2 - y1)];
}
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);
var newPt = diffOrigin(6.42, 8.0); // array with 3.42, 4
// 6.42 - 3.0 = 3.42, 8.0 -4.0= 4
ここでのdiffPoint関数は2つのペア(x1とy1 , x2とy2)の差を求めたいという関数であるが、カリー関数を使わないでやると一度にまとめて引数を渡さないといけない。
function diffPoint (x1, y1, x2, y2) {
return [x2 - x1, y2 - y1];
}
何回かに分けて引数を渡したいときに使うのがカリー関数である。まず一回目の呼び出しで引数を渡して、その引数を持った関数を返す。(要はクロージャー)
このとき、第一引数が実行する関数であり、第二引数がスコープ(this)になるもので、それ以降が実際に実行されるとき(第一引数で渡した関数)に引数となるもの。
var diffOrigin = curry(diffPoint, null, 3.0, 4.0);// function返ってきた関数にまた引数を渡すと、内部でconcatされて2回に分けた引数が連結されて、
Function applyを使って一回目に渡した関数を連結した引数で実行する。
var newPt = diffOrigin(6.42, 8.0); // array with 3.42, 4こういうすることで引数を複数回に分けて渡して関数を実行することで可能になる。
■6.9 Improve Application Performance with Memoization(Caching Calculations)
次は少し前にやったフィボナッチ数列のメモ化について。フィボナッチ数列自体についてはこの本以外でもフィボナッチ数列で触れた
出展The Good Parts の内容。
var fibonacci = function () {
var memo = [0,1];
var fib = function (n) {
var result = memo[n];
if (typeof result != "number") {
result = fib(n -1) + fib(n - 2);
memo[n] = result;
}
return result;
};
return fib;
}();
fibonacci(23) // 28657 number
要は階乗の計算を3!=1*2*3 、4!=1*2*3*4みたいに毎回全部計算するのは無駄なので、4!=3! * 4 とした方が処理が圧倒的に少なくて済む。
このメモ化によるキャッシュ機能はJava, Perl, Lispなどはビルトインされているらしい。
実際にメモ化したものとしてないものフィボナッチ数列の比較 - Memoized Function · jsPerf
早さだけじゃなくて、メモ化してないと単純に大きな数字でやったとき固まって終わるぐらいの差がでる。
■6.10 Using an Anonymous Function to Wrap Global Variables
グローバルなものを一切作りたくないって時には無名関数で全体を囲って実行すればOK
(function() {
// 処理
})();
jQuery使うときに以下みたいに無名関数に引数を渡してやるとかもあり
(function($) {
// 処理
})(jQuery);
これについてはGreasemonkeyスクリプト全体を無名関数で囲う意味 - prog*sigとかfunctionについて - prog*sigとかでかなり詳しくやった記憶がある。少し難しくなってきた章だった。
コメント(0件)
- TB-URL http://efcl.info/adiary/063/tb/