如何最好地继承本机JavaScript对象? (特别是字符串)

时间:2011-07-24 01:16:02

标签: javascript inheritance

我是一个长期浏览器,但是第一次参与。如果我遗漏任何礼仪细节,请告诉我!

另外,我搜索过高低,包括这个网站,但我还没有找到一个明确而简洁的解释,正是我要做的事情。如果我错过了它,请指出我正确的方向!

好吧,我想扩展一些原生的JavaScript对象,比如Array和String。但是,我不想实际扩展它们,而是创建从它们继承的新对象,然后修改它们。

对于Array,这可行:

var myArray = function (n){
    this.push(n);

    this.a = function (){
        alert(this[0]);
    };
}

myArray.prototype = Array.prototype;

var x = new myArray("foo");
x.a();

但是,对于String,同样不起作用:

var myString = function (n){
    this = n;

    this.a = function (){
        alert(this);
    };
}

myString.prototype = String.prototype;

var x = new myString("foo");
x.a();

我也试过了:

myString.prototype = new String();

现在,在尝试研究这个问题时,我发现这确实有效:

var myString = function (n){
    var s = new String(n);

    s.a = function (){
        alert(this);
    };

    return s;
}

var x = myString("foo");
x.a();

但是,这几乎就像是在欺骗我。比如,我应该使用“真正的”继承模型,而不是这个快捷方式。

所以,我的问题:

1)你能否告诉我在继承String时我做错了什么? (最好有一个工作实例......)

2)在“真正的”继承示例和“快捷方式”示例之间,您能说出一种方式明显的利益或损害吗?或者也许只是在一个人如何在功能上运作的一些差异? (因为他们看起来对我来说最终......)

全部谢谢!

修改

感谢所有评论/回答的人。我认为@ CMS的信息是最好的,因为:

1)他回答了我的String继承问题,指出通过在我自己的字符串对象中部分重新定义String,我可以使它工作。 (例如重写toString和toValue) 2)创建一个继承自Array的新对象具有自己的局限性,即使部分重新定义Array,也无法立即看到并无法解决。

从上面的两件事中,我得出结论,JavaScript的继承性声明只扩展到您自己创建的对象,而当涉及到本机对象时,整个模型就会崩溃。 (这可能就是为什么你找到的90%的例子是Pet-> Dog或Human-> Student,而不是String-> SuperString)。这可以解释为@ chjj的答案,即这些对象实际上是原始值,即使JS中的所有内容似乎都是一个对象,因此应该是100%可继承的。

如果结论完全没有,请纠正我。如果它是准确的,那么我确信除了我自己之外,这对任何人都不是新闻 - 但是再次感谢大家的评论。我想我现在可以选择:

要么继续使用寄生继承(我的第二个例子,我现在知道它的名称)并尝试尽可能减少其内存使用影响,或做一些像@ davin,@ Jeff或@chjj建议和psudo-重新定义或完全重新定义这些对象(这似乎是浪费)。

@CMS - 将您的信息编译成答案,我会选择它。

6 个答案:

答案 0 :(得分:12)

这种痛苦简单但有缺陷的方式是:

var MyString = function() {};

MyString.prototype = new String();

你所要求的是奇怪的,因为通常在JS中,你不是将它们视为字符串对象,而是将它们视为“字符串”类型,作为原始值。而且,字符串根本不可变。通过指定.toString方法,您可以让任何对象,就像它是一个字符串一样:

var obj = {};
obj.toString = function() {
  return this.value;
};
obj.value = 'hello';

console.log(obj + ' world!');

但显然它不会有任何字符串方法。你可以通过几种方式做到继承。其中一个是javascript应该使用的“原始”方法,以及你和我在上面发布的,或者:

var MyString = function() {};
var fn = function() {};
fn.prototype = String.prototype;
MyString.prototype = new fn();

这允许在不调用构造函数的情况下添加到原型链。

ES5的方式是:

MyString.prototype = Object.create(String.prototype, {
  constructor: { value: MyString }
});

非标准但最方便的方式是:

MyString.prototype.__proto__ = String.prototype;

所以,最后,你可以做的是:

var MyString = function(str) {
  this._value = str;
};

// non-standard, this is just an example
MyString.prototype.__proto__ = String.prototype;

MyString.prototype.toString = function() {
  return this._value;
};

继承的字符串方法可能使用该方法工作,我不确定。我认为他们可能是因为有一个toString方法。这取决于它们如何在内部由任何特定的JS引擎实现。但他们可能不会。你只需要定义自己的。再一次,你所要求的是非常奇怪的。

您也可以尝试直接调用父构造函数:

var MyString = function(str) {
  String.call(this, str);
};

MyString.prototype.__proto__ = String.prototype;

