扩展核心类型而无需修改原型

时间:2011-08-21 23:04:04

标签: javascript prototypal-inheritance

如何在不修改原型的情况下扩展核心JavaScript类型(String,Date等)?例如,假设我想用一些方便的方法创建一个派生的字符串类:

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上显式调用它们。

是否可以扩展核心类型?

5 个答案:

答案 0 :(得分:17)

2年后:在全球范围内改变任何事情都是一个糟糕的想法

<强>原始

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

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

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

我的意见是你可以改变原生原型,并且应该停止使用任何破坏不良的代码

答案 1 :(得分:3)

更新:即使此代码也未完全扩展原生String类型(length属性不起作用)。

Imo可能不值得遵循这种方法。有太多事情需要考虑,你必须投入太多时间来确保它完全有效(如果它完全没有)。 @Raynos provides another interesting approach

然而,这是一个想法:


似乎你不能在真正的字符串之外的任何东西上调用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 specification并查看方法的规范。

E.g。 String.prototype.split算法的第二步是:

  

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

如果将对象传递给ToString,则它基本上调用此对象的toString方法。这就是我们覆盖toString时的原因。

更新 无法正常工作s.length。因此,尽管您可以使方法工作,但其他属性似乎更棘手。

答案 2 :(得分: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使用字符串的内部表示来执行其操作(即返回其内部原始字符串),并且不能应用于共享字符串原型的任意目标对象< / em>的。所以当你打电话给split时,分裂的实现说“哦,我的目标不是一个字符串,让我打电话给toString,”然后toString说“我的目标不是一个字符串,我不是通用的“所以它扔了TypeError

如果您想了解有关JavaScript中泛型的更多信息,请参阅this MDN section on Array and String generics

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

至于扩展精确的内置类型,如StringDate等,而不更改原型,你真的不会,不创建包装或委托或子类。但是,这将不允许像

这样的语法
d1.itervalTo(d2)

其中d1d2是内置Date类的实例,其的原型未扩展。 :-) JavaScript使用原型链来实现这种方法调用语法。它就是这样。但是很好的问题......但这是你想到的吗?

答案 3 :(得分: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它解释了你不能改变bool,int和字符串的类型,因此它们不能被“子类化”。

答案 4 :(得分:0)

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

http://sugarjs.com/

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