Как лучше всего наследовать от нативного объекта JavaScript? (Особенно String) - PullRequest
18 голосов
/ 24 июля 2011

Я давний браузер, но впервые участвую. Если мне не хватает подробностей этикета, просто дайте мне знать!

Кроме того, я искал все выше и ниже, включая этот сайт, но я не нашел четкого и краткого объяснения того, что именно я собираюсь сделать. Если я только что пропустил это, пожалуйста, укажите мне правильное направление!

Хорошо, я хочу расширить некоторые нативные объекты JavaScript, такие как Array и String. Однако я не хочу на самом деле расширять их, а создавать новые объекты, которые наследуются от них, а затем изменять их.

Для массива это работает:

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) Можете ли вы назвать какие-либо явные преимущества или недостатки между «реальным» примером наследования и «сокращенным» примером? Или, может быть, только некоторые различия в том, как один будет функционировать над другим функционально? (Потому что они в конечном итоге выглядят одинаково для меня ...)

Спасибо всем!

EDIT:

Спасибо всем, кто прокомментировал / ответил. Я думаю, что информация @ CMS является лучшей, потому что:

1) Он ответил на мою проблему наследования строк, указав, что, частично переопределив строку в моем собственном строковом объекте, я смог бы заставить его работать. (например, переопределение toString и toValue) 2) Создание нового объекта, который наследуется от Array, имеет собственные ограничения, которые не были сразу видны и не могли быть обойдены, даже путем частичного переопределения Array.

Из вышеприведенных двух вещей я заключаю, что утверждение JavaScript о наследуемости распространяется только на объекты, которые вы создаете сами, и что, когда дело доходит до нативных объектов, вся модель разрушается. (Вероятно, поэтому 90% примеров, которые вы найдете, это Pet-> Dog или Human-> Student, а не String-> SuperString). Это можно объяснить ответом @ chjj о том, что эти объекты действительно должны быть примитивными значениями, даже если все в JS кажется объектом и поэтому должно быть на 100% наследуемым.

Если этот вывод полностью неверен, пожалуйста, поправьте меня. И если это верно, то я уверен, что это не новость для кого-либо, кроме меня самого, но еще раз спасибо всем за комментарии. Полагаю, теперь у меня есть выбор:

Либо продолжите работу с паразитическим наследованием (мой второй пример, для которого теперь я знаю имя) и попытайтесь уменьшить его влияние на использование памяти, если это возможно, либо сделайте что-нибудь вроде @davin, @Jeff или @chjj и либо псевдо- переопределить или полностью переопределить эти объекты для себя (что кажется пустой тратой).

@ CMS - соберите свою информацию в ответ, и я ее выберу.

Ответы [ 6 ]

12 голосов
/ 24 июля 2011

Ужасно простой, но некорректный способ сделать это:

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];
  }
};

Очень хакерский. В зависимости от реализации, вы можете просто вызывать конструктор для добавления индексов и длины. Вы также можете использовать геттер для длины, если вы хотите использовать ES5.

Еще раз, однако, то, что вы хотите сделать здесь, не является идеальным. Это будет медленно и не нужно.

5 голосов
/ 24 июля 2011

Эта строка недопустима:

this = n;

this не является допустимым значением lvalue.Это означает, что вы не можете присвоить значение, на которое ссылается 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, расширяете свойство функции и затем вызываете его. нет наследования.

Например, если вы выполняете x instanceof myString в моем примере выше, оно оценивается как true, но в вашем примере это не так, потому чтоФункция myString не является типом, это просто обычная функция.

1 голос
/ 24 июля 2011

Я хотел бы изучить создание нового объекта, используя собственный объект в качестве вспомогательного поля и вручную воссоздать функции для собственных объектов. Для некоторого очень грубого, непроверенного примера кода ...

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

Теперь я уверен, что это не сработает так, как задумано. Но я думаю, что с некоторыми изменениями он может напоминать объект, унаследованный от String.

1 голос
/ 24 июля 2011

Вы не можете присвоить this в конструкторе

this = n - ошибка

Ваш myarray - просто псевдоним для собственного массива - любые измененияВы вносите в myarray.prototype изменения в Array.prototype.

0 голосов
/ 17 октября 2017

Стандарт ECMAScript6 позволяет напрямую наследовать функции конструктора нативного объекта.

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

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

var number = новый ExtendedNumber (2);
console.log (номер экземпляра Number); // true
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
0 голосов
/ 24 июля 2011

Эта ссылка дает вам некоторые функции, которые разрешают наследование, которое вы ищете.Посмотрите в разделе "сахар".

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...