[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 件のコメント:
コメントを投稿