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

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

Ответ 1

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Ответ 3

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

Ответ 4

Вы можете сделать 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/[email protected]/msg00392.html для объяснения, почему нет Object.setPrototypeOf() и почему __proto__ устарел.

Ответ 5

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

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

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

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

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

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

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

вы пишете

'   test  '.trim();

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

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

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

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

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

  • Свойство collision, поскольку оно не расширяет прототипы объектов
  • Показывает синтаксис привязки OOP
  • Это 450-байтовый кросс-браузер (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) script, который не разрешает многие проблемы с конфликтом свойств прототипа 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]() для функции, она позволяет вызывать функцию как метод для объекта. Это как промежуточная точка между ООП и функциональными дорогами.

Ответ 6

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

// 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 представляет новый прототип, ergo, его объект Custom.prototype содержит все новые свойства, которые вы хотели бы использовать с вашим исходным объектом.

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

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

Ответ 7

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

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

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

Ответ 8

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

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

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

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

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

Ответ 9

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

Ответ 10

Невозможно действительно наследовать от 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"}]

Ответ 11

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

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