18

如何在不修改其原型的情况下扩展核心 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 上显式调用它们。

是否可以这样扩展核心类型?

4

5 回答 5

18

2 年后:在全球范围内改变任何东西都是一个糟糕的主意

原来的:

在 ES5 浏览器中扩展原生原型存在一些“错误”是 FUD。

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

但是,如果您必须支持 ES3 浏览器,那么人们for ... in在字符串上使用循环就会出现问题。

我的观点是你可以改变原生原型并且应该停止使用任何写得不好的代码

于 2011-08-21T23:48:48.387 回答
3

更新:即使此代码也没有完全扩展本机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. 因此,尽管您可以使这些方法起作用,但其他属性似乎更棘手。

于 2011-08-21T23:34:40.370 回答
2

首先,在这段代码中:

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

现在关于你的错误。你调用reverse了你的MyString对象。这个方法在哪里定义?在原型中,与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 中关于 Array and String generics的部分。

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

至于在不更改原型的情况下扩展确切的内置类型,例如String等等Date,你真的不需要,不创建包装器或委托或子类。但是这样就不允许像这样的语法

d1.itervalTo(d2)

whered1和是您没有扩展其原型d2的内置类的实例。:-) JavaScript 为这种方法调用语法使用原型链。它就是这样。很好的问题虽然......但这就是你的想法吗?Date

于 2011-08-21T23:27:44.507 回答
0

你这里只错了一处。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它解释说你不能改变布尔、整数和字符串的类型,因此它们不能“子类”。

于 2011-08-21T23:14:02.950 回答
0

我认为基本的答案是你可能不能。您可以做的是 Sugar.js 所做的 - 创建一个类似对象的对象并从中扩展:

http://sugarjs.com/

Sugar.js 是关于原生对象扩展的,它们扩展 Object.prototype。

于 2013-07-12T16:51:44.540 回答