自从last question以来,我一直在研究Javascript的原型(prototype)模型,并试图摆脱我从其他语言继承的OOP愿景(双关语意)。
我回到基础知识,阅读了Crookford的Javascript: The Good Parts和You Don't Know JS Material ,并决定坚持所谓的行为委托(delegate)。
重组我之前实现行为委托(delegate)和命名空间的示例,我写道:
var GAME = {};
(function(namespace) {
var Warrior = {};
Warrior.init = function(weapon) {
this.setWeapon(weapon);
};
Warrior.getWeapon = function() {
return this.weapon;
};
Warrior.setWeapon = function(value) {
this.weapon = value || "Bare hands";
};
namespace.Warrior = namespace.Warrior || Warrior;
})(GAME);
(function(namespace) {
var Archer = Object.create(namespace.Warrior);
Archer.init = function(accuracy) {
this.setWeapon("Bow");
this.setAccuracy(accuracy);
};
Archer.getAccuracy = function() {
return this.accuracy;
};
Archer.setAccuracy = function(value) {
this.accuracy = value;
};
namespace.Archer = namespace.Archer || Archer;
})(GAME);
因此,每次我复制一个新的Archer对象时:
var archer1 = Object.create(GAME.Archer);
仅创建此对象,以节省内存。
但是,如果我不想公开“准确性”属性怎么办?该属性只能通过调用“training()”方法或类似方法来增加。我试图在匿名函数中使用
var accuracy
,但是它变成了一种静态变量,所有Archer实例将共享相同的值。问题:有什么方法可以将变量设置为私有(private)变量,同时仍保持行为委托(delegate)/原型(prototype)模式?
我确实也知道功能模式,在这里我以内存为代价成功实现了可变隐私。通过运行,每个新的“archer”实例都会生成一个新的“Warrior”,然后生成一个新的“Archer”。即使考虑到Chrome和Firefox具有不同的优化,对两者的测试也表明委派/原型(prototype)模式更有效:
http://jsperf.com/delegation-vs-functional-pattern
如果我采用纯对象委托(delegate)模式,是否应该忘记经典的封装概念并接受属性的自由变化性质?
最佳答案
我会尝试用与one稍有不同的方式回答您的问题,该here告诉您如何使用库。
不同之处在于它(希望)为您提供一些想法,使我们可以如何解决OLOO中的私有(private)变量问题。至少在某种程度上,使用我们自己的代码,不需要外部lib,这在某些情况下可能很有用。
为了使代码更整洁,我对您的匿名包装器功能进行了划分,因为它们与问题完全无关。
var Warrior = {};
Warrior.warInit = function (weapon){
this.setWeapon(weapon);
}
Warrior.getWeapon = function(){
return this.weapon;
}
Warrior.setWeapon = function (value){
this.weapon = value || "Bare hands";
}
var Archer = Object.create(Warrior);
Archer.archInit = function (accuracy){
this.setWeapon("Bow");
this.setAccuracy(accuracy);
}
Archer.getAccuracy = function (pocket) {
return pocket.accuracy;
}
Archer.setAccuracy = function (value, pocket){
pocket.accuracy = value;
}
function attachPocket(){
var pocket = {};
var archer = Object.create(Archer);
archer.getAccuracy = function(){
var args = Array.prototype.slice.call(arguments);
args = args.concat([pocket]);
return Archer.getAccuracy.apply(this, args)
};
archer.setAccuracy = function(){
var args = Array.prototype.slice.call(arguments);
args = args.concat([pocket]);
return Archer.setAccuracy.apply(this, args);
};
return archer;
}
var archer1 = attachPocket();
archer1.archInit("accuracy high");
console.log(archer1.getAccuracy()); // accuracy high
archer1.setAccuracy("accuracy medium");
console.log(archer1.getAccuracy()); // accuracy medium
测试here以上的代码。 (然后打开浏览器控制台)
用法
1)在OOO中,有关在不同级别的原型(prototype)链上命名函数的命名的通用实践是对的。我们想要不同的名称,这些名称更具描述性和自我记录性,从而使代码更简洁,更易读。更重要的是,通过提供不同的名称,我们避免了递归循环:
Archer.init = function(accuracy, pocket){
this.init() // here we reference Archer.init() again, indefinite recurson. Not what we want
...
}
Archer.archInit = fucntion (accuracy, pocket){ // better,
this.warInit() // no name "collisions" .
}
2)我们创建了一个attachPocket()函数,该函数创建内部变量Pocket。使用Object.create()创建新对象,并将其原型(prototype)设置为指向Archer。暂停。如果您注意到,我们定义了需要私有(private)var的函数,以便每个函数都使用一个以上的参数(pocket),其中一些仅使用Pocket。这是窍门。
像这样:
function AtachPocket(){
...
var pocket = {};
archer.setAccuracy = function(){
var args = Array.prototype.slice.call(arguments);
args = args.concat([pocket]); // appending pocket to args
return Archer.setAccuracy(this, args);
};
...
}
从本质上讲,通过这样做,我们绕过了通常在原型(prototype)链中搜索功能的过程,而只是对需要私有(private)变量的功能进行了搜索。这就是“直接致电”所指的。
通过像在原型(prototype)链(
archer
)中一样为Archer
(“instance”)中的函数设置相同的名称,我们在实例级别隐藏了该函数。没有不确定的循环的危险,因为如上所述我们正在“直接调用”。同样,通过具有相同的功能名称,我们保留了在“实例”中访问相同功能的正常预期行为,就像在原型(prototype)链中一样。这意味着在var archer = Object.create(Archer)
之后,我们可以访问setAccuracy
函数,就像在原型(prototype)链中正常搜索函数时一样。3)每次调用attachPocket()时,都会创建一个新的“实例”,该实例具有那些传递了Pocket参数的包装函数(全部作为实现的内部细节)。因此,每个实例都有自己的唯一私有(private)变量。
您通常会在“实例”中使用函数:
archer1.archInit("accuracy high"); // Passing needed arguments.
// Placed into pocked internally.
archer1.getAccuracy(); // Getting accuracy from pocket.
可扩展性
到目前为止,我们拥有的功能是使用诸如
Archer.setAccuracy
,Archer.getAccuracy
的硬编码值“附加一个口袋”。如果我们想通过引入像var AdvancedArcher = Object.create(Archer)
这样的新对象类型来扩展原型(prototype)链,如果我们将传递给它甚至没有attachPocket
函数的AdvancedArcher
对象传递给setAccuracy()
,那么attachPocket()
的行为将如何?每当我们在原型(prototype)链中引入一些更改时,是否都要更改attachPocket()
?让我们尝试通过使
attachPocket
更通用来回答这些问题。首先,扩展原型(prototype)链。
var AdvancedArcher = Object.create(Archer);
AdvancedArcher.advInit = function(range, accuracy){
this.archInit(accuracy);
this.setShotRange(range);
}
AdvancedArcher.setShotRange = function(val){
this.shotRange = val;
}
更通用的
attachPocketGen
。function attachPocketGen(warriorType){
var funcsForPocket = Array.prototype.slice.call(arguments,1); // Take functions that need pocket
var len = funcsForPocket.length;
var pocket = {};
var archer = Object.create(warriorType); // Linking prototype chain
for (var i = 0; i < len; i++){ // You could use ES6 "let" here instead of IIFE below, for same effect
(function(){
var func = funcsForPocket[i];
archer[func] = function(){
var args = Array.prototype.slice.call(arguments);
args = args.concat([pocket]); // appending pocket to args
return warriorType[func].apply(this, args);
}
})()
}
return archer;
}
var archer1 = attachPocketGen(Archer,"getAccuracy","setAccuracy");
archer1.advInit("11","accuracy high");
console.log(archer1.getAccuracy()); // "accuracy high";
archer1.setAccuracy("accuracy medium");
console.log(archer1.getAccuracy());
测试代码here。
在这个更通用的
warriorType
作为第一个参数的情况下,我们有一个attachPocketGen
变量,它表示原型(prototype)链中的任何对象。可能无法使用的参数代表需要私有(private)变量的函数的名称。archer
采用这些函数名称,并在apply()
“instance”中使包装函数具有相同的名称。像以前一样阴影。还要认识到的另一点是,这种制作包装器函数并使用
this
函数从闭包中传递变量的模型将适用于仅使用Pocket的函数,使用Pocket和其他变量的函数,以及当这些变量当然使用前面的相对attachPocketGen
引用。因此,我们已经获得了更多可用的attachPocket,但这仍然是需要注意的事情。
1)通过必须传递需要私有(private)var的函数名称,这种用法意味着我们(
attachPocketGen
用户)需要了解整个原型(prototype)链(因此我们可以看到哪些函数需要私有(private)var)。因此,如果您要像此处那样制作一个原型(prototype)链,并且只是将Archer.getAccuracy
作为API传递给希望使用带有私有(private)变量的行为委托(delegate)的程序员,则他/她将不得不分析原型(prototype)链中的对象。有时那不是我们想要的。1a)但是,当在原型(prototype)链中定义函数时(例如
Archer.getAccuracy.flg = true;
),我们可以改为向其添加一个属性,例如可以判断该函数是否需要私有(private)var的标志:flg
然后,我们可以添加其他逻辑,以检查原型(prototype)链中具有此
funcsForPocket
的所有函数并填充var archer1 = attachPocketGen(AdvancedArcher)
。结果是只有这个调用:
warriorType
除
setAccuracy
外,没有其他参数。不需要此功能的用户就不必知道原型(prototype)链的外观,这就是私有(private)var需要什么功能。改良风格
如果我们要看这段代码:
Archer.archInit = function (accuracy){
this.setWeapon("Bow");
this.setAccuracy(accuracy);
}
我们看到它使用“口袋”功能
setAccuracy
。但是我们没有在此处添加口袋作为最后一个参数,因为被调用的archer1.archInit(...)
是影子版本,即实例的版本。因为它将仅从像Archer.archInit
这样的实例中调用,因为它已经在最后一个参数中添加了一个口袋,因此有必要。挺好的,但是它的定义是:Archer.setAccuracy = function (value, pocket){ ...
在上面的
setAccuracy
这样的原型(prototype)链中制作对象时,可能会造成混淆。如果我们查看archInit
的定义,它看起来就应该如此。因此,为了不必记住我们不必在使用其他Pocket函数的函数(例如attachPocketGen
)中添加pocket作为最后一个arg,也许我们应该这样做:Archer.setAccuracy = function (value){
var pocket = arguments[arguments.length -1];
pocket.accuracy = value;
}
测试代码ojit_a。
没有口袋作为函数定义中的最后一个参数。现在很明显,无论代码在哪里,都不必使用Pocket作为参数来调用函数。
1)当我们考虑更通用的原型(prototype)链和ojit_code时,还有其他可以说是次要的东西。就像使函数在不需要传递口袋时使用可调用的Pocket一样,即将Pocket的使用切换为一个函数,但是为了不使这篇文章过长,让我们暂停一下。
希望这可以为您提供解决问题的想法。
关于javascript - Javascript行为委托(delegate)模式中的可变隐私,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/32748078/