Як встановити прототип об'єкта 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.prototypeнорму Array.prototype. Як я можу це зробити?


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

Ви маєте на увазі внести зміни в прототип - або насправді змінити прототип, як вимкнути один прототип і замінити його на інший. Я навіть не впевнений, що можливий наступний випадок.
Джеймс Гонт

2
Ви маєте на увазі явну властивість прототипу або неявну прототипну посилання? (Ці два - це дві дуже різні речі)
Šime Vidas

Я спочатку переймався цим: stackoverflow.com/questions/7013545/…
Вівіан-Рівер

1
Чи знайомі ви з кістяками 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

Об'єкти мають внутрішнє [[прототип]] властивість, яке вказує на поточний прототип. Як він працює, кожен раз, коли називається властивість на об'єкті, він починається від об'єкта, а потім проходить по ланцюгу [[прототип]], поки не знайде збіг або не вдасться після прототипу кореневого об'єкта. Ось як Javascript дозволяє будувати час виконання та змінювати об'єкти; у нього є план пошуку того, що йому потрібно.

__proto__Властивість існує в деяких реалізаціях (зараз багато): будь-яка реалізацію Mozilla, все WebKit один я знаю, деякі інші. Це властивість вказує на внутрішнє [[прототип]] властивості та дозволяє змінювати пост-створення на об'єктах. Будь-які властивості та функції миттєво переключаються на відповідність прототипу завдяки цьому ланцюговому пошуку.

Ця функція, хоча зараз є стандартизованою, все ще не є обов'язковою частиною 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 ніколи не буде). Цілком ймовірно, що він буде працювати в веб-казі та мозі протягом наступних 10 років, оскільки ES6 не буде доопрацьовано до 2013 року.

Brendan Eich - re: Підхід до нових методів об'єктів у ES5 :

Вибачте, ... але налаштований __proto__, окрім випадку використання об'єкта ініціалізатора (тобто, на новому об'єкті, який ще не доступний, аналогічно ES5 в Object.create), є жахливою ідеєю. Я пишу це, розробивши та впровадивши програму __proto__12 років тому.

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

Нарешті, мутація __proto__існуючого об'єкта може порушити негенеричні методи в новому об'єкті-прототипі, який не може працювати на приймальному (прямому) об'єкті, __proto__який встановлюється. Це просто погана практика, форма навмисного типу плутанини взагалі.


Відмінна поломка! Незважаючи на те, що це не те саме, що і прототип, що змінюється, ECMA Harmony, ймовірно, реалізує проксі-сервери , які дозволять вам надавати додаткову функціональність певним об'єктам за допомогою шаблону catchall.
Нік Хашер

2
Випуск Брендана Ейха є рідним для мов прототипу в цілому. Можливо, виготовлення __proto__ non-writable, configurableусуне ці проблеми, змусивши користувача явно переконфігурувати властивість. Зрештою, погані практики пов'язані зі зловживанням можливостями мови, а не самими можливостями. Запис __proto__ не є нечуваним. Існує багато інших незліченних властивостей для запису, і, хоча існують небезпеки, є і найкращі практики. Голову молотка не слід знімати просто тому, що її можна неправильно використовувати для поранення когось.
Поворот

Мутація __proto__потрібна, тому що Object.create створюватиме лише Об'єкти, а не функції, наприклад, Символи, Регекси, елементи DOM або інші хост-об'єкти. Якщо ви хочете, щоб ваші об’єкти можна було називати або спеціальними іншими способами, але все ж змінювати прототип ланцюга, ви застрягли без налаштування __proto__або проксі
user2451227

2
Було б краще, якби це було зроблено не з магічним властивістю, але, а замість Object.setPrototype, усуваючи __proto__занепокоєння " в JSON". Інші занепокоєння Брендана Ейха є смішними. Рекурсивні ланцюги прототипів або використання прототипу з неадекватними методами для даного об'єкта є помилками програміста, а не мовними помилками і не повинні бути фактором, і, крім того, вони можуть статися з Object.create так само, як і з вільно налаштованими __proto__.
user2451227

1
IE 10 підтримує __proto__.
кж


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: Це інформативно та цікаво, але насправді не допомагає мені отримати те, що я хочу. Я актуалізую своє запитання, щоб бути більш конкретним.
Вівіан Рівер

Ви також можете скористатисяfoo.__proto__.bar = 'bar';
Jam Risser

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

Вищезгаданий синтаксис був введений у термін OOP через синтаксис object.method (). Деякі основні переваги OOP перед традиційним функціональним програмуванням включають:

  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потенційно може порушити сценарії (що призведе до зіткнення властивостей та переопределення).

Між цими двома стилями програмування немає загальноприйнятого середнього місця, і немає способу організувати спеціальні функції OOP

UnlimitJS надає середину, яка дозволяє визначати власні методи. Це дозволяє уникнути:

  1. Зіткнення власності, оскільки воно не поширює прототипи Об'єктів
  2. Все ще дозволяє створити синтаксис ланцюга OOP
  3. Сценарій 450-байтового крос-браузера (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) дозволяє обмежити більшість проблем зіткнення властивостей прототипу 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]()функцію, вона дозволяє викликати функцію як метод на об'єкті. Це як середнє місце між OOP та функціональними дорогами.


2

[[prototype]]Наскільки я знаю, ви не можете змінити посилання на вже побудовані об'єкти. Ви можете змінити властивість прототипу оригінальної функції конструктора, але, як ви вже коментували, такий конструктор є Object, а зміна основних конструкцій JS - це погана річ.

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

Можливо, ви можете отримати те, що хочете іншим способом, якщо будете готові підходити з іншого кута: Що вам потрібно зробити, що стосується возитися з прототипом?


Хтось ще може прокоментувати точність цього?
Вівіан-Рівер

На основі цієї жахливої ​​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 prototypeстатичний? Я не впевнений, що ти маєш на увазі.
Šime Vidas

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.