2021-04-25

[Flash] JavaScript Ninja

[Flash] JavaScript Ninja

Environment:

  • Adobe AIR SDK 29
  • FlashDevelop 5.3.3.1
  • Windows 10

[話者] 書籍『JavaScript Ninjaの極意』を読んだ。

日本語版は英語の第1版(2013)の翻訳だから、ES6非対応。英語の第2版(2016)はES6対応してるが、今回は(買ったけど)読まない。

第3章「関数が肝心」では関数の引数とか this とか学んだ。

package {
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.setTimeout;
[SWF(width="640", height="480", frameRate="30", backgroundColor="#eeeeee")]
public class Main extends Sprite {
    public static var STAGE_W:int = 640;
    public static var STAGE_H:int = 480;
    public function Main() {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }
    private function init(e:Event = null):void {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // entry point
        test1();
    }
    
    private function assert(value,desc){trace(desc+".. "+(value?"pass":"FAIL"))};
    
    private function test1():void {
        // 引数が不足/余る場合を調査
        var whatever = function(a,b,c){
            trace('a:'+a+' ('+typeof(a)+')');
            trace('b:'+b+' ('+typeof(b)+')');
            trace('c:'+c+' ('+typeof(c)+')');
            for (var n=0; n<arguments.length; n++){
                trace('arg '+n+':'+arguments[n]+' ('+typeof(arguments[n])+')');
            }
        };
        whatever(1); // -> a:1 (number), b:undefined (undefined), c:undefined (undefined), arg 0:1 (number)
        whatever('a','b','c','d'); // -> a:a (string), b:b (string), c:c (string), arg 0:a (string), arg 1:b (string), arg 2:c (string), arg 3:d (string)
        
        // 呼び出しコンテキスト=this を調査
        var func1 = function(){return this};
        var func2 = func1;
        trace(this); // -> [object Main]
        // 関数呼び出しでは this はグローバル
        //assert(func2() == window, "func2 == window");
        assert(func2() == Main as Object, "func2 == Main"); // -> func2 == Main.. FAIL
        trace(func2(this)); // -> [object global]
        
        // メソッド呼び出しでは this が(メソッドを所有する)オブジェクトになる
        var Class1 = { myfunc: func1 };
        assert(Class1.myfunc() == Class1, "1st Class"); // -> 1st Class.. pass
        
        var Instance2 = { myfunc: func1 };
        assert(Instance2.myfunc() == Instance2, "2nd Instance2"); // -> 2nd Instance2.. pass
        
        // コンストラクター呼び出し
        var Class2 = function(){this.myfunc = function(){return this;}}
        var instance3 = new Class2();
        var instance4 = new Class2();
        assert(instance3.myfunc() == instance3, '3rd instance3'); // -> 3rd instance3.. pass
        assert(instance4.myfunc() == instance4, '4th instance4'); // -> 4th instance4.. pass
        
        // apply/call で呼び出しコンテキストを自由に指定
        var func_juggle = function(){
            var result = 0;
            for (var n=0; n<arguments.length; n++){ result += arguments[n]; }
            this.result = result;
        }
        var obj5 = {};
        var obj6 = {};
        func_juggle.apply(obj5, [1,2,3,4]);
        func_juggle.call(obj6, 1,2,3,4);
        assert(obj5.result === 10, 'obj5'); // -> obj5.. pass
        assert(obj6.result === 10, 'obj6'); // -> obj6.. pass
        
        // 命令型でなく宣言型(関数型言語)のように扱うための for-each
        var for_each = function(list, callback){
            for (var n=0; n<list.length; n++){ callback.call(list[n], n); };
            //list[n]..context(=this), n..arguments[0]
        }
        var weapons = ['shuriken', 'katana', 'nunchuck'];
        for_each(
            weapons,
            function(index){
                trace('this:' + this + ' (' + (typeof this)+ ')');
                assert(this == weapons[index], weapons[index] + ' got '+ index + '.');
            }
        );
        // -> this:shuriken (string), shuriken got 0... pass
        // -> this:katana (string), katana got 1... pass
        // -> this:nunchuck (string), nunchuck got 2... pass
    }
}}

