在Javascript中扩展Number.prototype和Math对象?

31

我一直想知道为什么JavaScript有全局的Math对象,而不是让数字拥有自己的方法。这样做有好的原因吗?

还有除效率之外的任何缺点吗?:

Number.prototype.round = function(){
    return Math.round(this);
};

只是为了明确,我理解像π这样的常量需要有一个地方以及应用于多个数字的函数,例如min/max。问题主要涉及仅影响单个数字的方法,例如round、abs、sin、pow等。

8个回答

26

Math对象存在的原因很简单:“因为Java这样做”。虽然不是最好的理由,但我们已经这样了。我想在Douglas Crockford开始他的运动压制了语言的一半之前,事情更加合理。最初你“被允许”或者说是打算这样做:

with (Math) {
  var n = min( round(a) * round(b), sqrt(c) );
  var result = exp( n + d );
}
在扩展 Number.prototype 的缺点是其他人可能会做同样的事情。更糟糕的是,例如,将 Number.prototype.round 定义为一个对称舍入函数。

如果你正在寻找使生活变得更轻松的方法,为什么要止步于此?为什么不直接将 Math 函数作为全局函数包含进来呢?
var m = 'abs acos asin atan atan2 ceil cos exp floor log max min ' +
        'pow random round sin sqrt tan PI').split(' ');
for (var i=0,l=m.length; i<l; i++) {
  window[ m[i] ] = Math[ m[i] ];
}

这将把所有数学函数放入全局作用域,实际上允许您停止输入 "Math."。问问自己:扩展Number和使用这些函数扩展window之间是否有任何真正的区别?

* 在你炮轰我之前:Crockford的评论不应该被过分认真对待。我同意他在隐式全局环境中使用with是非常危险的。


24

除了会让其他人感到困惑之外,扩展Number.prototype没有任何缺点。那么,使用value.round()有什么好处呢?相比之下,使用Math.round(value)有什么不好的呢?

以下是使用Math对象的几个好处:

  1. 它也适用于非数字类型:Math.round("5")是可行的,而value.round()在value为字符串(例如文本框的值)时无法正常工作。
  2. Math对象的某些成员不属于“主要”数字值,例如Math.min()Math.max()。你想用它来执行像a.max(b)这样的操作吗?
  3. 其他成员是全局的,不属于特定的数字类型。例如,像Math.PI这样的常量或函数Math.random()

好的观察,+1。不过我想知道,为什么Douglas Crockford说JavaScript在从Java借用Math对象时犯了一个错误。 - Ionuț G. Stan
@Ferdinand:我从来没有打算使用类似“a.max('5')”这样的东西。一些函数和常量属于Math对象(我会更新我的帖子以使其更清晰)。我感兴趣的是仅适用于数字的那些函数。你提到可以使用字符串是一个好点。 - AnnanFay
还有性能方面的考虑——value.anything需要将值装箱,例如将值转换为对象。(偶尔可以避免这种情况,但在您可能使用的任何情况下都不行) - olliej
我个人认为上面的第一点是一个非常有用的功能。尝试对一个字符串进行四舍五入应该会抛出一个错误。@olliej提到的值装箱也是一个非常好的观点! - AnnanFay

18

尝试执行123.round();

你的JavaScript控制台会抛出几百个错误给你看:P,不要...

你可以这样做:

Number.prototype.x 然后: (123).x(); 但不要使用123.x();


2
我一直想知道为什么不能执行123.method()。我知道由于浮点数的原因不允许可能更容易,但是我认为Ruby允许这种语法并且会使一切变得更加容易! - AnnanFay
6
еҸҜд»ҘдҪҝз”Ё123..round()жҲ–(123).round()пјҢдёӨиҖ…йғҪеҸҜд»ҘгҖӮ123..round() === 123.0.round()гҖӮ - sam
直到你开始使用像Vue这样的框架,然后你就会看到 Error compiling template: - Yevgeniy Afanasyev

1

我相信这样调用也可以正常工作,因为Number的原型函数和其他对象的原型函数的工作方式是一样的:

5.5["round"]();
//should return 6

1

我认为数学不仅仅是一组数字方法,它的应用范围更广。例如,使用NumberVariable.PI可能会让人感到困惑。同样的道理,随机数生成也是如此。

另外,我认为扩展数字是可以的,因为这是JS本质的一部分。如果我在这里有错的话,或许有人可以纠正我。


你几乎可以扩展任何东西,但不能扩展Array对象。这是因为在扩展Array对象时会变得很烦人,因为你就不能使用简单的for..in循环了(是的,我知道你可以使用for(),但那看起来太丑了)。 - Pim Jager
3
@Pim Jager:你不应该在数组中使用 for...in 循环,它是为对象设计的。这就是为令人真正不满意的情况——扩展 Object.prototype… - Shog9
2
如果你的JavaScript版本支持Object.defineProperty,你可以进行扩展。有一个选项是enumerable:false。 - dresende

1

所以,先不论这是否是个好主意,你可以很好地执行这项任务。

你可以执行123.x(),但js解释器可能会出错(因为它无法将点解释为消息分发)

奇怪的是,你可以用123 .x()(数字和点之间有一个空格)代替,这样就可以工作了。

123..也能行,但是这是因为你实际上已经创建了一个小数并对其进行了分发

(typeof 123.) === 'number') // true

尝试在控制台中输入:

Number.prototype.times = function(other) { return this * other }; 3 .times(5);


当它按照预期的方式工作时,它并不是“损坏”的。 - snarf

0

0

我的看法是,如果您进行适当的检查以避免覆盖原生功能,使用符合本地命名标准的命名方式,使您的代码更易读和可管理,那么请让您的代码更加舒适和便利。

if (Number.prototype.round == null)
    Number.prototype.round = function() { return Math.round(this); }

关于使用这个,由于JavaScript的特性,我认为最好的方法是:

  • 将数字包装在变量中而不是简单的静态值(大多数情况下)
    • nNum.round();
  • 将简单的静态值用括号括起来:
    • (123).round();
  • 使用方括号表示法调用
    • 123["round"].call();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接