JSFiddle测试
var Operators = (function ($, self) {
var self = {};
//private
var IPT_X = '#x';
var IPT_Y = '#y';
//public
self.x = 0;
self.y = 0;
//public
self.showOperators = function() {
//use of a private property (IPT_X) and a public property (this.x)
$(IPT_X).val(self.x);
$(IPT_Y).val(self.y);
};
self.clean = function() {
self.x = 0;
self.y = 0;
// call to a local public method
self.showOperators();
};
self.updateOperators = function(_x, _y) {
// use of a public property
self.x = _x;
self.y = _y;
};
self.clean();
return self;
//Module dependencies
})(jQuery, {});
var Randomizer = (function(self, math) {
// private
function getRandomNumber() {
return math.round(math.random() * 1000);
};
// keep superior method so we can call it after overriding
self.oldUpdateOperators = self.updateOperators
// public
self.updateOperators = function(_x, _y) {
// call to superior class's method
self.oldUpdateOperators(_x, _y);
// call to method of superior object
self.showOperators();
};
self.populateRandomNumbers = function() {
// call to public local method (this.updateOperators())
// and to a local private method (getRandomNumber()))
self.updateOperators(getRandomNumber(), getRandomNumber());
};
// init
self.populateRandomNumbers();
return self;
})(Operators, Math)
var Operations = (function(self, $) {
//private
var IPT_RES = '#res';
var BTN_SUM = '#sum';
var BTN_SUBTRACT = '#subt';
var BTN_MULTIPLY = '#mult';
var BTN_DIVISION = '#div';
var BTN_CLEAN = '#clean';
var BTN_RAND = '#rand';
// private
function calcSum() {
return self.x + self.y;
};
function calcSubtraction() {
return self.x - self.y;
};
function calcMultiplication() {
return self.x * self.y;
};
function calcDivision() {
return self.x / self.y;
};
function showRes(val) {
$(IPT_RES).val(val);
};
//public
self.sum = function() {
// call to 2 local private methods
showRes(calcSum());
};
self.subtract = function() {
showRes(calcSubtraction());
};
self.multiply = function() {
showRes(calcMultiplication());
};
self.division = function() {
showRes(calcDivision());
};
// init
$(BTN_SUM).on('click', function() { self.sum() });
$(BTN_SUBTRACT).on('click', function() { self.subtract() });
$(BTN_MULTIPLY).on('click', function() { self.multiply() });
$(BTN_DIVISION).on('click', function() { self.division() });
$(BTN_CLEAN).on('click', function() { self.clean() });
$(BTN_RAND).on('click', function() { self.populateRandomNumbers() });
return self;
})(Randomizer, jQuery);
<html>
<body>
X: <input id='x'>
<br>
Y: <input id='y'>
<br>
Res: <input id='res'>
<br>
<input id='sum' type='button' value='+'>
<input id='subt' type='button' value='-'>
<input id='mult' type='button' value='*'>
<input id='div' type='button' value='/'>
<input id='clean' type='button' value='C'>
<input id='rand' type='button' value='Rand'>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
</body>
</html>
优点:
清洁代码。没有太多样板代码
无需使用
.call
或.bind
,在我看来,这会使代码更难以理解清晰定义的继承
无需实例化任何内容(新)。类定义的简单存在就足以使一切正常工作。
一旦不需要在html中定义任何内容,就可以在视图和业务层上实现清晰的分离。所有动作都绑定在JavaScript中
不用担心原型
我已经在生产系统中使用了这种方法,并且可以正常工作。
缺点:
我无法调用更高版本的方法。我的意思是,调用已经被覆盖的“祖父”类的方法。仅适用于最后一个方法版本。我很少这样做,所以我认为还可以。
与JavaScript中通常所做的有很大不同。虽然,我对此真的不确定。
这个问题的目的不是简单计算器的代码审查。我是一名高级开发人员,现在已经学习JS一段时间,并且使用其他几种语言和FW进行开发。您可以在我的博客文章此处查看以前研究过的其他(更详细的)OOP方法。
我非常清楚MVC的工作原理以及每一层的功能。我已经做了一个简单的计算器作为示例,以说明我打算如何进行JS OOP。我将UI处理代码和逻辑混合在一起只是为了简化示例。在真实的系统中,我会将其分为不同的类。名称也是如此。在实际代码中,我总是使用非常冗长的名称。
我试图在JS中简化OO,并创建了此技术。正如我所说的,它已经在生产系统中使用过。
这个问题的目的是将这种OOP格式提供给其他开发人员,以查看是否有我看不到的任何问题。所以我不担心这个特定的任务,因为它仅仅是一个例子。为了重点解决这一问题,请坚持在OOP中执行此操作。
如果有人认为还可以,我也希望收到您的来信。
编辑
我通过许多建议的修改创建了一个新问题,在这里:简单的面向对象计算器-后续操作
#1 楼
私有变量我看到了您提供的用于表示哪些变量是公共变量或私有变量的注释:
//private
var IPT_X = '#x';
var IPT_Y = '#y';
//public
self.x = 0;
self.y = 0;
但是这些评论并不太有用。如果您想要一种更好的方法(更标准的是),可以在变量名称之前用下划线表示私有变量。看起来像这样:
var _IPT_X = '#x';
var _IPT_Y = '#y';
self.x = 0;
self.y = 0;
通过查看代码,您正在遵循类似Python的语法。为了保持一致,我将始终将
self
作为第一个参数。var Operators = (function ($, self) {
更改为...
var Operators = (function (self, $) {
重新创建JQuery对象
正如@YiJiang在问题注释中所说,存储JQuery对象比不断调用
$
更好。关于您拥有的私有变量。因此,您可以执行以下操作:var IPT_X = '#x';
更改为...
var _$IPT_X = $('#x');
存储过去的内容
在以下代码中:
// keep superior method so we can call it after overriding
self.oldUpdateOperators = self.updateOperators
I感觉似乎更好的方法来覆盖函数是在
self.updateOperators
上放置一些称为override
的属性。function overridable(self, prop, descriptor) {
Object.defineProperty(self, prop, descriptor);
// If you cannot write over the property then there cannot be an override.
if(!descriptor.writable) return self;
// This is the function that will be used to override the property.
Object.defineProperty(self[prop], "override", { value: function (descriptor) {
// Creates a chain of overridden properties that store the old property values.
var overridden = self[prop];
overridable(self, prop, descriptor);
self[prop].overridden = overridden;
}});
return self;
}
这里是如何使用函数:
var obj = {};
overridable(obj, "add", { value: function(a,b) { return a + b }, writable: true });
obj.add(1,2); // => 3
obj.add.override({ value: function(a,b,c) { return a + b + c }, writable: true });
obj.add(1,2,3); // => 6
obj.add.overridden(1,2,3); // => 3
obj.add.override({ value: function(a,b,c,d) { return a + b + c + d }, writable: true });
obj.add(1,2,3,4); // => 10
obj.add.overridden(1,2,3,4); // => 6
obj.add.overridden.overridden(1,2,3,4); // => 3
注意:绝对不是防弹。因此,请尝试使用它以使其适合您的需求。
有关
Object.defineProperty
的更多信息。要添加的内容
我发现用JavaScript编写库时将所有内容放在一个大函数中来控制范围非常有用。如下所示:
(function(scope) {
/* Library */
})(this)
这可以防止泛洪全局范围,并且您可以控制库连接到的范围。
这是一个例如:
(function(scope, dp) {
var _foo = "bar!";
function _f() { return _foo };
var lib = { bar: _f };
// Making to where it is a non-enumerable, non-configurable, and non-writable property to keep the version stuck there.
dp(lib, "VERSION", { value: "1.0.0" });
dp(scope, "lib", { value: lib });
})(this, Object.defineProperty)
这样,我可以大量使用
Object.defineProperty
之类的东西而不会为所添加的字符感到不适。其他
我觉得您应该看一些著名的库,看看它们如何处理大量代码。然后,尝试将他们的一些技巧带入您的风格,因为它们已经存在了一段时间,并且一定有其作用的原因。
我知道一些“大牌”:
jquery.js
(当然),underscore.js
和backbone.js
。然后有一些我喜欢的东西:Three.js
和Two.js
。还有很多,但我现在能想到的就是这些。
您的风格
我个人的观点是,由于JavaScript是一种脚本语言,所以我认为您可以根据自己的建筑风格来开发自己的风格。您的样式对于您一直在编写的代码很有用,但是我永远不会以一种特定的方式进行设置。可以做一些事情,例如使用要求
self
作为第一个参数的声明来模仿Python语法,但是您应该始终根据所编写的代码来塑造样式。抱歉,发布此消息花了一段时间,我整天不停地工作。希望这会有所帮助!
#2 楼
首先,我想说jQuery的存在对于您完成的任务来说是过大的。可以使用Vanilla DOM处理。此外,您似乎只使用jQuery处理事件和设置值,而addEventListener
和input.value
无法处理。接下来是将UI代码从逻辑上分离,分离关注点。在这里,您的操作模块似乎知道DOM。您的操作员也知道,但是为什么呢?随机还知道您要更新运算符,但随机不仅仅是为您生成随机数吗?
要可视化,它应该类似于
-- events -> -- data -->
DOM UI handling code logic <-> utils
<- results -- <- results --
UI处理代码将处理DOM事件和操作。只有在这里,我们才能看到与DOM相关的代码。逻辑将仅处理业务逻辑,例如特定于应用程序的内容。实用程序(这是一个糟糕的名称)通常是可以帮助您提高逻辑性的库和代码片段,但不一定绑定到您的应用程序(例如您的随机数生成器)。
要注意的另一件事是您的名称。我们使用的是一种非常高级的语言,这与空间无关(存在缩小器)。详细命名您的内容。
x
和y
可以表示任何含义,value1
和value2
怎么样。您拥有选择器的“常量”并没有被命名为包含选择器,但看起来它们实际上包含了对DOM元素的引用(BTN_CLEAN
,但这并不是一个真正的按钮)。这是一个超级选择它的简化版本:
// This part can live in another file, and are only about operations.
// No part of this code is aware of the DOM.
var CalculatorOperations = {
add: function(value1, value2) {
return value1 + value2;
},
subtract: function(value1, value2) {
return value1 - value2;
},
multiply: function(value1, value2) {
return value1 * value2;
},
divide: function(value1, value2) {
return value1 / value2;
},
generateRandomNumber: function() {
return (Math.random() * 1000) | 0;
},
};
// This part manages events and *uses* the operations.
// No part of this code actually does the logic.
(function() {
var input1 = document.getElementById('input-1');
var input2 = document.getElementById('input-2');
var result = document.getElementById('result');
var addButton = document.getElementById('add-button');
var subtractButton = document.getElementById('subtract-button');
var multiplyButton = document.getElementById('multiply-button');
var divideButton = document.getElementById('divide-button');
var clearButton = document.getElementById('clear-button');
var randomButton = document.getElementById('random-button');
function setResult(value) {
result.value = value;
}
function setValue1(value) {
input1.value = value;
}
function setValue2(value) {
input2.value = value;
}
addButton.addEventListener('click', function() {
setResult(CalculatorOperations.add(+input1.value, +input2.value));
});
subtractButton.addEventListener('click', function() {
setResult(CalculatorOperations.subtract(+input1.value, +input2.value));
});
multiplyButton.addEventListener('click', function() {
setResult(CalculatorOperations.multiply(+input1.value, +input2.value));
});
divideButton.addEventListener('click', function() {
setResult(CalculatorOperations.divide(+input1.value, +input2.value));
});
clearButton.addEventListener('click', function() {
setValue1('');
setValue2('');
setResult('');
});
randomButton.addEventListener('click', function() {
setValue1(CalculatorOperations.generateRandomNumber());
setValue2(CalculatorOperations.generateRandomNumber());
});
}());
X:
<input id='input-1'>
<br>Y:
<input id='input-2'>
<br>Res:
<input id='result'>
<br>
<input id='add-button' type='button' value='+'>
<input id='subtract-button' type='button' value='-'>
<input id='multiply-button' type='button' value='*'>
<input id='divide-button' type='button' value='/'>
<input id='clear-button' type='button' value='C'>
<input id='random-button' type='button' value='Rand'>
OOP不是“必须的”。这只是JavaScript中可用的范例之一。尽管“必须”是使您的代码简单易维护。在您的版本中,我到处跑是因为一些代码做到了这一点,而另一部分又做到了。在上面的代码中,我知道与DOM相关的代码在哪里发生,逻辑在哪里发生,谁在做什么。
评论
\ $ \ begingroup \ $
您的代码不正确。对于55 + 65,它将返回5565。您应该使用+ val1 + + val2或parseInt(val1,10)+ parseInt(val2,10)
\ $ \ endgroup \ $
–伊斯梅尔·米格尔(Ismael Miguel)
15年12月24日在21:07
\ $ \ begingroup \ $
同意约瑟夫,最近,我开始使用+ val1 + + val2来确保将字符串类型的数字转换为数字。除非我也想截断小数,否则我将远离parseInt。我不喜欢parseInt的另一件事是,它忽略字符串中数字之后的非数字。
\ $ \ endgroup \ $
–将
2015年12月25日在5:10
\ $ \ begingroup \ $
不错的评论。我还有2个附加功能:a)您可以节省大量时间来编写快捷方式$ = function(el){return document.querySelectorAll(el)} b)您可以使用委托事件侦听器:给按钮id如“ add ”,“减”,您可以根据名称切换到计算器操作
\ $ \ endgroup \ $
– Thomas垃圾
2015年12月25日在9:03
#3 楼
您拥有的内容更接近装饰器模式:您正在用附加功能装饰基础对象。它不是完全继承。它也不是完全“类”,因为它实际上只是一个对象实例。您的结构导致一个主要问题:您的3个“类”是完全相同的对象。您只有3个变量对其进行引用。
此外,由于您正在使用IIFE来定义它们中的每一个,因此您无法再次实例化/创建某些东西。一旦被评估,装饰器函数将丢失,仅保留装饰的对象。无论哪种方式,都不是典型的面向对象设计。
它也不是通常的装饰器模式。真正的装饰器函数将是通用的,并且适用于多种类型的对象(例如,请参见prototype.js;它基本上通过方法的组合来装饰不同的本机浏览器对象),但是在这里,这更像是将代码划分为命名的“步骤”。虽然有用,但也可以通过代码注释来完成。
函数之间的耦合也困扰着我。您可以进行一些轻度的重构,从而简单地删除每个调用,因此可以重复使用它们,例如:
转而得到一个合理有用的对象,因为每个对象都依赖于前一个对象。当然,这是子类的工作方式,但是由于这不是完全子类,因此您必须自己进行管理。即唯一真正有用的调用是
Operations(Randomizer(Operators(x)))
。之所以能够满足这种耦合,是因为您使用了IIFE,并确保只用一次“正确的方式”进行调用。我将通过在函数中明确依赖项链来对此进行更改:
var Operators = function ($, self) { ... };
var Randomizer = function (self, math) { ... };
var Operations = function (self, $) { ... };
现在,它更像是典型的“类”:
您可以调用任何“链中的链接”,并且它请确保其“祖先”被调用。
您可以从任何步骤中获得一个“实例”。它们不仅是一次IIFE。
如果您有要扩展的兼容对象,则可以将它们用作常规装饰器。
仍然有足够的耦合此处(也针对页面上的UI元素),这不是最直接的构造。它仍然闻起来像以复杂的方式将代码块分成多个单独的块。
总而言之:当然可以,并且可读性强。但这也有点奇怪。
其他说明
Operators
应该命名为Operands
。运算符是+
,-
等。操作数是像x
和y
这样的运算符所要操作的值。与您的参数一致。
Operators
将self
作为其第二个参数,而其他两个将self
作为第一个参数。容易弄乱。不要传递不需要的东西。
Math
是全局对象,因此将其作为名为math
的参数传递实际上并没有任何用处。 将
jQuery
传递为$
更有意义,因为这意味着即使在全局范围内使用$
,也可以使用jQuery.noConflict()
别名。但是,由于您可能想对所有函数都使用它,因此我将执行以下操作:var Operators = function ($, self) {
self = self || {};
// ...
return self;
};
var Randomizer = function (self, math) {
self = Operators(self);
// ...
return self;
};
var Operations = function (self, $) {
self = Randomizer(self);
// ...
return self;
};
您实际上无法以以下形式输入自己的值。或:可以,但不会改变结果。视图中的更改不会反映在对象的状态中。只是相反。即使这样,也只有在您致电
showOperators
(哼,“ showOperands”)时才这样做。因此,恐怕它作为计算器并不是很有用。评论
\ $ \ begingroup \ $
哇...你们摇滚。我真的很喜欢这个网站。只有爱,没有苛刻的评论。与我们Stackoverflow的正常工作有何不同。 :D那是一个很好的批评和建议。明天我将尝试您建议的修改,然后再回头。非常感谢 :)
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
2015年12月25日4:44
\ $ \ begingroup \ $
根据答案对我进行了许多修改,对问题进行了修改。 :)
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
2015年12月26日下午3:43
\ $ \ begingroup \ $
@NelsonTeixeira一旦被回答,请不要编辑问题。它使我们所有的答案都是无效和无关紧要的。 Q&A格式取决于匹配Q。如果您有新代码,请张贴新问题,然后获得新答案。我会自由地撤消您的编辑(除非在我写作时有人殴打我)
\ $ \ endgroup \ $
– Flaambino
15/12/26在3:47
\ $ \ begingroup \ $
好的...那我要提一个新问题
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
2015年12月26日,下午3:50
\ $ \ begingroup \ $
@NelsonTeixeira Nope-实际上,这是值得鼓励的:)因此,一定要将两者联系起来!您也可以使用新问题的链接来编辑此问题。关键是不要对现有问题的主要内容进行太多修改,以使答案不同步,但是链接到后续问题就可以了。
\ $ \ endgroup \ $
– Flaambino
15/12/26在3:57
评论
\ $ \ begingroup \ $
很有帮助! :D我发现Objet.defineProperty特别有趣,并将对其进行更深入的研究。这是我的技术缺乏的东西。在层次结构链中达到更高地位的一种方法。谢谢!仅作记录,我不打算使用python语法。我来自Pascal / C ++ / Java / PHP背景。我仍然维护一个Python项目。我使用self是因为我不能在语法中使用它。所以我不得不选择一些东西,自我对我来说似乎是正确的选择。但这不是一个封闭的问题。我可以使用“ ths”或“ this_”。你认为呢 ?
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
15年12月24日在23:55
\ $ \ begingroup \ $
您会推荐哪些“大牌”图书馆?我现在可以想到prototype.js。任何其他 ?
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
15年12月24日在23:56
\ $ \ begingroup \ $
还有一个问题:我最想知道的是,这种操作方式是否存在一些严重的缺陷。根据您的回答并考虑您的建议修改,我认为您基本上可以找到它。我对吗 ?
\ $ \ endgroup \ $
–尼尔森·特谢拉(Nelson Teixeira)
2015年12月24日23:58
\ $ \ begingroup \ $
@NelsonTeixeira。自我是一个很好的选择,我自己使用。 (或者我在我懒惰的时候笑)。它让我想起了Python。没有“关键缺陷”,我认为您应该更改样式以适合您要实现的目标。由于您使它看起来更像是面向对象的,对我来说,这很好。我仍然会添加我正在谈论的范围界定,以保护全球范围。您可以做的是建立自己的模拟OOP的库,以了解有关JavaScript的更多信息。 (写方法如可重写)
\ $ \ endgroup \ $
–tkellehe
2015年12月25日,0:29
\ $ \ begingroup \ $
@NelsonTeixeira。听起来不错!如果可以的话,我想最终看看您最终会做什么:)
\ $ \ endgroup \ $
–tkellehe
2015年12月25日下午2:27