第4章「関数を使いこなす」でも引数の扱いなど。

    private function test1():void {
        // 再帰をつかって回文かを判定する
        var isPalindrome = function(txt){
            if(txt.length<=1) return true;
            if(txt.charAt(0)!=txt.charAt(txt.length-1)) return false;
            return isPalindrome(txt.substr(1,txt.length-2));
        }
        assert(isPalindrome('abc') == true, 'abc is palindrome?');     // -> abc is palindrome?.. FAIL
        assert(isPalindrome('azaza') == true, 'azaza is palindrome?'); // -> azaza is palindrome?.. pass
        assert(isPalindrome('azza') == true, 'azza is palindrome?');   // -> azza is palindrome?.. pass
        
        // 再帰
        var dog1={bark:function(n){return n>1 ? dog1.bark(n-1) + '-bark' : 'bark'}};
        assert(dog1.bark(3) == 'bark-bark-bark', 'dog1 barks well. 1'); // -> dog1 barks well. 1.. pass
        var cat2={bark:dog1.bark};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 2'); // -> cat2 barks well. 2.. pass
        dog1 = {};
        //assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 3');
        // [Fault] exception, information=TypeError: Error #1006: bark は関数ではありません。

        // dog1.bark()ではなく this.bark()にすると消えない
        dog1={bark:function(n){return n>1 ? this.bark(n-1) + '-bark' : 'bark'}};
        assert(dog1.bark(3) == 'bark-bark-bark', 'dog1 barks well. 4'); // -> dog1 barks well. 4.. pass
        cat2={bark:dog1.bark};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 5'); // -> cat2 barks well. 5.. pass
        dog1 = {};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 6'); // -> cat2 barks well. 6.. pass

        // 名前付きインライン関数は、名前が見えるのは関数内だけ
        dog1={bark:function signal(n){return n>1 ? signal(n-1) + '-bark' : 'bark'}};
        assert(dog1.bark(3) == 'bark-bark-bark', 'dog1 barks well. 7'); // -> dog1 barks well. 7.. pass
        cat2={bark:dog1.bark};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 8'); // -> cat2 barks well. 8.. pass
        dog1 = {};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 9'); // -> cat2 barks well. 9.. pass
        //assert(cat2.signal(3) == 'bark-bark-bark', 'cat2 barks well. 10');
        // [Fault] exception, information=TypeError: Error #1006: signal は関数ではありません。

        // callee()で関数参照
        dog1={bark:function(n){return n>1 ? arguments.callee(n-1) + '-bark' : 'bark'}};
        assert(dog1.bark(3) == 'bark-bark-bark', 'dog1 barks well. 11'); // -> dog1 barks well. 11.. pass
        cat2={bark:dog1.bark};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 12'); // -> cat2 barks well. 12.. pass
        dog1 = {};
        assert(cat2.bark(3) == 'bark-bark-bark', 'cat2 barks well. 13'); // -> cat2 barks well. 13.. pass
        
        var func_store = {
            nextId:1,
            cache:{},
            add:function(fn){
                if( ! fn.id) {
                    fn.id=this.nextId;
                    this.nextId++;
                    return !! (this.cache[fn.id] = fn);
                    // !! .. bool()
                }
            }
        };
        var func_tmp1 = function(){return null};
        assert(func_store.add(func_tmp1), 'func added? 1'); // -> func added? 1.. pass
        assert(func_store.add(func_tmp1), 'func added? 2'); // -> func added? 2.. FAIL
        
        // メモ化(memoization)とは、計算済み答えをキャッシュする関数を作ること。再計算しないので早いがメモリは消費
        var isPrime = function(v){
            if ( ! isPrime.answers) isPrime.answers = {};
            if (isPrime.answers[v] != null) return isPrime.answers;
            var prime = v != 1;
            for (var i=2; i<v; i++){
                if (v%i==0) {
                    prime=false; break;
                }
            }
            isPrime.answers[v] = prime;
            return prime;
        }
        
        assert(isPrime.answers && isPrime.answers[5], 'answer is cached.'); // -> answer is cached... FAIL
        assert(isPrime(5), '5 is prime?');                                  // -> 5 is prime?.. pass
        assert(isPrime.answers && isPrime.answers[5], 'answer is cached.'); // -> answer is cached... pass
        assert(isPrime(15), '15 is prime?');                                // -> 15 is prime?.. FAIL
        
        // 可変長引数を扱う
        var smallest = function(arr){return Math.min.apply(null, arr)};
        var largest = function(arr){return Math.max.apply(null, arr)};
        assert(smallest([1,0,5,4,3,2]) == 0, 'found smallest?'); // -> found smallest?.. pass
        assert(largest([1,0,5,4,3,2]) == 5, 'found largest?');   // -> found largest?.. pass
        
        // 関数の多重定義
        // JSでは、引数リストによる多重定義ができない。引数の型と個数を調査して振る舞いを変える。
        var merge = function(root){
            for(var i=1; i<arguments.length; i++){
                for (var key in arguments[i]) root[key] = arguments[i][key];
            }
            return root;
        };
        var merged = merge(
            {name:'Name1', city:'City1'},
            {city:'City2'},
            {name:'Name3', sex:'Male3'}
        );
        assert(merged.city == 'City2', 'name merged?'); // -> name merged?.. pass
        assert(merged.sex == 'Male3', 'name merged?');  // -> name merged?.. pass
        
        // 最初の引数に、残りの引数のうち最大の値を乗算する関数
        var multiMax = function(multi){
            return multi * Math.max.apply(null, arguments.slice(1));
        };
        assert(multiMax(3,2,5,4) == 15, 'largest');     // -> largest.. pass
        multiMax = function(multi){
            return multi * Math.max.apply(null, Array.prototype.slice.call(arguments, 1));
        };
        assert(multiMax(3,2,5,4) == 15, 'largest');     // -> largest.. FAIL
        
        // 引数の数によって関数を多重定義する
        var func_arg1 = function(arg1){};
        var func_arg2 = function(arg1,arg2){};
        assert(func_arg1.length==1, 'func length'); // -> func length.. pass
        assert(func_arg2.length==2, 'func length'); // -> func length.. pass
        
        // メソッド多重定義
        var addMethod = function(obj, name, fn){
            var old = obj[name];
            obj[name] = function(){
                if (fn.length == arguments.length)
                    return fn.apply(this, arguments);
                else if (typeof old == 'function')
                    return old.apply(this, arguments);
            }
        };
        var ninjas = {
            values: ['hattori', 'kemumaki', 'iga', 'kouga', 'chikuwa']
        };
        addMethod(ninjas, "find", function(){return this.values});
        addMethod(ninjas, "find", function(name){
            var ret = [];
            for (var i=0; i<this.values.length; i++){
                if (this.values[i].indexOf(name) == 0) ret.push(this.values[i]);
            }
            return ret;
        });
        addMethod(ninjas, "find", function(first, last){
            var ret = [];
            if (this.values.length > first) ret.push(this.values[first]);
            if (this.values.length > last) ret.push(this.values[last]);
            return ret;
        });
        assert(ninjas.find().length == ninjas.values.length, "finds all ninjas."); // -> finds all ninjas... pass
        assert(ninjas.find('hattori').length == 1, "finds a ninja by name.");      // -> finds a ninja by name... pass
        assert(ninjas.find(1,3).length == 2, "finds 2 ninjas by index.");          // -> finds 2 ninjas by index... pass
        assert(ninjas.find('strider','hiryu','ninnin') == null, "finds no one.");  // -> finds no one... pass
    }

