JavaScriptの練習帳
リンク
Code Conventions for the JavaScript Programming Language
連載:Ajax時代のJavaScriptプログラミング再入門 − @IT
javascript.el
ひげぽん氏のブログで紹介されていて人気があるらしい。
配布サイト: http://www.brgeight.se/
Version: 2.0 Beta 8
;;; javascript.el --- Major mode for editing JavaScript source text ;; Copyright (C) 2006 Karl Landstr旦m ;; Author: Karl Landstr旦m <****************> ;; Maintainer: Karl Landstr旦m <****************> ;; Version: 2.0 Beta 8 ;; Date: 2006-12-26
文字列や正規表現が正常にフォントロックされない不具合がある。2.0 Beta 8 の修正版もあるが完全ではないようだ。
入手先:
http://joost.zeekat.nl/category/javascript/
(http://joost.zeekat.nl/2007/10/31/javascript-regex-en-string-literal-highlighting-in-emacs/)
http://joost.zeekat.nl/wp-content/javascript.el
;;; javascript.el --- Major mode for editing JavaScript source text ;; This version contains a few font-lock fixes for quoted strings ;; and regular expressions by Joost Diepenmaat <****************> ;; Joost's version - 2007-11-29
EmacsからJavaScript Lintを使う(構文チェッカー)
参考記事: JavaScript Lint (Emacsのflymakeを使った構文チェック: flymake-jsl.el)
サイト: http://www.javascriptlint.com/
参考記事の flymake-jsl.el を利用すると、Emacs に付属している flymake を使って、JavaScriptコードのリアルタイム構文チェックができるようになる。当初、何か設定を間違えて上手くいかなかったので、以下のように compile コマンドに jsl を指定する方法を行った。(flymakeはリアルタイムでエラー箇所をハイライトしてくれるのでやはり便利だ。しかし、compileコマンドを使う方法も良いと思うので両方設定している。)
- wget http://www.javascriptlint.com/download/jsl-0.3.0-src.tar.gz
- 展開後 jsl-0.3.0/src 下で、$ gmake -f Makefile.ref
- jsl-0.3.0/src/Linux_All_DBG.OBJ にパスを通す
- Emacs の設定
;;; flymake でリアルタイム構文チェック ;; flymake-jsl.el を load-path にコピー (add-hook 'javascript-mode-hook (lambda () (require 'flymake-jsl) (setq flymake-check-was-interrupted t))) ;;; compile コマンドを利用して構文チェック ;; C-c C-c で構文チェック (add-hook 'javascript-mode-hook (lambda () ;; JavaScript Lint (set (make-local-variable 'compile-command) (concat "jsl -process " buffer-file-name)) (define-key javascript-mode-map "\C-c\C-c" 'compile)) ) ;; 以下好みで ;; C-c C-c 後、*compilation* ウィンドウにフォーカス (defadvice compile (after focus-on-compilation-window activate) (let ((win (get-buffer-window "*compilation*"))) (when win (select-window win)))) ;; q で *compilation* ウィンドウを閉じる (add-hook 'compilation-mode-hook (lambda () (define-key compilation-mode-map "q" (lambda () (interactive) (delete-windows-on (current-buffer))))) )
Firebug
本家サイト: http://www.getfirebug.com/
日本語サイト: http://www.getfirebug.com/jp.html
関数定義の3つの方法
- function文(宣言型関数: 静的関数)
- Functionコンストラクタ(無名関数: 動的関数)
- 関数リテラル(関数式)
1. function文
以下のコードをFirebugのテキストエディタに貼り付けて実行できる。
function factorial(n) { return n === 0 ? 1 : n * factorial(n - 1); } factorial(10); // 3628800
関数はオブジェクト。
function factorial(n) { return n === 0 ? 1 : n * factorial(n - 1); } window.alert(factorial); // メッセージボックス
宣言型関数は静的な関数で、呼び出し前に評価されている。下のはエラーにならない。
window.alert(factorial); // メッセージボックス function factorial(n) { return n === 0 ? 1 : n * factorial(n - 1); }
Functionオブジェクトのlengthプロパティ使って関数の引数の数を取得できる。
alert(factorial.length); // 1
2. Functionコンストラクタ(無名関数)
関数はオブジェクトなので、new演算子を使っても生成できる。末尾の引数が関数本体で、それ以外は関数引数。
var factorial = new Function("n", "return n == 0 ? 1 : n * factorial(n - 1);"); factorial(10); // 3628800
Functionコンストラクタによる関数は動的に生成されるので、宣言型関数と異なり定義前の呼び出しはエラーとなる。
factorial(10); // エラー: factorial is not a function var factorial = new Function("n", "return n == 0 ? 1 : n * factorial(n - 1);");
※動的に生成されるため、ループの中での使用は非推奨。
3. 関数リテラル
Functionコンストラクタを使わなくても関数は定義できる。
var factorial = function(n) { return n == 0 ? 1 : n * factorial(n - 1); } factorial(10); // 3628800
ここでは、次のように無名関数の場合、再帰的に呼び出す際に、名前がないのでcalleeプロパティを利用する必要があると書いてあるが、上のコードのように普通に呼び出せるのはなぜか?
=>名前を付けた場合は、関数本体の中からも参照できる(再帰)。
var factorial = function(n) { return n === 0 ? 1 : n * arguments.callee(n-1); } factorial(10); // 3628800
jsでも実行できる。
js> var factorial = function(n) { return n == 0 ? 1 : n * factorial(n - 1); } var factorial = function(n) { return n == 0 ? 1 : n * factorial(n - 1); } js> factorial(10); factorial(10); 3628800
たぶん、次のように無名関数を直接適用するような場合のことを言っているのかな。この場合は参照すべき名前がない。
(function(n){ return n == 0 ? 1 : n * arguments.callee(n - 1); })(10); // 3628800
関数リテラルは式なので引数に適用したり、関数を引数として渡すことができる。
(function(x, y, z){ return z(x, y); })(1, 2, function(x, y){ return x + y; }); // 3
このへんはSchemeやEmacsLispと同じ。
gosh> ((lambda (x y z) (z x y)) 1 2 (lambda (x y) (+ x y))) 3 ELISP> ((lambda (x y z) (funcall z x y)) 1 2 (lambda (x y) (+ x y))) 3
関数リテラルとScheme
factorial(階乗)をJavaScriptとScheme(Gauche)で比較してみる。
名前つきの場合:
var factorial = function(n) { return n == 0 ? 1 : n * factorial(n - 1); }
(define factorial (letrec ((f (lambda (n) (if (= n 0) 1 (* n (f (- n 1))))))) f)) (define factorial (rec f (lambda (n) (if (= n 0) 1 (* n (f (- n 1)))))))
名前なしの場合:
(function(n){ return n == 0 ? 1 : n * arguments.callee(n - 1); })(10);
((letrec ((f (lambda (n) (if (= n 0) 1 (* n (f (- n 1))))))) f) 10) ((rec f (lambda (n) (if (= n 0) 1 (* n (f (- n 1)))))) 10)
練習1
JavaScriptにはあまり慣れていないので、とにかく関数を定義してみる。SICPに出てくる例題を素材にするか。
高階関数1
// aからbまでの整数の和を計算する。 // for function sumIntegers(a, b) { var ans = 0; for ( ; a <= b; ++a) { ans += a; } return ans; } // while function sumIntegers(a, b) { var ans = 0; while (a <= b) { ans += a; ++a; } return ans; } // do...while function sumIntegers(a, b) { var ans = 0; do { ans += a++; } while (a <= b) return ans; } // JavaScript Lintで警告が出る。 // lint warning: increment (++) and decrement (--) operators used as part of greater statement // do { ans += a++; } while (a <= b) // ...................^ // 再帰 function sumIntegers(a, b) { return a > b ? 0 : a + sumIntegers(a + 1, b); } window.alert(sumIntegers(1,10)); // 55 // 無名関数 (function(a, b) { var ans = 0; do { ans += a; ++a; } while (a <= b) return ans; })(1,10); // 55
// aからbまでの整数の三乗の和を計算する。 // 関数内関数 function sumCube(a, b) { function cube(x) { return x * x * x; } var ans = 0; while (a <= b) { ans += cube(a); ++a; } return ans; } sumCube(1,10); // 3025 >>> cube(10); cube is not defined // sumCubeの外部からは参照できない // 再帰 function cube(x) { return x * x * x; } function sumCube(a, b) { return a > b ? 0 : cube(a) + sumCube(a + 1, b); } sumCube(1,10); // 3025
// πの近似(Leibniz) function piSum(a, b) { var ans = 0; while (a < b) { ans += 1.0 / ((a + 2) * a); a += 4; } return ans; } // 再帰 function piSum(a, b) { return a > b ? 0 : (1.0 / ((a + 2) * a)) + piSum(a + 4, b); } window.alert(8 * piSum(1,1000)); // 3.139592655589783
// sumIntegersとsumCubeとpiSumを高階関数sumを使って定義する。 function inc(x) { return x + 1; } function identity(x) { return x; } // sum(反復) function sum(term, a, next, b) { var ans = 0; while (a <= b) { ans += term(a); a = next(a); } return ans; } // sum(再帰) function sum(term, a, next, b) { return a > b ? 0 : term(a) + sum(term, next(a), next, b); } // sumIntegers function sumIntegers(a, b) { return sum(identity, a, inc, b); } window.alert(sumIntegers(1,10)); // 55 // sumCube function sumCube(a, b) { return sum( function(x) { return x * x * x; }, a, inc, b); } window.alert(sumCube(1,10)); // 3025 // piSum function piSum(a, b) { return sum( function(x) { return 1.0 / ((x + 2) * x); }, a, function(x) { return x + 4; }, b); } window.alert(8 * piSum(1,1000)); // 3.139592655589783
合成関数
// fとgを1引数関数として、gの後のfの合成関数compose。 function compose(f, g) { return function(x) { return f(g(x)); }; } (compose(Math.sqrt, function(x) { return x + 1; }))(1); // 1.4142135623730951
// fを数値関数、nを正整数として、fのn回作用関数repeated(composeを使う)。 function compose(f, g) { return function(x) { return f(g(x)); }; } // 再帰 function repeated(f, n) { return n == 1 ? f : compose(f, repeated(f, n - 1)); } // 反復 function repeated(f, n) { var ans = f; while (n > 1) { ans = compose(f, ans); --n; } return ans; } function square(x) { return x * x; } (repeated(square, 2))(5); // 625 // square(square(5)); // 625
高階関数2
// 階乗 // while function factorial(n) { var ans = 1; while (n > 0) { ans *= n; --n; } return ans; } // for function factorial(n) { var ans = 1; for ( ; n > 0; --n) { ans *= n; } return ans; } // do...while function factorial(n) { var ans = 1; do { ans *= n; --n; } while (n > 0) return ans; } factorial(10); // 3628800
// πの近似(John Wallis) function piProduct(a, b) { var ans = 1; while (a <= b) { ans *= (4 * (Math.pow(a, 2) + a)) / Math.pow(a * 2 + 1, 2); ++a; } return ans; } piProduct(1, 1000) * 4; // 3.1423773650938855 // 再帰 // function piProduct(a, b) { // return a > b // ? 1 // : ((4 * (Math.pow(a, 2) + a)) / Math.pow(a * 2 + 1, 2)) * piProduct(a + 1, b); // } // // piProduct(1, 1000) * 4; // too much recursion
// 高階関数sumと同様の高階関数productを使って、factorialとpiProductを定義する。 // product(再帰) function product(term, a, next, b) { return a > b ? 1 : term(a) * product(term, next(a), next, b); } // product(反復) function product(term, a, next, b) { var ans = 1; while (a <= b) { ans *= term(a); a = next(a); } return ans; } function inc(x) { return x + 1; } function identity(x) { return x; } // factorial function factorial(n) { return product(identity, 1, inc, n); } window.alert(factorial(10)); // 3628800 // piProduct function piProduct(a, b) { return product( function(x) { return (4 * (Math.pow(x, 2) + x)) / Math.pow(x * 2 + 1, 2); }, a, inc, b ); } // 再帰の場合、998程度なら計算できる。 window.alert(piProduct(1, 1000) * 4); // 3.1423773650938855
// 高階関数sumとproductよりもさらに一般的な高階関数accumを使って、 // factorialとpiProductを定義する。 // 再帰 function accum(combiner, init, term, a, next, b) { return a > b ? init : accum(combiner, combiner(term(a), init), term, next(a), next, b); } // 反復 function accum(combiner, init, term, a, next, b) { while (a <= b) { init = combiner(term(a), init); a = next(a); } return init; } function identity(x) { return x; } function inc(x) { return x + 1; } function mul(x, y) { return x * y; } // factorial function factorial(n){ return accum(mul, 1, identity, 1, inc, n); } window.alert(factorial(10)); // 3628800 // piProduct function piProduct(a, b) { return accum( mul, 1, function(x){ return (4 * (Math.pow(x, 2) + x)) / Math.pow(x * 2 + 1, 2); }, 1, inc, b ); } // 再帰の場合、998程度なら計算できる。 window.alert(piProduct(1, 1000) * 4); // 3.1423773650938855
// 高階関数accumを使って、sumIntegersとsumCubeとpiSumを定義する。 // 再帰 function accum(combiner, init, term, a, next, b) { return a > b ? init : accum(combiner, combiner(term(a), init), term, next(a), next, b); } // 反復 function accum(combiner, init, term, a, next, b) { while (a <= b) { init = combiner(term(a), init); a = next(a); } return init; } function identity(x) { return x; } function inc(x) { return x+1; } function add(x, y) { return x + y;} // sumIntegers function sumIntegers(a, b) { return accum(add, 0, identity, 1, inc, b); } window.alert(sumIntegers(1, 10)); // 55 // sumCube function sumCube(a, b) { return accum( add, 0, function(x){ return x * x * x; }, a, inc, b ); } window.alert(sumCube(1, 10)); // 3025 // piSum function piSum(a, b) { return accum( add, 0, function(x){ return 1.0 / ((x + 2) * x); }, a, function(x){ return x + 4; }, b ); } window.alert(8 * piSum(1, 1000)); // 3.139592655589782
argumentsオブジェクト
- 関数の内部でのみ利用可能なオブジェクト
- 関数に渡された引数を格納する配列のようなオブジェクト
- 関数が呼ばれると生成される
次の情報を参照できる。
- 引数の数(arguments.length)
- 引数の値(arguments[0], arguments[1]...)
- 関数自身(arguments.callee)
argumentsオブジェクトを使うと可変個の引数を扱える。
// 渡された可変個の整数引数の平均を求める。 // for function average() { var ans = 0, n = arguments.length; for (var i = 0; i < n; ++i) { ans += arguments[i]; } return ans / n; } // while function average() { var ans = 0, n = arguments.length; while (n--) { ans += arguments[n]; } return ans / arguments.length } // lint warning: increment (++) and decrement (--) operators used as part of greater statement // while (n--) { ans += arguments[n]; } // ..............^ window.alert(average(1,2,3,4)); // 2.5
// argumentsと同じ内容の配列を返す(1) // 参考書籍: 入門JavaScript (久野靖 著 ASCII) // 「1引数のArrayコンストラクタでその引数が整数のときは、その個数分の要素を持つ配列を作り出す。」 function args() { return Array.apply(null, arguments); // nullの代わりにthisでも良いのか? } args(1,2,3,4,5); // [1, 2, 3, 4, 5] args(1); // [undefined]
// argumentsと同じ内容の配列を返す(2) function args() { return Array.apply(this, arguments); // thisでも同じ結果になる } args(1,2,3,4,5); // [1, 2, 3, 4, 5] args(1); // [undefined]
// 可変個の整数引数の総和を求める。 function sigma() { var ans = 0; for (var i = 0, n = arguments.length; i < n; ++i) { ans += arguments[i]; } return ans; } window.alert(sigma(1,2,3,4,5,6,7,8,9,10)); // 55 // 再帰 function sigma() { if (arguments.length == 1) { return arguments[0]; } else { var arr = Array.apply(null, arguments); // arr: argumentsと同じ内容の配列 var elm = arr.shift(); // elm: 先頭要素を除き、その要素を返す return elm + arguments.callee.apply(null, arr) // null: thisでも同じ結果になる } } window.alert(sigma(1,2,3,4,5,6,7,8,9,10)); // 55
;; Schemeの場合 (define (sigma . x) (if (null? x) 0 (+ (car x) (apply sigma (cdr x))))) (sigma 1 2 3 4 5 6 7 8 9 10) ; 55
// argumentsと同じ内容の配列を返す(3) // for function args() { var arr = []; for (var i = 0, n = arguments.length; i < n; ++i) { arr.push(arguments[i]); } return arr; } window.alert(args(1,2,3,4,5)); // push function args() { var arr = []; arr.push.apply(arr, arguments); return arr; } window.alert(args(1,2,3,4,5)); // concat function args() { var arr = []; return arr.concat.apply(arr, arguments); } window.alert(args(1,2,3,4,5)); // slice function args(){ return [].slice.apply(arguments); } window.alert(args(1,2,3,4,5));
// 次のような方法もある。 function args() { return [].slice.call(arguments); } alert(args(1,2,3,4,5));
可変個の引数を取るSchemeのiota関数。
// listTabulate function listTabulate(n, f) { var ans = []; for (var i = 0; i < n; ++i) { ans.push(f(i)); } return ans; } function compose(f, g) { return function(x) { return f(g(x)); }; } listTabulate(5, compose(Math.sqrt, function(x){ return 2 * x + 1; })); // [1, 1.7320508075688772, 2.23606797749979, 2.6457513110645907, 3] // iota function iota(count, start, step) { start = start || 0; step = step || 1; return listTabulate(count, function(i){ return start + step * i; }); } iota(10); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] iota(10, 10); // [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] iota(10, 1, 2); // [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
変数のスコープ
ローカルスコープとグローバルスコープ
var x = 1; function scopeTest() { alert(x); // x は宣言された関数全体で有効だが初期化されていないので undefined var x = 0; alert(x); // 0 return x; } alert(scopeTest()); // 0 alert(x); // 1
JavaScript はブロックスコープを持たない。
var x = 1; { var x = 2; } alert(x); // 2
functionを使った擬似ブロックスコープ
function blockTest() { var x = 1; alert(x); // 1 (function(){ var x = 2; alert(x); // 2 })(); return x; } alert(blockTest()); // 1
withブロックと匿名オブジェクトを使った擬似ブロックスコープ
function blockTest() { var x = 1; alert(x); // 1 with ({x:2}) { alert(x); // 2 } return x; } alert(blockTest()); // 1
with(式) 文: 式が表わすオブジェクトを実行コンテキストとして文を実行する。
ブロック内で共通して利用するオブジェクトを指定でき、オブジェクトのメンバにアクセスする場合、「オブジェクト.メンバ」ではなく直接「メンバ」と記述できる。
var a, x, y; var r = 10; with (Math) { alert(PI * r * r); alert(r * cos(PI)); alert(r * sin(PI / 2)); }
関数内関数の変数スコープ
なぜ、次のように結果が異なるのか? 参考ページ
var x = 1; function scopeTest() { var x = 2; // Functionコンストラクタ // グローバル変数を参照している var Func1 = new Function("", "alert(x);"); Func1(); // 1 // 関数リテラル // ローカル変数を参照している var Func2 = function() { alert(x); }; Func2(); // 2 } scopeTest();
数値型(number) 文字列型(string) 基本型 真偽型(boolean) データ型 特殊型(null/undefined) 配列(array) 参照型 オブジェクト(object) 関数(function)
練習2
データの整列 (どう書く?org) の課題を素材にして、乱数・配列のコピー(浅いコピー)・ソートをやってみる。
// まず整列すべきデータを作る // 0 から n-1 までの整数の乱数を返す。 function randomInteger(n) { with(Math) { return floor(random() * n); } } // JavaScript Lintで警告が出るがとりあえず無視 // function randomInteger(n) { with(Math) { return floor(random() * n); } } // ............................^ // x,y 座標を要素とするn個の配列を生成する function makeData(n) { var ans = []; while (n--) { ans.push([randomInteger(10), randomInteger(10)]); } return ans; } // data1 = makeData(10); // 10 個の座標を要素とする配列 // [[1, 2], [7, 0], [7, 2], [0, 5], [3, 5], [7, 8], [1, 0], [0, 1], [6, 1], [0, 0]] data1 = [[1, 2], [7, 0], [7, 2], [0, 5], [3, 5], [7, 8], [1, 0], [0, 1], [6, 1], [0, 0]] // 辞書順でソートする関数 function dictSort(arr) { var tmp = [].slice.call(arr); return tmp.sort(function(x, y){ if (x[0] < y[0]) { return -1; } else if (x[0] == y[0]) { return x[1] < y[1] ? -1 : 1; } }); } dictSort(data1); // [[0, 0], [0, 1], [0, 5], [1, 0], [1, 2], [3, 5], [6, 1], [7, 0], [7, 2], [7, 8]] // [0, 0] からの距離順でソートする関数 function distSort(arr) { function dist(a, b){ return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); } var tmp = [].slice.call(arr); return tmp.sort(function(x, y){ return dist(x[0], x[1]) < dist(y[0], y[1]) ? -1 : 1; }); } distSort(data1); // [[0, 0], [0, 1], [1, 0], [1, 2], [0, 5], [3, 5], [6, 1], [7, 0], [7, 2], [7, 8]] // 距離順になっているかを確認するためmapを定義して調べる function map1(f, arr) { var ans = []; for (var i = 0, n = arr.length; i < n; ++i) { ans.push(f(arr[i])); } return ans; } map1(function(x){ return Math.sqrt(Math.pow(x[0], 2) + Math.pow(x[1], 2))}, [[0, 0], [0, 1], [1, 0], [1, 2], [0, 5], [3, 5], [6, 1], [7, 0], [7, 2], [7, 8]]); // [0, 1, 1, 2.23606797749979, 5, 5.830951894845301, 6.082762530298219, 7, 7.280109889280518, 10.63014581273465]
一応、深いコピーもやってみよう。
// treeCopy 配列の深いコピー // 再帰 function treeCopy(tree) { var ans = []; for (var i = 0, n = tree.length; i < n; ++i) { tree[i].constructor == Array ? ans.push(treeCopy(tree[i])) : ans.push(tree[i]); } return ans; } data1 = [1,[2,3,4],[5,[6,7,8]]]; // [1, [2, 3, 4], [5, [6, 7, 8]]] x = treeCopy(data1); // [1, [2, 3, 4], [5, [6, 7, 8]]] data1[1] = 2; // 2 data1 // [1, 2, [5, [6, 7, 8]]] x // [1, [2, 3, 4], [5, [6, 7, 8]]] data1[2][1][0] = 100; // 100 data1 // [1, 2, [5, [100, 7, 8]]] x // [1, [2, 3, 4], [5, [6, 7, 8]]]
treeCopy は、上で定義した高階関数 map1 を使えばもっと簡単になる。
// 高階関数を使った treeCopy function map1(f, arr) { var ans = []; for (var i = 0, n = arr.length; i < n; ++i) { ans.push(f(arr[i])); } return ans; } // 再帰とmap function treeCopy(tree) { return map1( function(sub){ return sub.constructor == Array ? treeCopy(sub) : sub; }, tree ); } data2 = [[1,2],[[[3],4],5]]; // [[1, 2], [[[3], 4], 5]] x = treeCopy(data2); // [[1, 2], [[[3], 4], 5]] data2[1][0][0][0] = 100; // 100 data2; // [[1, 2], [[[100], 4], 5]] x; // [[1, 2], [[[3], 4], 5]]
ついでに treeMap を定義してみた。treeCopy とほとんど同じコードになる。
function map1(f, arr) { var ans = []; for (var i = 0, n = arr.length; i < n; ++i) { ans.push(f(arr[i])); } return ans; } // 再帰とmap function treeMap(f, tree) { return map1( function(s){ return s.constructor == Array ? treeMap(f, s) : f(s); }, tree ); } tree1 = [[1,2,3],[4,[5,[6]],[7,8]], 9]; treeMap(function(x){return x*x;}, tree1); // [[1, 4, 9], [16, [25, [36]], [49, 64]], 81]
一応、高階関数を使わない treeMap も定義しておこう、といってもこれも treeCopy とほとんど同じだ。
// treeMap function treeMap(f, tree) { var ans = []; for (var i = 0, n = tree.length; i < n; ++i) { tree[i].constructor == Array ? ans.push(treeMap(f, tree[i])) : ans.push(f(tree[i])); } return ans; } tree1 = [[1,2,3],[4,[5,[6]],[7,8]], 9]; treeMap(function(x){return x*x;}, tree1); // [[1, 4, 9], [16, [25, [36]], [49, 64]], 81]