Как установить прототип объекта JavaScript, который уже был создан?


106

Предположим, у меня есть объект 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.prototypenor Array.prototype. Как я могу это сделать?


Вы хотите добавить к существующему прототипу или переключить его на новый прототип?
SLaks

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

2
Вы имеете в виду явное свойство прототипа или неявную ссылку на прототип? (Эти двое - две очень разные вещи)
Шиме Видас

Первоначально я был обеспокоен этим: stackoverflow.com/questions/7013545/…
Вивиан Ривер

1
Вы знакомы с Backbone extendили Google goog.inherit? Многие разработчики предоставляют способы построения наследования перед вызовом старого newконструктора - это было до того, как нам дали, Object.createи не нужно было беспокоиться о переопределении Object.prototype.
Райан

Ответы:


114

РЕДАКТИРОВАТЬ Февраль 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 года.

Брендан Эйх - re: Подход новых методов Object в ES5 :

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

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

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


Отличная поломка! Хотя это не совсем то же самое, что изменяемый прототип, ECMA Harmony, вероятно, будет реализовывать прокси , которые позволят вам накладывать дополнительные функции на определенные объекты, используя общий шаблон.
Ник Хашер

2
Проблема Брендана Эйха присуща языкам прототипирования в целом. Возможно, создание __proto__ non-writable, configurableустранит эти проблемы, заставив пользователя явно перенастроить свойство. В конце концов, плохие практики связаны со злоупотреблением возможностями языка, а не самими возможностями. Записываемый __proto__ не является чем-то необычным. Есть много других неперечислимых записываемых свойств, и, хотя есть опасности, есть и передовые методы. Головку молотка нельзя снимать просто потому, что ею можно ненадлежащим образом ранить кого-нибудь.
Swivel

Мутация __proto__необходима, потому что Object.create будет создавать только объекты, а не функции, например, ни символы, регулярные выражения, элементы DOM или другие объекты хоста. Если вы хотите, чтобы ваши объекты были вызываемыми или особенными другими способами, но все же изменяли их цепочку прототипов, вы застряли без устанавливаемых __proto__или прокси-
серверов

2
Было бы лучше, если бы это было сделано не с помощью волшебного свойства, а вместо этого с помощью Object.setPrototype, что устраняет проблему « __proto__в JSON». Другие опасения Брендана Эйха смехотворны. Рекурсивные цепочки прототипов или использование прототипа с неадекватными методами для данного объекта являются ошибками программиста, а не языковыми ошибками и не должны быть фактором, и, кроме того, они могут происходить с Object.create так же хорошо, как и со свободно устанавливаемым __proto__.
user2451227

1
IE 10 поддерживает __proto__.
kzh


14

Вы можете использовать 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;
    }
  };
};

1
+1: Это информативно и интересно, но на самом деле не помогает мне получить то, что я хочу. Я обновляю свой вопрос, чтобы он был более конкретным.
Vivian River

Вы также можете использоватьfoo.__proto__.bar = 'bar';
Jam

9

Вы можете это сделать 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

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

// 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

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

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

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

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


3

Вы не можете изменить прототип объекта 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. метод chaining ( 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+), который устраняет большую часть проблем с конфликтом свойств прототипа JavaScript Unlimit.

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

Вот пример:

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]()функцию, она позволяет вызывать функцию как метод объекта. Это как золотая середина между ООП и функциональными дорогами.


2

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

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

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


Может ли кто-нибудь еще прокомментировать точность этого?
Vivian River

Похоже, что на основе этого ужасного jsfiddle вы можете изменить прототип существующего объекта, изменив его __proto__свойство. Это будет работать только в браузерах, поддерживающих __proto__нотацию, а именно Chrome и Firefox, и они устарели . Короче говоря, вы можете изменить [[prototype]]объект, но, вероятно, не должны этого делать.
Ник Хашер,

1

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

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

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

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

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


0

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

Что вы можете сделать, так это ( ВНИМАНИЕ: КОД ЗАГРУЗКИ ВПЕРЕДИ ):

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

0

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

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);

-1
foo.prototype.myFunction = function(){alert("me");}

Нет, не можешь. Это статическое свойство.
SLaks

@SLaks prototypeстатичен? Я не уверен, что ты имеешь в виду.
Шиме Видас

1
prototypeэто свойство функции, а не объекта. Object.prototypeсуществуют; {}.prototypeнет.
SLaks

Если вас не волнует совместимость браузера, вы можете использовать Object.getPrototypeOf(foo)для получения объекта-прототипа конструктора. Вы можете изменить его свойства, чтобы изменить прототип объекта. Он работает только в последних версиях браузеров, но не могу сказать, в каких именно.
Ник Хашер

@TeslaNick: Вы можете просто пойти и изменить Object.prototypeнапрямую, потому что это, скорее всего Object.getPrototypeOf(foo), вернется. Не очень полезно.
Владимир Палант
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.