Как установить прототип объекта JavaScript, который уже был создан? - PullRequest
101 голосов
/ 10 августа 2011

Предположим, у меня есть объект foo в моем коде JavaScript.foo - сложный объект, и он генерируется где-то еще.Как я могу изменить прототип объекта foo?

Моя мотивация заключается в установке соответствующих прототипов для объектов, сериализованных из литералов .NET в JavaScript.

Предположим, что я написал следующий JavaScriptкод на странице ASP.NET.

var foo = <%=MyData %>;

Предположим, что MyData является результатом вызова .NET JavaScriptSerializer для объекта Dictionary<string,string>.

Во время выполненияэто становится следующим:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

Как видите, foo становится массивом объектов.Я хотел бы иметь возможность инициализировать foo с соответствующим прототипом.Я не хочу изменить Object.prototype или Array.prototype.Как я могу это сделать?

Ответы [ 11 ]

111 голосов
/ 23 августа 2011

РЕДАКТИРОВАТЬ Февраль 2012: ответ ниже не является точным.__proto__ добавляется в ECMAScript 6 как «необязательный нормативный», что означает, что его не нужно реализовывать, но если это так, он должен следовать заданному набору правил.В настоящее время это не решено, но, по крайней мере, оно будет официально частью спецификации JavaScript.

Этот вопрос намного сложнее, чем кажется на первый взгляд, и выходит за рамки уровня оплаты труда большинства людей в отношении знанийвнутренних компонентов Javascript.

Свойство prototype объекта используется при создании новых дочерних объектов этого объекта.Его изменение не отражается в самом объекте, а отражается, когда этот объект используется в качестве конструктора для других объектов, и не имеет смысла изменять прототип существующего объекта.

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

Объекты имеютвнутреннее свойство [[prototype]], которое указывает на текущий прототип.Он работает так, что всякий раз, когда вызывается свойство объекта, оно начинается с объекта, а затем идет вверх по цепочке [[prototype]], пока не найдет совпадение или потерпит неудачу после прототипа корневого объекта.Вот как Javascript позволяет создавать и модифицировать объекты во время выполнения;у него есть план поиска того, что ему нужно.

Свойство __proto__ существует в некоторых реализациях (сейчас много): любая реализация Mozilla, все известные мне webkit, некоторые другие.Это свойство указывает на внутреннее свойство [[prototype]] и позволяет модифицировать пост-создание объектов.Любые свойства и функции будут мгновенно переключаться в соответствии с прототипом благодаря этому цепочечному поиску.

Эта функция, будучи стандартизированной в настоящее время, по-прежнему не является обязательной частью JavaScript, и в языках, поддерживающих ее, высокая вероятностьсбивая ваш код в категорию «неоптимизированные».Механизмы JS должны делать все возможное для классификации кода, особенно «горячего» кода, к которому часто обращаются, и если вы делаете что-то необычное, например, изменение __proto__, они вообще не будут оптимизировать ваш код.

В этих публикациях https://bugzilla.mozilla.org/show_bug.cgi?id=607863 конкретно обсуждаются текущие реализации __proto__ и различия между ними.Каждая реализация делает это по-своему, потому что это сложная и нерешенная проблема.Все в Javascript является изменчивым, кроме a.) Синтаксиса b.) Хост-объектов (технически DOM существует вне Javascript) и c.) __proto__.Все остальное полностью в руках вас и любого другого разработчика, так что вы можете понять, почему __proto__ торчит как больной большой палец.

Есть одна вещь, которую __proto__ допускает, что в противном случае сделать невозможно: обозначение прототипа объекта во время выполнения отдельно от его конструктора.Это важный вариант использования и одна из основных причин, по которой __proto__ еще не умер.Достаточно важно, чтобы это было серьезным предметом обсуждения в формулировке Harmony или скоро станет известным как ECMAScript 6. Возможность указать прототип объекта во время создания будет частью следующей версии Javascript, и это будетколокол, обозначающий __proto__ дни, формально пронумерован.

В краткосрочной перспективе вы можете использовать __proto__, если вы нацеливаетесь на браузеры, которые его поддерживают (не IE, и ни один IE не будет).Вполне вероятно, что он будет работать в webkit и moz в течение следующих 10 лет, так как ES6 не будет завершен до 2013 года.

Brendan Eich - re: Подход новых методов объектав ES5 :

