В моей структуре все является объектом или "интерфейсом".
Интерфейсы определяют общие функции (методы / property_gets / property_sets), которые могут иметь объекты.
Вы создаете интерфейскак это: var some_interface = GetInterface(constructor, interface_setup, parent_interface..)
Вы можете указать любое количество parent_interfaces.Поэтому, если interface_A
наследует interface_B
и interface_C
, вы можете создать interface_A следующим образом: GetInterface(constructor, interface_setup, interface_B, interface_C);
Если interface_A
наследует interface_X
, а interface_X
наследует interface_Y
, тогда interface_A
будет иметь все функции, которые есть у interface_X
и interface_Y
.
Интерфейсы, которые не требуют конструктора, оставят аргумент конструктора как нулевой.Interface_setup - это функция, которая выглядит следующим образом:
function(proto){
}
Объект, на который указывает аргумент proto , имеет 4 метода: SetM
, ShadowM
, SetP
иShadowP
.
Эти 4 метода используются для настройки вашего интерфейса.
Эта структура также обеспечивает ленивую реализацию интерфейсов.(другими словами, код установки фактически никогда не будет выполняться до тех пор, пока он в действительности не понадобится).
Ограничения этой платформы требуют как минимум поддержки Object.keys
, Object.getOwnPropertyDescriptor
и Object.defineProperty
.(Другими словами, он работает в последних версиях FireFox, IE, Chrome, Safari, но не Opera)
TestPage2.html:
<!doctype html>
<script src="js.js"></script>
<script>
var human = GetInterface(function(){
},function(proto){
//alert("trace: initing human");
proto.$SetM("Sleep",function(){
alert(this.Name+" is sleeping");
});
proto.$SetP("Name",function(){
return this._name;
},function(value){
this._name=value;
});
});
var female = GetInterface(function(){
},function(proto){
//alert("trace: initing female");
proto.$SetM("Dance",function(){
alert(this.Name+" is dancing");
});
},human);
var male = GetInterface(function(){
},function(proto){
//alert("trace: initing male");
proto.$SetM("Fight",function(){
alert(this.Name+" is fighting");
});
proto.$ShadowP("Name",function(parent_get){
return "Mr. "+parent_get();
},function(parent_set,value){
parent_set(value);
});
},human);
var child = GetInterface(function(){
},function(proto){
//alert("trace: initing child");
proto.$SetM("Play",function(){
alert(this.Name+" is playing");
});
},human);
var adult = GetInterface(function(){
},function(proto){
//alert("trace: initing adult");
proto.$SetM("Work",function(){
alert(this.Name+" is working");
});
},human);
var mammal = GetInterface(function(){
},function(proto){
//alert("trace: initing mammal");
proto.$SetM("DoMammalStuff",function(){
alert("doing mammal stuff");
});
});
var john=new male();
john.Name="john";
john.Sleep();
var mary=new female();
mary.$IsA(child);
mary.$IsA(mammal);
mary.$Setup(function(proto){
proto.$ShadowP("Name",function(parent_get){
return "Miss "+parent_get.call(this);
},function(parent_set,value){
parent_set.call(this,value);
});
});
mary.Name="mary";
mary.Play();
</script>
TestPage.html:
<!doctype html>
<script src="js.js"></script>
<script>
var human_interface = GetInterface(function(){
},function(proto){
alert("trace: initing human");
proto.$SetM("Sleep",function(){
alert(this.Name+" is sleeping");
});
proto.$SetP("Name",function(){
return this._name;
},function(value){
this._name=value;
});
});
var female_interface = GetInterface(function(){
},function(proto){
alert("trace: initing female");
proto.$SetM("Dance",function(){
alert(this.Name+" is dancing");
});
},human_interface);
var male_interface = GetInterface(function(){
},function(proto){
alert("trace: initing male");
proto.$SetM("Fight",function(){
alert(this.Name+" is fighting");
});
},human_interface);
var child_interface = GetInterface(function(){
},function(proto){
alert("trace: initing child");
proto.$SetM("Play",function(){
alert(this.Name+" is playing");
});
},human_interface);
var adult_interface = GetInterface(function(){
},function(proto){
alert("trace: initing adult");
proto.$SetM("Work",function(){
alert(this.Name+" is working");
});
},human_interface);
var mammal_interface = GetInterface(function(){
},function(proto){
alert("trace: initing mammal");
proto.$SetM("DoMammalStuff",function(){
alert("doing mammal stuff");
});
});
var john={};
john.$IsA(adult_interface);
//the above 2 lines are equal to simply doing:
//var john=new adult_interface();
//you can think of it as a shortcut
john.$IsA(mammal_interface);
john.DoMammalStuff();
john.Name="john";
john.Sleep();
var mary=new female_interface();
mary.$IsA(child_interface);
mary.$IsA(mammal_interface);
mary.DoMammalStuff();
mary.Name="mary";
mary.Play();
mary.Dance();
</script>
Js.js:
"use strict";
var GetInterface;
(function(){
//================================================================================//
//(constructor:Function, setup:Function?, parent_interfaces:Function..):Function
GetInterface = function (constructor, setup) {
var parent_classes = GetParray(arguments, 2);
var output = function () {
output.$Init();
for (var x = parent_classes.length - 1; x >= 0; --x) {
parent_classes[x](this);
}
if(constructor===null){
constructor.apply(this, arguments);
}
};
output.$Init = Mize(function () {
var output_proto = output.prototype;
parent_classes.forEach(function (parent_class) {
parent_class.$Init();
Infect(output_proto, parent_class.prototype);
});
init_proto(output_proto,setup);
if(setup!==undefined){
setup(output_proto);
}
});
return output;
};
var init_proto=function(proto){
$defineProperty(proto, "$SetM", { value: set_m, writable: true, configurable: true });
$defineProperty(proto, "$ShadowM", { value: shadow_m, writable: true, configurable: true });
$defineProperty(proto, "$SetP", { value: set_p, writable: true, configurable: true });
$defineProperty(proto, "$ShadowP", { value: shadow_p, writable: true, configurable: true });
};
var set_m = function (method_name, method) {
this[method_name] = method;
};
var set_p = function (property_name, getter, setter) {
$defineProperty(this, property_name, { get: getter, set: setter, enumerable: true, configurable: true });
};
var shadow_m = function (method_name, supplied_method) {
var old_method = this[method_name];
this[method_name] = function () {
var args = GetParray(arguments);
args.unshift(old_method.bind(this));
supplied_method.apply(this, args);
};
};
var shadow_p = function (property_name, getter, setter) {
var old_descriptor = $getOwnPropertyDescriptor(this, property_name);
var old_get = old_descriptor.get;
var old_set = old_descriptor.set;
$defineProperty(this, property_name, { get: function () {
return getter.call(this, old_get.bind(this));
}, set: function (value) {
setter.call(this, old_set.bind(this), value);
}, enumerable: true, configurable: true
});
};
var $slice=Array.prototype.slice;
var $defineProperty=Object.defineProperty;
var $getOwnPropertyDescriptor=Object.getOwnPropertyDescriptor;
if($defineProperty===undefined){
throw "Object.defineProperty, Object.getOwnPropertyDescriptor, Object.keys are required";
}
//================================================================================//
//(victim:Object, disease:Object):void
var Infect=function (victim, disease, excludes) {
var keys=Object.keys(disease);
if(excludes!==undefined){
excludes.forEach(function(exclude){
ForEach(keys,function(key,x){
if(key===exclude){
keys.splice(x,1);
return false;
}
});
});
}
keys.forEach(function(key){
$defineProperty(victim, key, $getOwnPropertyDescriptor(disease, key));
});
};
//================================================================================//
//(args:Object # arguments object #, start_index:int?):Array
var GetParray = function (args, start_index) {
if (start_index === undefined) {
start_index = 0;
}
return $slice.call(args, start_index);
};
//================================================================================//
//(array:Array, f:Function(item:Object|null, index:pint):boolean?):Object
var ForEach=function(array,f){
for (var x = 0, xx = array.length, last_index=xx-1; x < xx; ++x) {
var result = f(array[x], x, last_index);
if (result !== undefined) {
return result;
}
}
};
//================================================================================//
//provides memoization.
//(f:Function, arity_fixed:boolean?true):Function
//caching is done according to the inputs. the results of calling function(undefined) and function() are cached differently.
//if arity is fixed, optimizations can be done
var Mize=function(f, arity_fixed) {
if (arity_fixed === undefined) {
arity_fixed = true;
}
var used; //for 0 arg
var result; //for 0 arg
var results; //for >0 args
var used_params; //for 1 arg
var used_param_sets; //for >1 args
var f_length = f.length;
var use_generic = !arity_fixed || f_length > 3;
if (use_generic) { //if `f_length` <= 3, it will be optimized (i.e. not using generic function)
results = [];
used_param_sets = [];
return function () {
var params = GetParray(arguments);
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (used_param_set.length === params.length) {
var params_match = true;
ForEach(params,function (param, y) {
if (used_param_set[y] !== param) {
params_match = false;
return false;
}
});
if (params_match) {
result_found = true;
return results[x];
}
}
});
if (!result_found) {
used_param_sets.push(params);
result = f.apply(null, params);
results.push(result);
}
return result;
};
}
if (f_length === 0) {
used = false;
return function () {
if (!used) {
result = f();
used = true;
}
return result;
};
}
if (f_length === 1) {
used_params = [];
} else {
used_param_sets = [];
}
results = [];
switch (f_length) {
case 1:
return function (arg) {
var result_found = false;
var result = ForEach(used_params,function (used_param, x) {
if (arg === used_param) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_params.push(arg);
result = f(arg);
results.push(result);
}
return result;
};
break;
case 2:
return function (arg1, arg2) {
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (arg1 === used_param_set[0] && arg2 === used_param_set[1]) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_param_sets.push([arg1, arg2]);
result = f(arg1, arg2);
results.push(result);
}
return result;
};
break;
case 3:
return function (arg1, arg2, arg3) {
var result_found = false;
var result = ForEach(used_param_sets,function (used_param_set, x) {
if (arg1 === used_param_set[0] && arg2 === used_param_set[1] && arg3 === used_param_set[2]) {
result_found = true;
return results[x];
}
});
if (!result_found) {
used_param_sets.push([arg1, arg2, arg3]);
result = f(arg1, arg2, arg3);
results.push(result);
}
return result;
};
break;
default:
throw "Invalid `f_length`: " + f_length;
}
};
//================================================================================//
Object.prototype.$Setup=function(setup){
setup(Object.getPrototypeOf(this));
};
//================================================================================//
Object.prototype.$IsA=function(_interface){
var excludes=GetParray(arguments,1);
if(this.$SetM===undefined){
this.$SetM=set_m;
this.$SetP=set_p;
this.$ShadowM=shadow_m;
this.$ShadowP=shadow_p;
}
_interface.$Init();
/*var this_proto={};
init_proto(this_proto);
Infect(this_proto,Object.getPrototypeOf(this));
this.__proto__=this_proto;*/
Infect(Object.getPrototypeOf(this),_interface.prototype,excludes);
};
//================================================================================//
})();