但这也略显粗略。

无论你想做什么,都可能不值得。我认为有更好的方法可以解决你想要使用它的问题。

如果您想要一种绝对可靠的方式:

// warning, not backwardly compatible with non-ES5 engines

var MyString = function(str) {
  this._value = str;
};

Object.getOwnPropertyNames(String.prototype).forEach(function(key) {
  var func = String.prototype[key];
  MyString.prototype[key] = function() {
    return func.apply(this._value, arguments);
  };
});

这将会对this._value每个String方法产生影响。这将是有趣的,因为你的字符串将是可变的,不像真正的javascript字符串。

你可以这样做:

    return this._value = func.apply(this._value, arguments);

这会增加一个有趣的动态。如果您希望它返回一个字符串而不是本机字符串:

    return new MyString(func.apply(this._value, arguments));

或者简单地说:

    this._value = func.apply(this._value, arguments);
    return this;

根据您想要的行为,有几种方法可以解决这个问题。

另外,你的字符串不会像javascript字符串那样有长度或索引,解决这个问题的方法就是放入构造函数中:

var MyString = function(str) {
  this._value = str;
  this.length = str.length;
  // very rough to instantiate
  for (var i = 0, l = str.length; i < l; i++) {
    this[i] = str[i];
  }
};

非常hacky。根据实现,您可能只能在那里调用构造函数来添加索引和长度。如果你想使用ES5,你也可以使用一个getter。

再一次,你想做什么在任何方面都不理想。这将是缓慢和不必要的。

答案 1 :(得分:5)

此行无效:

this = n;

this不是有效的左值。意思是,您无法分配this引用的值。永远。这只是无效的javascript。如果您这样做,您的示例将起作用:

var myString = function (n){
    this.prop = n;

    this.a = function (){
        alert(this.prop);
    };
}

myString.prototype = new String; // or String.prototype;

var x = new myString("foo");
x.a();

关于你的解决方法,你应该意识到你所做的只是创建一个String对象,扩充一个函数属性,然后调用它。发生 no 继承。

例如,如果您在上面的示例中执行x instanceof myString,则评估为true,但在您的示例中则不是,因为函数myString不是类型,这只是一个常规功能。

答案 2 :(得分:1)

您无法在构造函数

中指定 this

这= n是错误

您的myarray只是原生数组的别名 - 您对myarray.prototype所做的任何更改都是对Array.prototype的更改。

答案 3 :(得分:1)

我将研究创建一个新对象,使用本机对象作为支持字段并手动重新创建本机对象的函数。对于一些非常粗略,未经测试的示例代码...

var myString = {
    _value = ''
    ,substring:_value.substring
    ,indexOf:_value.indexOf
}

现在,我确信这不会按预期工作。但我想通过一些调整,它可能类似于继承自String的对象。

答案 4 :(得分:0)

此链接为您提供了一些允许您正在寻找的继承的功能。看看“糖”部分。

http://www.crockford.com/javascript/inheritance.html

答案 5 :(得分:0)

ECMAScript6标准允许直接从本机对象的构造函数继承。

class ExtendedNumber extends Number
{
    constructor(value)
    {
        super(value);
    }

    add(argument)
    {
        return this + argument;
    }
}

var number = new ExtendedNumber(2);
console.log(number instanceof Number); //真实 console.log(数字+ 1); // 4
的console.log(number.add(3)); // 5
的console.log(编号(1)); // 1

在ECMAScript5中,可以像这样继承:

function ExtendedNumber(value)
{
    if(!(this instanceof arguments.callee)) { return Number(value); }

    var self = new Number(value);

    self.add = function(argument)
    {
        return self + argument;
    };

    return self;
}

但是,生成的对象不是原始的:

console.log(number); // ExtendedNumber {[[PrimitiveValue]]: 2}

但是你可以通过扩展他使用的实际原型来扩展原始类型:

var str = "some text";
var proto = Object.getPrototypeOf(str);
proto.replaceAll = function(substring, newstring)
{
    return this.replace(new RegExp(substring, 'g'), newstring);
}

console.log(str.replaceAll('e', 'E')); // somE tExt

对面向类的表示法的强制转换有点棘手:

function ExtendedString(value)
{
    if(this instanceof arguments.callee) { throw new Error("Calling " + arguments.callee.name + " as a constructor function is not allowed."); }
    if(this.constructor != String) { value = value == undefined || value == null || value.constructor != String ? "" : value; arguments.callee.bind(Object.getPrototypeOf(value))(); return value; }

    this.replaceAll = function(substring, newstring)
    {
        return this.replace(new RegExp(substring, 'g'), newstring);
    }
}

console.log(!!ExtendedString("str").replaceAll); // true