Извините, ... но можно установить __proto__, за исключением случая использования инициализатора объекта (т. е. для нового объекта, который еще не доступен, аналог ES. Object.create), это ужасная идея.Я пишу это, спроектировав и установив settable __proto__ более 12 лет назад.

... проблема отсутствия стратификации (рассмотрим данные JSON с ключом "__proto__").И что еще хуже, изменчивость означает, что реализации должны проверять циклические цепочки прототипов, чтобы избежать ошибок. [требуются постоянные проверки бесконечной рекурсии]

Наконец, изменение __proto__ в существующем объекте может нарушить неуниверсальные методы в новом объекте-прототипе, которые не могут работать с объектом-получателем (прямым), чей __proto__ устанавливается.Это просто плохая практика, форма намеренного смешения типов, в общем.

31 голосов
/ 06 мая 2015

ES6 окончательно указывает Object.setPrototypeOf (объект, прототип) , который уже реализован в Chrome и Firefox.

14 голосов
/ 23 августа 2011

Вы можете использовать constructor на экземпляре объекта, чтобы изменить прототип объекта на месте.Я считаю, что это то, что вы просите сделать.

Это означает, что если у вас есть foo, который является экземпляром Foo:

function Foo() {}

var foo = new Foo();

Вы можете добавить свойство bar для всех случаев Foo, выполнив следующие действия:

foo.constructor.prototype.bar = "bar";

Вот скрипка, показывающая подтверждение концепции: http://jsfiddle.net/C2cpw/. Не очень уверены, как старые браузеры будут использовать этуподход, но я почти уверен, что это должно хорошо сработать.

Если вы хотите смешать функциональность с объектами, этот фрагмент должен выполнить эту работу:

function mix() {
  var mixins = arguments,
      i = 0, len = mixins.length;

  return {
    into: function (target) {
      var mixin, key;

      if (target == null) {
        throw new TypeError("Cannot mix into null or undefined values.");
      }

      for (; i < len; i += 1) {
        mixin = mixins[i];
        for (key in mixin) {
          target[key] = mixin[key];
        }

        // Take care of IE clobbering `toString` and `valueOf`
        if (mixin && mixin.toString !== Object.prototype.toString) {
          target.toString = mixin.toString;
        } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) {
          target.valueOf = mixin.valueOf;
        }
      }
      return target;
    }
  };
};
9 голосов
/ 10 августа 2011

Вы можете сделать foo.__proto__ = FooClass.prototype, AFAIK, который поддерживается Firefox, Chrome и Safari. Имейте в виду, что свойство __proto__ нестандартно и может в какой-то момент исчезнуть.

Документация: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto. Также см. http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html для объяснения, почему нет Object.setPrototypeOf() и почему __proto__ устарела.

3 голосов
/ 24 августа 2011

Вы не можете изменить прототип объекта JavaScript, который уже был создан кросс-браузерным способом.Как уже упоминали другие, ваши варианты включают в себя:

  1. изменение нестандартного / кросс-браузер __proto__ свойство
  2. Скопируйте свойства объектов в новый объект

Ни один из них не особенно хорош, особенно если вам нужно рекурсивно пройти через объект во внутренние объекты, чтобы эффективно изменить прототип всего элемента.

Альтернативное решение вопроса

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

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

function trim(x){ /* implementation */ }
trim('   test   ');

вы пишете

'   test  '.trim();