第5章「クロージャ」

    private function test1():void {
        // closureは func4()宣言時の変数をキープする。func3=nullになってもf2に残っている。
        var v1 = 'v1';
        var f2;
        var func3 = function(){
            var v2 = 'v2';
            var func4 = function(p4){
                assert(v1,'v1 is seen');
                assert(v2,'v2 is seen');
                assert(v3,'v3 is seen');
                assert(p4,'p4 is seen');
            }
            f2 = func4;
        }
        trace('func3');  // -> func3
        func3();         // (nothing)
        func3 = null;
        var v3 = 'v3';
        trace('f2');     // -> f2
        f2(1);           // -> v1 is seen.. pass, v2 is seen.. pass, v3 is seen.. pass, p4 is seen.. pass
        // func4 = null;
        // Error: Access of possibly undefined property func4.

                // closureでプライベート変数を再現
        var Class5 = function(){
            var cnt = 0;
            this.getCnt=function(){return cnt};
            this.addCnt=function(){cnt++};
        };
        var c6 = new Class5();
        c6.addCnt();
        trace('c6.cnt='+c6.cnt);              // -> c6.cnt=undefined 直接アクセスは不可
        trace('c6.getCnt() -> '+c6.getCnt()); // -> c6.getCnt() -> 1
        c6.newcnt = 5;
        trace('c6.newcnt='+c6.newcnt);        // -> c6.newcnt=5
        
        // 無名関数内部にカウンタ(private var)を持つことで、複数のタイマーを同時作動可能
        var animateIt = function(itv){
            var tick = 0;
            var timer = setInterval(function(){
                if (tick < itv) {tick++;}
                else {
                    clearInterval(timer);
                    assert(tick == itv, "stop at " + tick);
                    assert(timer, "timer is " + timer);
                }
            }, 20);
        };
        animateIt(250);
        animateIt(50); // 20msec*50=1000msec=1sec
        animateIt(100);
        // -> stop at 50.. pass
        //    timer is 2.. pass
        //    stop at 100.. pass
        //    timer is 3.. pass
        //    stop at 250.. pass
        //    timer is 1.. pass
        
        // 関数の部分適用(partial apply)
        //String.prototype.divide_csv = String.prototype.split.partial(/,\s*/);
        //var results = "AB, CD, EF".divide_csv();
        //trace(results);
        // <- AS3 cannot set StaticClass.prototype
        
        // 既存の関数にメモ化を導入する
        var memoized = function(context, key){
            context.values = context.values || {};
            //if (context.values[key] !== undefined) {
            //  return context.values[key];
            //} else {
            //  context.values[key] = context.apply(context, arguments);
            //  return context.values[key];
            //}
            return context.values[key] !== undefined ?
                context.values[key] :
                context.values[key] = context.apply(context, arguments);
        };
        assert(memoized(isPrime, 5)==true, '5 is prime.'); // -> 5 is prime... pass
        assert(isPrime.values[5], 'answer is cached.');    // -> answer is cached... pass
        // 関数にmemoized()かます面倒を省く
        var isPrime2 = function(arg){return memoized(isPrime,arg)};
        assert(isPrime2(17)==true, '17 is prime.');  // -> 17 is prime... pass
        assert(isPrime.values[17]==true, 'cached.'); // -> cached... pass
        
        // 即時関数
        (function(what){trace(what);})('Hello immediate function');   // -> Hello immediate function
        
        // 短い名前でコードを読みやすく
        var too_long_too_long_function = function(v){trace(v + ':' + v);};
        (function(fn){fn('a'); fn('b'); fn('c');})(too_long_too_long_function);  // -> a:a,  b:b,  c:c
        
        // 即時関数にしておかないとiが全部5(loop後の値)になる。
        var ninjas = ['hattori', 'kemumaki', 'iga', 'kouga', 'chikuwa'];
        for(var i=0; i<ninjas.length; i++) {
            (function(n){
                setTimeout(function(){trace('ninja['+n+'] is '+ninjas[n])}, Math.random()*5000);
            })(i);
        }
        // -> ninja[4] is chikuwa
        //    ninja[2] is iga
        //    ninja[0] is hattori
        //    ninja[3] is kouga
        //    ninja[1] is kemumaki
    }

