在不修改原型的情况下扩展核心类型
如何在不修改原型的情况下扩展核心 JavaScript 类型(字符串、日期等)?例如,假设我想使用一些方便的方法创建一个派生字符串类:
function MyString() { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
return this.split('').reverse().join('');
};
var s = new MyString("Foobar"); // Hmm, where do we use the argument?
s.reverse();
// Chrome - TypeError: String.prototype.toString is not generic
// Firefox - TypeError: String.prototype.toString called on incompatible Object
该错误似乎源自 String 基本方法,在这种情况下可能是“拆分”,因为它的方法被应用于某些非字符串对象。但是,如果我们不能将 应用于非字符串对象,那么我们真的可以自动重用它们吗?
[编辑]
显然,我的尝试在很多方面都有缺陷,但我认为这表明了我的意图。经过一番思考,我们似乎无法重用任何 String 原型对象的函数,除非在 String 上显式调用它们。
是否可以这样扩展核心类型?
How does one extend core JavaScript types (String, Date, etc.) without modifying their prototypes? For example, suppose I wanted to make a derived string class with some convenience methods:
function MyString() { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
return this.split('').reverse().join('');
};
var s = new MyString("Foobar"); // Hmm, where do we use the argument?
s.reverse();
// Chrome - TypeError: String.prototype.toString is not generic
// Firefox - TypeError: String.prototype.toString called on incompatible Object
The error seems to originate from String base methods, probably "split" in this case, since its methods are being applied to some non-string object. But if we can't apply the to non-string objects then can we really reuse them automatically?
[Edit]
Obviously my attempt is flawed in many ways but I think it demonstrates my intent. After some thinking, it seems that we can't reuse any of the String prototype object's functions without explicitly calling them on a String.
Is it possible to extend core types as such?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
2年后:在全局范围内改变任何东西都是一个糟糕的想法
原始:
扩展原生原型是“错误”的,这在ES5浏览器中是FUD。
但是,如果您必须支持 ES3 浏览器,那么人们在字符串上使用
for ... in
循环就会出现问题。我的观点是,您可以更改本机原型,并且应该停止使用任何会破坏的编写不良的代码
2 years later: mutating anything in global scope is a terrible idea
Original:
There being something "wrong" with extending native prototypes is FUD in ES5 browsers.
However if you have to support ES3 browsers then there are problems with people using
for ... in
loops on strings.My opinion is that you can change native prototypes and should stop using any poorly written code that breaks
更新: 即使此代码也没有完全扩展本机
String
类型(length
属性不起作用)。在我看来,采用这种方法可能不值得。有太多的事情需要考虑,并且您必须投入太多的时间来确保它充分发挥作用(如果它完全有效)。 @Raynos 提供了另一种有趣的方法。
尽管如此,这个想法是这样的:
似乎你不能在真正的字符串之外的任何东西上调用
String.prototype.toString
。您可以覆盖此方法:如您所见,我还必须覆盖
valueOf
才能使其正常工作。但是:我不知道这些是否是您必须重写的唯一方法,对于其他内置类型,您可能必须重写其他方法。一个好的开始是采用 ECMAScript 规范 并拥有看一下方法的规范。
例如,
String.prototype.split
算法的第二步是:如果一个对象被传递给
ToString
,那么它基本上会调用该对象的toString
方法。这就是为什么当我们重写 toString 时它会起作用。更新:不起作用的是
s.length
。因此,尽管您可能能够使这些方法发挥作用,但其他属性似乎更加棘手。Update: Even this code does not fully extend the native
String
type (thelength
property does not work).Imo it's probably not worth it to follow this approach. There are too many things to consider and you have to invest too much time to ensure that it fully works (if it does at all). @Raynos provides another interesting approach.
Nevertheless here is the idea:
It seems that you cannot call
String.prototype.toString
on anything else than a real string. You could override this method:As you see, I also had to override
valueOf
to make it work.But: I don't know whether these are the only methods you have to override and for other built-in types you might have to override other methods. A good start would be to take the ECMAScript specification and have a look at the specification of the methods.
E.g. the second step in the
String.prototype.split
algorithm is:If an object is passed to
ToString
, then it basically calls thetoString
method of this object. And that is why it works when we overridetoString
.Update: What does not work is
s.length
. So although you might be able to make the methods work, other properties seem to be more tricky.首先,在这段代码中:
变量
MyString.prototype
和String.prototype
都引用同一个对象!分配给一个就是分配给另一个。当您将reverse
方法放入MyString.prototype
时,您也将其写入String.prototype
。所以试试这个:最后两行都发出警报,因为它们的原型是同一个对象。您确实扩展了
String.prototype
。现在关于你的错误。您对
MyString
对象调用了reverse
。这个方法在哪里定义的?在原型中,与String.prototype
相同。您覆盖了reverse
。它做的第一件事是什么?它对目标对象调用split
。现在的问题是,为了让String.prototype.split
工作,它必须调用String.prototype.toString
。例如:此代码会生成错误:
这意味着
String.prototype.toString
使用字符串的内部表示来执行其操作(即返回其内部原始字符串),并且 不能应用于共享字符串原型的任意目标对象。因此,当您调用split
时,split 的实现会说“哦,我的目标不是字符串,让我调用toString
”,但随后toString
说“我的目标不是字符串,而且我不是通用的”,所以它抛出了TypeError
。如果您想了解有关 JavaScript 中泛型的更多信息,您可以参阅这个关于数组和字符串的 MDN 部分泛型。
至于让它在没有错误的情况下工作,请参阅 Alxandr 的答案。
至于扩展确切的内置类型,如
String
和Date
等而不改变它们的原型,你真的不需要,而不创建包装器或委托或子类。但是,这将不允许使用像d1
和d2
是内置Date
类的实例的语法,该类的原型是您没有延长。 :-) JavaScript 使用原型链来实现这种方法调用语法。确实如此。虽然这是一个很好的问题...但这就是您的想法吗?First of all, in this code:
the variables
MyString.prototype
andString.prototype
are both referencing the same object! Assigning to one is assigning to the other. When you dropped areverse
method intoMyString.prototype
you were also writing it toString.prototype
. So try this:The last two lines both alert because their prototypes are the same object. You really did extend
String.prototype
.Now about your error. You called
reverse
on yourMyString
object. Where is this method defined? In the prototype, which is the same asString.prototype
. You overwrotereverse
. What is the first thing it does? It callssplit
on the target object. Now the thing is, in order forString.prototype.split
to work it has to callString.prototype.toString
. For example:This code generates an error:
What this means is that
String.prototype.toString
uses the internal representation of a string to do its thing (namely returning its internal primitive string), and cannot be applied to arbitrary target objects that share the string prototype. So when you calledsplit
, the implementation of split said "oh my target is not a string, let me calltoString
," but thentoString
said "my target is not a string and I'm not generic" so it threw theTypeError
.If you want to learn more about generics in JavaScript, you can see this MDN section on Array and String generics.
As for getting this to work without the error, see Alxandr's answer.
As for extending the exact built-in types like
String
andDate
and so on without changing their prototypes, you really don't, without creating wrappers or delegates or subclasses. But then this won't allow the syntax likewhere
d1
andd2
are instances of the built-inDate
class whose prototype you did not extend. :-) JavaScript uses prototype chains for this kind of method call syntax. It just does. Excellent question though... but is this what you had in mind?你这里只错了一部分。 MyString.prototype 不应该是 String.prototype,它应该是这样的:
[编辑]
为了以更好的方式回答你的问题,不,它不应该是可能的。
如果你看一下这个: https://developer.mozilla.org /en/JavaScript/Reference/Global_Objects/Object/constructor 它解释了您无法更改布尔、整数和字符串的类型,因此它们不能被“子类化”。
You got only one part wrong here. MyString.prototype shouldn't be String.prototype, it should be like this:
[Edit]
To answer your question in a better way, no it should not be possible.
If you take a look at this: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor it explains that you can't change the type on bools, ints and strings, thus they cannot be "subclassed".
我认为基本的答案是你可能不能。你可以做的就是 Sugar.js 所做的 - 创建一个类似对象的对象并从中扩展:
http://sugarjs.com/
Sugar.js 都是关于原生对象扩展的,它们不扩展Object.prototype。
I think the basic answer is you probably can't. What you can do is what Sugar.js does - create an object-like object and extend from that:
http://sugarjs.com/
Sugar.js is all about native object extensions, and they do not extend Object.prototype.