从我的last question开始,我一直在研究Javascript的原型模型,试图摆脱我从其他语言继承的
我回到基础并阅读了Crookford的Javascript: The Good Parts以及You Don't Know JS材料,并决定坚持所谓的行为授权。
重构我之前的实现行为委托和命名空间的示例,我写道:
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的所有实例都会共享相同的值。
问题:有没有办法将变量设置为私有,同时仍然保持行为委派/原型模式?
我也知道功能模式,在这里,我成功地以内存为代价实现了可变隐私。通过功能,每个新的“射手”实例生成一个新的“战士”,然后一个新的“射手”。即使考虑到Chrome和Firefox有不同的优化,两者的测试都报告了委托/原型模式更有效:
http://jsperf.com/delegation-vs-functional-pattern
如果我选择纯对象委托模式,我是否应该忘记经典的封装概念并接受属性的自由变化性质?
答案 0 :(得分:1)
我会尝试使用与此one略有不同的内容来回答您的问题,该内容会告诉您如何使用库。 不同之处在于它(希望)会给你一些关于我们如何在OLOO中解决私人变量问题的想法。至少在某种程度上,使用我们自己的代码,不需要外部库,这在某些情况下可能很有用。
为了使代码更清晰,我已经对您的匿名包装函数进行了条带化处理,因为它们与问题没有任何关系。
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) OLOO关于在不同级别的原型链上命名函数的一般做法是OOP的相似之处。我们希望不同的名称更具描述性和自我记录,这些代码更清晰,更易读。更重要的是,通过给出不同的名称,我们避免了递归循环:
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()函数。使用Object.create()创建新对象并将其原型设置为指向Archer。暂停。如果你注意到,我们已经定义了需要私有var的函数,以便每个函数都需要一个参数(口袋),有些只使用口袋。这是诀窍。
通过制作像archer.setAccuracy()这样的包装函数,archer.getAccuracy() ...我们可以创建闭包并直接调用需要的函数 private var(这里是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);
};
...
}
通过这样做,我们绕过了原型链中的函数的正常搜索,仅适用于需要私有var的函数。这就是"直接打电话"是指。
通过为archer
(" instance")中的函数设置相同的名称,就像它在原型链(Archer
)中一样,我们在实例级别隐藏该函数。没有无限循环的危险,因为我们直接打电话"如上所述。同样,通过使用相同的函数名,我们保留了在"实例中访问相同函数的正常预期行为。就像在原型链中一样。这意味着var archer = Object.create(Archer)
我们可以访问函数setAccuracy
,就像我们在原型链中正常搜索函数一样。
3)每次调用attachPocket()时,它都会创建一个新的"实例"具有传递口袋参数的那些包装函数(所有这些都作为实现的内部细节)。因此,每个实例都有自己独特的私有变量。
你可以在"实例中使用函数"通常:
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)
这样的新对象类型来扩展原型链,如果我们传递给它attachPocket
的对象可能甚至没有AdvancedArcher
功能?每当我们在原型链中引入一些变化时,我们是否会改变setAccuracy()
?
让我们通过使attachPocket()
更加通用来尝试回答这些问题。
首先,扩展原型链。
attachPocket()
更通用的var AdvancedArcher = Object.create(Archer);
AdvancedArcher.advInit = function(range, accuracy){
this.archInit(accuracy);
this.setShotRange(range);
}
AdvancedArcher.setShotRange = function(val){
this.shotRange = val;
}
。
attachPocket
测试代码here。
在这个更通用的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());
作为第一个参数中,我们有一个attachPocketGen
变量,它代表我们原型链中的任何对象。可以休闲的参数是表示需要私有var的函数名称的参数。
warriorType
获取这些函数名称,并在attachPocketGen
&#34; instance&#34;中生成具有相同名称的包装函数。阴影,就像以前一样。
另一件需要认识的是,这个制作包装函数的模型和使用archer
函数从闭包中传递变量的模型适用于只使用口袋的函数,使用口袋和其他变量的函数,当然,这些变量在它们前面使用相对apply()
引用。
所以我们已经实现了一些更有用的attachPocket,但这仍然是应该注意的事情。
1)通过必须传递需要私有var的函数的名称,该用法意味着我们(this
用户)需要知道整个原型链(所以我们可以看到什么函数需要私有var)。因此,如果您要创建一个类似于此处的原型链,并将attachPocketGen
作为API传递给想要使用您的行为委托与私有变量的程序员,他/她将不得不分析原型链中的对象。有时这不是我们想要的。
1a)但是我们可以在原型链中定义我们的函数(比如attachPocketGen
)来向它们添加一个属性,就像一个标志,可以判断该函数是否需要私人var:
Archer.getAccuracy
然后我们可以添加额外的逻辑来检查具有此Archer.getAccuracy.flg = true;
的原型链中的所有函数并填充flg
。
结果是只有这个电话:
funcsForPocket
除了var archer1 = attachPocketGen(AdvancedArcher)
之外没有其他参数。这个函数的用户不需要知道原型链是什么样的,这就是私有var所需的函数。
如果我们要查看此代码:
warriorType
我们看到它使用&#34; pocket&#34;函数Archer.archInit = function (accuracy){
this.setWeapon("Bow");
this.setAccuracy(accuracy);
}
。但是我们不是在这里添加口袋作为它的最后一个参数,因为被调用的setAccuracy
是阴影版本,来自实例的版本。因为它只会从像setAccuracy
之类的实例中调用 那个人已经添加了一个口袋作为最后一个参数,所以有需要。这很好,但它的定义是:
archer1.archInit(...)
在上面的Archer.setAccuracy = function (value, pocket){ ...
之类的原型链中制作对象时可能会造成混淆。如果我们看一下Archer.archInit
的定义,我们就应该这样做了。因此,为了不必记住我们不必添加口袋作为使用其他口袋功能的函数(如setAccuracy
)中的最后一个arg,也许我们应该像这样:
archInit
测试代码here。
没有口袋作为函数定义中的最后一个参数。现在很清楚,无论在代码中,函数都不必用pocket作为参数调用。
1)当我们考虑更一般的原型链和Archer.setAccuracy = function (value){
var pocket = arguments[arguments.length -1];
pocket.accuracy = value;
}
时,还有其他可以说是次要的东西。就像制作使用口袋可调用的功能时,我们不会这样做。为了传递一个口袋,即将口袋用法切换到一个功能,但是为了不让这个帖子太长,让我们暂停它。
希望这可以让您查看解决问题的想法。