第6章 プロトタイプによるオブジェクト指向。

    private function test1():void {
        var Class1 = function(){};
        Class1.prototype.swing = function(){return true};
        
        var n1 = Class1();      // 関数として呼び出す
        assert(n1 === undefined, "did not create");   // -> did not create.. pass
        
        var n2 = new Class1();  // コンストラクタとして呼び出す
        assert(n2 && n2.swing && n2.swing(), "created"); // -> created.. pass
        
        var Class2 = function(){
            this.swung = false;
            this.swing = function(){return ! this.swung};
        };
        Class2.prototype.swing = function(){return this.swung};
        var n3 = new Class2();
        Class2.prototype.move = function(){return true};
        // インスタンスメソッド優先
        assert(n3.swing(), "instance method");     // -> instance method.. pass
        assert( ! n3.swing(), "prototype method"); // -> prototype method.. FAIL
        // プロトタイプの変更が動的に反映
        assert(n3.move(), "dynamic prototype method"); // -> dynamic prototype method.. pass

        assert(typeof n3 == "object", "object type");     // -> object type.. pass
        assert(n3 instanceof Class2, "instanciated from");//    instanciated from.. pass
        assert(n3.constructor == Class2, "instance from"); //    instance from.. pass

        //var n4 = new n3.constuctor(); // [Fault] exception, information=TypeError: Error #1007
        //assert(n4 instanceof Class2, "instance from");
        //assert(n3 !== n4, "not same instance");
        
        // プロトタイプによって継承
        var ParentClass = function(){};
        ParentClass.prototype.dance = function(){};
        var ChildClass = function(){};
        ChildClass.prototype = new ParentClass();
        var n5 = new ChildClass();
        assert(n5 instanceof ChildClass, "extends ChildClass"); // -> extends ChildClass.. pass
        assert(n5 instanceof ParentClass, "extends ParentClass"); //  extends ParentClass.. pass
        assert(n5 instanceof Object, "extends Object");           //  extends Object.. pass
        assert(typeof n5.dance == "function", "has method");      //  has method.. pass
        
        // サブクラスを作る
        var MyArray = function(){};
        MyArray.prototype = new Array();
        var arr = new MyArray();
        arr.push(1,2,3);
        assert(arr.length == 3, "subclass");           // -> subclass.. pass
        assert(arr instanceof Array, "extends Array"); //     extends Array.. pass
        
        // プロトタイプ使わずサブクラスにしてみる->失敗
        var MyArray2 = function(){};
        (function(){
            var methods = 'push pop shift unshift slice splice sort length'.split(' ');
            for (var i=0; i<methods.length;i++) {
                (function(name) {
                    MyArray2.prototype[name] = function(){return Array.prototype[name].apply(this, arguments)};
                })(methods[i]);
            }
        })();
        arr = new MyArray2();
        arr.push(3,1,2);
        arr.sort();
        trace(arr[0], arr[1], arr[2]); // -> undefined undefined undefined
        assert(arr.length == 3, "not subclass");   // -> not subclass.. FAIL
    }

0 件のコメント:

コメントを投稿

人気記事