記事
- 2011年12月25日 22:30
javascript - からnewを取り除いてみる
2/2
非標準の
見ての通り、
結局のところ、newなしでnewの挙動を再現できない以上、newを完全に消してしまうことはできなさそうです。
というわけで作ったのがこちら。ES5推奨ですが、ES5でなくても動きます。
というわけで、
hope((new Year).isHappy());
Dan the JavaScripter
__proto__を持つ処理系であれば、ほとんど再現できるのですが…function newer(cf, args){ var seed = {}; seed.__proto__ = cf.prototype; return cf.apply(seed, args) || seed; }; var o, Point = function Point(x, y){ this.x = x; this.y = y; }; Point.prototype.fromOrigin = function(){ return Math.sqrt(this.x*this.x + this.y*this.y) }; try{ o = newer(Point, [3, 4]); p( JSON.stringify(o) ); /* 今度はうまく行ったぞ */ p( o instanceof Point ); /* よし */ p( o.fromOrigin() ); /* よし */ /* でもこれがだめ */ o = newer(Date, [2011,11,25,12,34,56]); p( o ); /* 一見うまく行ってるけど */ p( typeof o ); /* new なしで Date(2011,11,25,12,34,56) した時と同じ */ }catch(e){ p(e); }
見ての通り、
newのありなしで挙動を変えてしまうコンストラクター関数(とあえてしつこくそう呼ぶ)の前には無力。それもよりによって標準のDateが!結局のところ、newなしでnewの挙動を再現できない以上、newを完全に消してしまうことはできなさそうです。
消せないなら隠せばいいじゃない!
というわけで作ったのがこちら。ES5推奨ですが、ES5でなくても動きます。
(function(global){ "use strict"; var news = [], make = function(){ var l = arguments.length, i, _; if (!news[l]){ for (i = 0, _ = []; i < l; i++) _.push('_[' + i + ']'); news[l] = new Function('$','_', 'return new $(' + _.join(',') + ')' ); } return news[l](this, arguments); }, ignoreProps = {arguments:1,caller:1,callee:1,length:1,name:1}, maker = function(importProps){ var that = this, ret = function(){ return make.apply(that, arguments) }, i; ret.origin = that; ret.prototype = that.prototype; if (importProps){ /* if (pseudo)array */ if (typeof(importProps) === 'object' && typeof(importProps.length) === 'number'){ for (i = 0; i < importProps.length; i++){ if (!ret[importProps[i]] && that[importProps[i]]){ ret[importProps[i]] = that[importProps[i]]; } } }else if (Object.getOwnPropertyNames){ Object.getOwnPropertyNames(that).forEach(function(k){ if (k in ignoreProps) return; if (!ret[k] && that[k]) ret[k] = that[k]; }); } }; return ret; }; (function(namespace, method){ for (var name in method){ if (Object.defineProperty){ Object.defineProperty(namespace, name,{ value:method[name], configurable:true,enumerable:false,writable:true }); }else{ namespace[name] = method[name] } } })(Function.prototype, {make:make,maker:maker}); })(this);
Demo
var d, D, toString = Object.prototype.toString; D = Date.maker(); /* static methods not imported */ /* No new required! makes Pythonistas happy */ d = D(1234567890e3); p(d); p(toString.call(d)); /* number of arguments does not matter */ d = D(2011,12-1,25,12,34,56); p(d); p(toString.call(d)); /* D.new() is forbidden by JS but you can D.make() * perl mongers and rubyists should be happier, too! */ d = D.make(1234567890e3); p(d); p(toString.call(d)); /* new D also works, keeping old-school JavaScripters happy */ d = new D(1234567890e3); p(d); p(toString.call(d)); /* since we have not imported static methods, this fails */ try{ p(D.now()); }catch(e){ p(e); } /* but you can always falls back to the origin */ p(D.origin.now()); D = Date.maker(Object.defineProperty ? true /* imports every static methods in ES5 */ : ["parse", "UTC", "now"] /* or manually */ ); p(D.now());
というわけで、
newの隠蔽工作には成功したのですが、無料というわけにはいきません。だいたい3倍から10倍のペナルティがかかっています。これに対し、this instanceof Constructorのテクニックを使った場合のペナルティは無視できる程度です。Benchmark
var times = function(l, f){ for(var i = 0; i < l; i++) f(i); }, timeit = function(l, f){ var timer = Date.now(); times(l, f); return Date.now() - timer; }, Point = function Point(x, y){ this.x = x; this.y = y; }, P = Point.maker(), SafePoint = function Point(x, y){ if (this instanceof Point){ this.x = x; this.y = y; }else{ return new Point(x, y); } }; Point.prototype.fromOrigin = SafePoint.prototype.fromOrigin = function(){ return Math.sqrt(this.x*this.x + this.y*this.y) }; /* all 5, give me five! */ p( (new Point(3,4)).fromOrigin() ); p( SafePoint(3,4).fromOrigin() ); p( P(3,4).fromOrigin() ); p( P.make(3,4).fromOrigin() ); p( (new P(3,4)).fromOrigin() ); p( 'new Point: ' + timeit(1e5, function(){ new Point(3,4) }) ); p( 'new SafePoint: ' + timeit(1e5, function(){ new SafePoint(3,4) }) ); p( 'SafePoint(): ' + timeit(1e5, function(){ SafePoint(3,4) }) ); p( 'P(): ' + timeit(1e5, function(){ P(3,4) }) ); p( 'P.make(): ' + timeit(1e5, function(){ P.make(3,4) }) ); p( 'new P(): ' + timeit(1e5, function(){ new P(3,4) }) ); if (Object.create) p( 'Object.create: ' + timeit(1e5, function(){ var o = Object.create(Point); o.x = 3; o.y = 4; }) );
というわけで
hope((new Year).isHappy());
Dan the JavaScripter



