2012-04-16

Struct.js ver. 0.3をリリースしました

欲しかった機能が実装できたのでタグを切りました。

主な機能追加

  • ネストした構造のサポート
  • nullableオプションの追加
  • カスタムバリデーション関数のサポート
  • 設定で全チェックを無効化する機能を追加
  • 初期作成時の値チェックを追加
型チェックのためのコーディングをなるべく増やさない、というポリシーです。なので、素のObjectと同様に扱えるが必要なチェック機構は備えている、という物を目指しています。しかし副作用としてプロパティの有無でそれが何であるかを判定するダックタイピングは不可能になってしまったので、そこだけは Struct.getType(obj) を使います。

リリース用の設定

Proxyオブジェクトを生成しないので、通常のプロパティアクセスのコストだけになる。
1
2
3
4
Struct.configure({
  // 全てのチェック無効化
  'disable any check': true
});

バリデーション関数のサポート

1
2
3
4
5
6
7
8
9
Struct.define('Size', {
  width:  {type: 'number', cond: 'v >= 0'},
  height: {type: 'number', cond: function(v){return v >= 0} /*same as ↑*/}
});
 
var size = Struct.create('Size', {
  width: 0,
  height: 0
});

ネスト構造のサポート

Struct.create にネストしたObjectをそのまま食わせても大丈夫。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Struct.define('Position', {
  x: {type: 'number', nullable: false},
  y: {type: 'number', nullable: false}
});
 
Struct.define('Size', {
  width:  {type: 'number', nullable: false, cond: 'v >= 0'},
  height: {type: 'number', nullable: false, cond: 'v >= 0'}
});
 
Struct.define('Rect', {
  pos:  {type: 'struct:Position', nullable: false},
  size: {type: 'struct:Size', nullable: false},
  createdAt: {type: 'date', writable: false}
});
 
var rect = Struct.create('Rect', {
  pos: {x: 0, y: 0},
  size: {width: 100, height: 100},
  createdAt: new Date()
});
 
rect.pos.x = "200"; // => type mismatch error
rect.size.height = -1; // => condition formula validation error

今後の予定

次は関数呼び出しのパラメータチェックを実装する予定。



2012-04-04

JavaScriptでrubyのmethod_missingを実装する

追記: Firefoxの実装で既に有る__noSuchMethod__に名前は合せた方が良い、というコメントを頂いたので名前を変えました。

何の役に立つか不明だけど書いてみた*1。Proxyでプロパティアクセスをフックして、存在しない場合は用意しておいた関数プロキシを返す。

/**
* Enable route to __noSuchMethod__ when unknown method calling.
*
* @param {Object} obj Target object.
* @return {Object}
*/
function enableMethodMissing(obj) {
var functionHandler = createBaseHandler({});
functionHandler.get = function(receiver, name) {
return function(){};
}
var calledProperty;
var trapFn = Proxy.createFunction(functionHandler, function() {
return obj.__noSuchMethod__(calledProperty, Array.prototype.slice.call(arguments));
});
var propertyAccessHandler = createBaseHandler(obj);
propertyAccessHandler.get = function(receiver, name) {
if (name in obj) {
return obj[name];
} else {
calledProperty = name;
return trapFn;
}
}
return Proxy.create(propertyAccessHandler);
/**
* Create trap functions (internal)
*
* @param {Object} obj Original object.
* @return {Object} Proxy handler.
*/
function createBaseHandler(obj) {
return {
getOwnPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(obj, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getPropertyDescriptor: function(name) {
var desc = Object.getPropertyDescriptor(obj, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(obj);
},
getPropertyNames: function() {
return Object.getPropertyNames(obj);
},
defineProperty: function(name, desc) {
return Object.defineProperty(obj, name, desc);
},
delete: function(name) {
return delete obj[name];
},
fix: function() {
if (Object.isFrozen(obj)) {
return Object.getOwnPropertyNames(obj).map(function(name) {
return Object.getOwnPropertyDescriptor(obj, name);
});
}
return undefined;
},
has: function(name) {
return name in obj;
},
hasOwn: function(name) {
return Object.prototype.hasOwnProperty.call(obj, name);
},
set: function(receiver, name, val) {
// No check
// But normally needs checking property descriptor
obj[name] = val;
},
enumerate: function() {
var result = [];
for (var name in obj) { result.push(name); }
return result;
},
keys: function() {
return Object.keys(obj);
}
};
}
}
var Ninja = function(name, age) {
this.name = name;
this.age = age;
return enableMethodMissing(this);
}
Ninja.prototype.getName = function() {
return this.name;
}
Ninja.prototype.__noSuchMethod__ = function(methodName, args) {
console.log('method name:' + methodName);
console.log(args);
};
var sasuke = new Ninja('Sasuke', 0);
sasuke.getName(); // => Sasuke
sasuke.hoge(1,2,3); // => __noSuchMethod__('hoge', [1,2,3])
sasuke.hoge; // => undefined
sasuke.age; //=> 0
view raw test.js hosted with ❤ by GitHub
上記の処理が書いてあるのはこの部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function enableMethodMissing(obj) {
  // 関数プロキシの作成
  var functionHandler = createBaseHandler({});
  functionHandler.get = function(receiver, name) {
  // プロパティアクセスの場合は何も返さない
    return function(){};
  }
 
  var calledProperty;
  var trapFn = Proxy.createFunction(functionHandler, function() {
    // 実行の場合は obj.methodMissing を呼び出す
    return obj.__noSuchMethod__(calledProperty, Array.prototype.slice.call(arguments));
  });
 
  // プロパティプロキシの作成
  var propertyAccessHandler = createBaseHandler(obj);
  propertyAccessHandler.get = function(receiver, name) {
    if (obj[name]) {
      return obj[name];
    } else {
      // 存在しないプロパティへのアクセスは関数プロキシを返す
      calledProperty = name;
      return trapFn;
    }
  }
  return Proxy.create(propertyAccessHandler);
 
// (略)
method_missing用の関数をわざわざProxy.createFunctionしているのは、プロパティアクセスの時はundefinedを返す、関数呼び出しの時はmethod_missing用の関数実行と処理を分けるため。
Proxyが何に使えるか、というのはこの動画がわかりやすかった。


D



*1:Firefox4以降 or JavaScriptの実験機能を有効にしたChromeでのみ動作します。





このエントリーをはてなブックマークに追加