Приведенный выше синтаксис был придуман термином ООП из-за синтаксиса object.method ().Некоторые из основных преимуществ ООП перед традиционным функциональным программированием включают:

  1. Короткие имена методов и меньшее количество переменных obj.replace('needle','replaced') по сравнению с необходимостью запоминать такие имена, как str_replace ( 'foo' , 'bar' , 'subject') и расположение различных переменных
  2. цепочка методов (string.trim().split().join()) потенциально легче модифицировать и писать, чем вложенные функции join(split(trim(string))

К сожалению, в JavaScript (как показано выше) вы не можете изменить уже существующий прототип.В идеале выше вы можете изменить Object.prototype только для указанного выше объекта, но, к сожалению, изменение Object.prototype может привести к поломке сценариев (что приведет к конфликту свойств и переопределению).

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

UnlimitJS обеспечивает золотую середину, которая позволяет вам определять пользовательские методы.Он избегает:

  1. конфликта свойств, поскольку он не расширяет прототипы объектов
  2. Тем не менее допускает синтаксис цепочки ООП
  3. Это кросс-браузер 450 байт (IE6 +, Firefox 3.0+, Chrome, Opera, Safari 3.0+) сценарий, который в Unlimit вызывает большую часть проблем с конфликтами свойств прототипов JavaScript

Используя ваш код выше, я просто создал бы пространство имен функций, которые вы намереваетесь вызыватьпротив объекта.

Вот пример:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

// define namespace with methods
var $ = {
  log:function(){
    console.log(this);
    return this;
  }[Unlimit](),
  alert:function(){
    alert(''+this);
  }[Unlimit]()
}


foo[$.log]()
   [$.log]()
   [$.alert]();

Вы можете прочитать больше примеров здесь UnlimitJS .По сути, когда вы вызываете [Unlimit]() для функции, она позволяет вызывать функцию как метод для объекта.Это как середина между ООП и функциональными дорогами.

3 голосов
/ 10 августа 2011

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

// your original object
var obj = { 'foo': true };

// your constructor - "the new prototype"
function Custom(obj) {
    for ( prop in obj ) {
        if ( obj.hasOwnProperty(prop) ) {
            this[prop] = obj[prop];
        }
    }
}

// the properties of the new prototype
Custom.prototype.bar = true;

// pass your original object into the constructor
var obj2 = new Custom(obj);

// the constructor instance contains all properties from the original 
// object and also all properties inherited by the new prototype
obj2.foo; // true
obj2.bar; // true

Демонстрационная версия Live: http://jsfiddle.net/6Xq3P/

Конструктор Custom представляет новый прототип, следовательно, его объект Custom.prototype содержит все новые свойства, которые вы хотели бы использовать со своим исходным объектом.

Внутри Customконструктор, вы просто копируете все свойства из исходного объекта в новый объект экземпляра.

Этот новый экземплярный объект содержит все свойства исходного объекта (они были скопированы в него внутри конструктора), а также все новые свойства, определенные внутри Custom.prototype (поскольку новый объект является Customэкземпляр).

2 голосов
/ 10 августа 2011

Насколько я знаю, вы не можете изменить ссылку [[prototype]] уже построенных объектов. Вы можете изменить свойство prototype исходной функции конструктора, но, как вы уже прокомментировали, этот конструктор равен Object, а изменение базовых конструкций JS - плохая вещь.

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

Возможно, вы можете получить то, что вы хотите, другим способом, если вы хотите подойти с другой стороны: что вам нужно сделать, чтобы связать себя с прототипом?

1 голос
/ 16 апреля 2013

Если вы знаете прототип, почему бы не внедрить его в код?

var foo = new MyPrototype(<%= MyData %>);

Итак, после сериализации данных вы получите

var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);

, теперь вам нужно толькоконструктор, который принимает массив в качестве аргумента.

0 голосов
/ 16 октября 2015

, если вы хотите создать прототип на лету, это один из способов

function OntheFlyProto (info){
    this.items = info;
    this.y =-1;
    for(var i = 0; i < this.items.length ; i++){
        OntheFlyProto.prototype["get"+this.items[i].name] = function (){
            this.y++;
            return this.items[this.y].value;
        }
    }
}

var foo = [{name:"one", value:1},{name:"two", value:2}];
v = new OntheFlyProto(foo);
0 голосов
/ 24 августа 2011

Нет способа действительно наследовать от Array или «подкласса» его.

Что вы можете сделать, это ( ПРЕДУПРЕЖДЕНИЕ: FESTERING CODE AHEAD ):

function Foo(arr){
  [].push.apply(this, arr)
}
Foo.prototype = []
Foo.prototype.something = 123

var foo = new Foo(<%=MyData %>)

foo.length // => 2
foo[0] // => {"A":"1","B":"2"}
foo.something // => 123

Это работает, но создаст определенные проблемы для любого, кто пересекает его путь (он выглядит как массив, но если вы попытаетесь манипулировать им, все пойдет не так).

Почему бы вам не пойти вразумительным путем и не добавить методы / свойства непосредственно в foo, или использовать конструктор и сохранить свой массив как свойство?

function Foo(arr){
  this.items = arr
}
Foo.prototype = {
  someMethod : function(){ ... }
  //...
}

var foo = new Foo(<%=MyData %>)
foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}]
...