在不修改原型的情况下扩展核心类型

发布于 2024-11-30 20:20:21 字数 755 浏览 0 评论 0原文

如何在不修改原型的情况下扩展核心 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

彼岸花ソ最美的依靠 2024-12-07 20:20:21

2年后:在全局范围内改变任何东西都是一个糟糕的想法

原始:

扩展原生原型是“错误”的,这在ES5浏览器中是FUD。

Object.defineProperty(String.prototype, "my_method", {
  value: function _my_method() { ... },
  configurable: true,
  enumerable: false,
  writeable: true
});

但是,如果您必须支持 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.

Object.defineProperty(String.prototype, "my_method", {
  value: function _my_method() { ... },
  configurable: true,
  enumerable: false,
  writeable: true
});

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

欢烬 2024-12-07 20:20:21

更新: 即使此代码也没有完全扩展本机 String 类型(length 属性不起作用)。

在我看来,采用这种方法可能不值得。有太多的事情需要考虑,并且您必须投入太多的时间来确保它充分发挥作用(如果它完全有效)。 @Raynos 提供了另一种有趣的方法

尽管如此,这个想法是这样的:


似乎你不能在真正的字符串之外的任何东西上调用String.prototype.toString。您可以覆盖此方法:

// constructor
function MyString(s) {
    String.call(this, s); // call the "parent" constructor
    this.s_ = s;
}

// create a new empty prototype to *not* override the original one
tmp = function(){};
tmp.prototype = String.prototype;
MyString.prototype = new tmp();
MyString.prototype.constructor = MyString;

// new method
MyString.prototype.reverse = function() {
  return this.split('').reverse().join('');
};

// override 
MyString.prototype.toString = function() {
  return this.s_;
};

MyString.prototype.valueOf = function() {
  return this.s_;
};


var s = new MyString("Foobar");
alert(s.reverse());

如您所见,我还必须覆盖valueOf才能使其正常工作。

但是:我不知道这些是否是您必须重写的唯一方法,对于其他内置类型,您可能必须重写其他方法。一个好的开始是采用 ECMAScript 规范 并拥有看一下方法的规范。

例如,String.prototype.split 算法的第二步是:

S为调用ToString的结果,并将this值作为其参数。

如果一个对象被传递给ToString,那么它基本上会调用该对象的toString方法。这就是为什么当我们重写 toString 时它会起作用。

更新:不起作用的是s.length。因此,尽管您可能能够使这些方法发挥作用,但其他属性似乎更加棘手。

Update: Even this code does not fully extend the native String type (the length 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:

// constructor
function MyString(s) {
    String.call(this, s); // call the "parent" constructor
    this.s_ = s;
}

// create a new empty prototype to *not* override the original one
tmp = function(){};
tmp.prototype = String.prototype;
MyString.prototype = new tmp();
MyString.prototype.constructor = MyString;

// new method
MyString.prototype.reverse = function() {
  return this.split('').reverse().join('');
};

// override 
MyString.prototype.toString = function() {
  return this.s_;
};

MyString.prototype.valueOf = function() {
  return this.s_;
};


var s = new MyString("Foobar");
alert(s.reverse());

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:

Let S be the result of calling ToString, giving it the this value as its argument.

If an object is passed to ToString, then it basically calls the toString method of this object. And that is why it works when we override toString.

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.

去了角落 2024-12-07 20:20:21

首先,在这段代码中:

MyString.prototype = String.prototype;   
MyString.prototype.reverse = function() {
    this.split('').reverse().join('');
};

变量 MyString.prototypeString.prototype 都引用同一个对象!分配给一个就是分配给另一个。当您将 reverse 方法放入 MyString.prototype 时,您也将其写入 String.prototype。所以试试这个:

MyString.prototype = String.prototype;   
MyString.prototype.charAt = function () {alert("Haha");}
var s = new MyString();
s.charAt(4);
"dog".charAt(3);

最后两行发出警报,因为它们的原型是同一个对象。您确实扩展了String.prototype

现在关于你的错误。您对 MyString 对象调用了 reverse。这个方法在哪里定义的?在原型中,与String.prototype相同。您覆盖了reverse。它做的第一件事是什么?它对目标对象调用split。现在的问题是,为了让 String.prototype.split 工作,它必须调用 String.prototype.toString。例如:

var s = new MyString();
if (s.split("")) {alert("Hi");}

此代码会生成错误:

TypeError: String.prototype.toString is not generic

这意味着 String.prototype.toString 使用字符串的内部表示来执行其操作(即返回其内部原始字符串),并且 不能应用于共享字符串原型的任意目标对象。因此,当您调用 split 时,split 的实现会说“哦,我的目标不是字符串,让我调用 toString”,但随后 toString说“我的目标不是字符串,而且我不是通用的”,所以它抛出了 TypeError

如果您想了解有关 JavaScript 中泛型的更多信息,您可以参阅这个关于数组和字符串的 MDN 部分泛型

至于让它在没有错误的情况下工作,请参阅 Alxandr 的答案。

至于扩展确切的内置类型,如StringDate等而不改变它们的原型,你真的不需要,而不创建包装器或委托或子类。但是,这将不允许使用像

d1.itervalTo(d2)

d1d2 是内置 Date 类的实例的语法,该类的原型是您没有延长。 :-) JavaScript 使用原型链来实现这种方法调用语法。确实如此。虽然这是一个很好的问题...但这就是您的想法吗?

First of all, in this code:

MyString.prototype = String.prototype;   
MyString.prototype.reverse = function() {
    this.split('').reverse().join('');
};

the variables MyString.prototype and String.prototype are both referencing the same object! Assigning to one is assigning to the other. When you dropped a reverse method into MyString.prototype you were also writing it to String.prototype. So try this:

MyString.prototype = String.prototype;   
MyString.prototype.charAt = function () {alert("Haha");}
var s = new MyString();
s.charAt(4);
"dog".charAt(3);

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 your MyString object. Where is this method defined? In the prototype, which is the same as String.prototype. You overwrote reverse. What is the first thing it does? It calls split on the target object. Now the thing is, in order for String.prototype.split to work it has to call String.prototype.toString. For example:

var s = new MyString();
if (s.split("")) {alert("Hi");}

This code generates an error:

TypeError: String.prototype.toString is not generic

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 called split, the implementation of split said "oh my target is not a string, let me call toString," but then toString said "my target is not a string and I'm not generic" so it threw the TypeError.

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 and Date 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 like

d1.itervalTo(d2)

where d1 and d2 are instances of the built-in Date 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?

江挽川 2024-12-07 20:20:21

你这里只错了一部分。 MyString.prototype 不应该是 String.prototype,它应该是这样的:

function MyString(s) { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
  this.split('').reverse().join('');
};
var s = new MyString("Foobar");
s.reverse();

[编辑]

为了以更好的方式回答你的问题,不,它不应该是可能的。
如果你看一下这个: 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:

function MyString(s) { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
  this.split('').reverse().join('');
};
var s = new MyString("Foobar");
s.reverse();

[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".

离不开的别离 2024-12-07 20:20:21

我认为基本的答案是你可能不能。你可以做的就是 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文