摘要
我试图在javascript中正确地实现继承和封装,就像在基于类的语言(如c#)中一样。
丑陋的部分是受保护的成员在私有实例中有多个副本,只能通过闭包访问,除了将这些成员刷新到私有实例之外我没有任何想法。
如果有可能,我想在transmit
的代码中删除transfer
和Function.extend
。
更新 对于对引用或研究感兴趣的人,这里是源代码库:
故事
由于程序集可能是一个超出javascript范围的概念,我不会考虑internal
修饰符,而是public
,{{1} }和protected
。
private
和public
修饰符并不难实现;但是对于继承,private
非常棘手。然而,使用javascript并不是一个推荐的事情,我读过的大多数文章都说带有特殊字符的前缀并记录。
但似乎我坚持使用javascript来模拟基于类的语言..我偷了this idea并以我的方式实现,代码在这篇文章的后面。
场景背后的想法是使用更高的原型提供更高的可访问性,并使用闭包访问最高的原型。
假设我们有三个原型protected
,A
和D
,它看起来像
因为对象不可能是另一种不属于原型链的类型的实例;我选择的方式是水平链接G
级别并从声明类型的原型中复制成员。这使得嵌套类成为可能,因为在较少派生类型上声明的成员可以传播到更多派生类型;我的代码中的protected
方法是这样做的。如果transmit
,A
和D
拥有自己的受保护成员,则会显示如下:
用于访问私有实例的闭包是G
。它需要一个用于识别类的参数。修饰符持有者只是类标识符,在this['']
中名为y
,在测试代码中名为Function.extend
,它不应暴露在类声明之外。它也用作_
的快捷方式。
this['']
实际上不仅是基础构造函数调用者,而且还是私有实例创建者。它为每个具有继承的构造函数创建私有实例并更新_['base']
,因此应始终在构造函数中调用它。
虽然私有实例可以访问公共成员,但不应该使用它来更改它们,因为在访问公共成员时不能保证调用this['']
。但访问私有实例是; this['']
会记住最近访问过的私有实例,并在发生更改时更新受保护的成员。
我的问题是,我怎样才能摆脱受保护成员的这种清新?是否有更好的想法来实现更真实的封装?
p.s。:我实际上不想要一个使用非标准方法/属性的解决方案..如果使用的方法/属性对于旧浏览器来说过于时尚,那么填充更好。
Function.extend
recent
转移
Function.extend=function (base, factory) {
factory.call(initializeClass);
updateStaticMembersOfDerivedInnerClasses(y['public'].constructor);
transfer(y['protected'], y['public']);
return y['public'].constructor;
function y($this) {
return $this[''](y);
}
function updateStaticMembersOfDerivedInnerClasses(outer) {
var member, inner;
for (var key in outer) {
if (Object.prototype.hasOwnProperty.call(outer, key)?
(member=outer[key]) instanceof outer?
outer!==(inner=member.constructor):
false:false) {
transfer(inner, outer);
}
}
}
function initializeInstance() {
var $this=Object.create(y['private']);
var derivedGet=this[''];
var recent=$this;
this['']=function (x) {
var value=y!==x?derivedGet.call(this, x):$this;
if (value!==recent) {
transfer(value, recent, x['protected']);
recent=value;
}
return value;
};
base.apply(this, arguments);
$this['']=this[''];
}
function initializeClass(derived) {
y['public']=Object.create(base.prototype);
y['public'].constructor=derived;
if (Object.prototype.hasOwnProperty.call(base, 'transmit')) {
base.transmit(y);
}
else {
y['protected']=Object.create(y['public']);
}
y['private']=Object.create(y['protected']);
y['base']=initializeInstance;
transfer(derived, base);
derived.transmit=function (x) {
if (x['public'] instanceof derived) {
x['protected']=Object.create(y['protected']);
x['protected'].constructor=x['public'].constructor;
}
};
derived.prototype=y['public'];
return y;
}
};
测试代码
function transfer(target, source, descriptor) {
if (target!==source?
'undefined'!==typeof target?
'undefined'!==typeof source:
false:false) {
var keys='undefined'!==typeof descriptor?descriptor:source;
for (var key in keys) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key]=source[key];
}
}
}
}
答案 0 :(得分:4)
我也有类似的想法,并决定尝试写点东西。一个香草js解决方案。还是早,但我喜欢它的结果。你也可能觉得它很有趣。
这不完全是c#,但提供了更严格的生态系统。还有一些轻量级解决方案中的其他高级js功能。
https://github.com/iamlothian/rucksack.js
这不是您的代码的解决方案,而是您的概念的解决方案。如果你的目标是让你的想法发挥作用,那么一定要继续,因为我对结果感兴趣。
如果你喜欢我只是想要一个更有条理的js环境,那么这里就是我写的一个与你的问题概念类似的野心。
第2部分:
这里的想法是使用闭包和访问限制来创建一个模式,该模式限制了代码在定义之后的使用和更改方式。在大多数情况下,已经完成了很多艰苦的工作。但是模式留给你定义。
这是一个快速模拟示例,演示如何实现public | protect | private继承。我试图决定天气,我将其中的一部分实现为内置功能,或者让用户实现他们自己的对象扩展,就像我在示例中所做的那样。
http://plnkr.co/edit/ao2hTyBV1b3nYIwr7ZS5
实现在scripts.js中。查看你的控制台,看看发生了什么。
rucksack提供的是用于创建分离的代码模块的框架。这些模块分为命名空间,可以相互依赖。这些依赖项按定义延迟解析,因此定义顺序并不重要。解决过程提供了一些其他有用的功能,如接口匹配和密封模块。
当前功能:
答案 1 :(得分:3)
虽然带闭包的代码可以解决你想要的问题,但我会选择更简单的Privileged methods as Crockford called them here。
使用方法很简单:
你最终会得到这样的东西:
function A() {
var protected = {
protectedA: function() { }
};
this.getProtected = (function() {
var allow = true;
//privileged function.
return function() {
if (allow) {
allow = false;
return protected;
}
};
});
}
//B - derives from (extends) A
function B() {
var base = {}; //acquiring a base omitted - depends on your implementation.
var protected = {
protectedB: function() { }
};
//"extend" simply copies new members into protected:
protected = $.extend(protected, base.getProtected());
this.getProtected = function() {
/* privileged function - similar to A.getProtected */
};
}
JavaScript在这个范围内的能力有限,所以protected
糖还带来一些成本。
答案 2 :(得分:1)
父类和子类如何相互作用
扩展子类调用super.call
,这是一个构造其父实例的函数。
父类通过在其构造函数中使用this.share
将其受保护的成员(包括字段和函数)共享到它的扩展子类。
子类也可以调用super.fetch()
,它返回父类传递给this.share
的字段/函数的对象
为了说明我的技术,下面的代码通过class Dog extends Animal
此面向对象模型的一些核心功能
// runs in both node.js and browser
var global_namespace = ('undefined'==typeof module)? window: global;
// put a no-operation function in the value for `share` in case nothing is extending a class
var not_extendable = {share:function(){}};
// when something is extending a class...
var extendable = function(constructor) {
// create a space for protected fields
var protected_space = {};
// the following is what will get passed as `this` to the parent constructor
var sharing = {
share: function(fields) {
protected_space = fields;
},
};
// the following is what will get passed as the first arg to the child constructor
return {
// enables child to call its parent's constructor
call: function(args) {
return constructor.apply(sharing, args);
},
// allows child to access protected fields shared by its parent
fetch: function() {
return protected_space;
},
};
};
Animal
上课
// class definition for `Animal`
(function(namespace) {
// construct an instance of this class
var constructor = function(name, weight, info) {
// private fields
var color = (info && info.color) || 'unknown';
// for protected fields
var protect = {
weight: weight,
noise: function() {
return 'nothing';
},
};
// share the protected fields with any subclass that might be extending this
this.share(protect);
// public fields and methods
return {
speak: function() {
console.log(name+' says '+protect.noise());
},
feed: function() {
console.log(name+' is not hungry');
},
weigh: function() {
console.log(name+' weighs '+protect.weight+' lbs');
},
toString: function() {
return '{Animal}';
},
};
};
// handle calls to: `Animal()`
namespace.Animal = function() {
// called with new operator: `new Animal(...)`
if(this !== namespace) {
// construct simple instance of this class
return constructor.apply(not_extendable, arguments);
}
// static call: `Animal(...)`, means the caller wants to extend this class
else {
// reference child constructor
var child_constructor = arguments[0];
// return a wrapped constructor function
return function() {
// call child constructor and allow it to call the super constructor
return child_constructor.apply({}, [extendable(constructor), arguments]);
};
}
};
})(global_namespace);
Dog
上课
// class definition for `Dog`
(function(namespace) {
// class `Dog` extends class `Animal`
var constructor = Animal(function(super_class, args) {
// private field
var been_fed = false;
// call super's constructor
var operator = super_class.call(args);
// inherit parent's protected members
var parent = super_class.fetch();
// override a protected method
parent.noise = function() {
return 'bark!';
};
// override a public method
operator.feed = function() {
been_fed = true;
parent.weight += 5;
};
// extend a public method
var super_weigh = operator.weigh;
operator.weigh = function() {
super_weigh();
if(been_fed) console.log('\t'+args[0]+' has been eating :)');
else console.log('\t'+args[0]+' has not been fed yet');
};
// override another public method
operator.toString = function() {
return '{Dog}';
},
// return the operator (interfacable instance object)
return operator;
});
// handle calls to: `Dog()`
namespace.Dog = function() {
// called with new operator: `new Dog()`
if(this !== namespace) {
return constructor.apply(this, arguments);
}
// static call: `Dog()`
else {
// we do no allow extending class `Dog`
return false;
}
};
})(global_namespace);
现在,我们可以这样做:
var giraffe = new Animal('Mr. Giraffe', 720);
giraffe.speak(); // "Mr. Giraffe says nothing"
giraffe.weigh(); // "Mr. Giraffe weighs 720 lbs"
var buddy = new Dog('Buddy', 50);
buddy.speak(); // "Buddy says bark!"
buddy.weigh(); // "Buddy weighs 50 lbs"
// "Buddy has not been fed yet"
buddy.feed();
buddy.weigh(); // "Buddy weighs 55 lbs"
// "Buddy has been eating :)"
这允许私有,受保护和公共字段/功能。受保护和公共字段/功能都可以被覆盖和扩展。
console.log(giraffe); // "{Animal}"
console.log(buddy); // "{Dog}"
答案 3 :(得分:1)