Подтвердить что ты не робот

Как добавить методы к прототипу объекта (JSON)?

Скажем, я получаю некоторый объект JSON с моего сервера, например. некоторые данные для объекта Person:

{firstName: "Bjarne", lastName: "Fisk"}

Теперь я хочу использовать некоторые методы поверх этих данных, например. для вычисления fullName:

fullName: function() { return this.firstName + " " + this.lastName; }

Чтобы я мог

var personData = {firstName: "Bjarne", lastName: "Fisk"};
var person = PROFIT(personData);
person.fullName(); // => "Bjarne Fisk"

Что я в принципе хотел бы сделать здесь, это добавить метод к прототипу объекта. Метод fullName() является общим, поэтому его нельзя добавлять к самому объекту данных. Как..

personData.fullName = function() { return this.firstName + " " + this.lastName; }

... вызовет много избыточности; и, возможно, "загрязняют" объект данных.

Каков нынешний наилучший способ добавления таких методов к простому объекту данных?

EDIT:

Слегка от темы, но если проблема, описанная выше, может быть решена, можно было бы сделать несколько приятных псевдо- pattern matching следующим образом:

if ( p = Person(data) ) {
   console.log(p.fullName());
} else if ( d = Dog(data) ) {
   console.log("I'm a dog lol. Hear me bark: "+d.bark());
} else {
   throw new Exception("Shitty object");
}

Person и Dog будут добавлять методы, если объект data имеет правильные атрибуты. Если нет, верните фальшивость (т.е. Данные не соответствуют/соответствуют).

ВОПРОС БОНУСА: Кто-нибудь знает библиотеку, которая либо использует, либо разрешает это (т.е. упрощает)? Это уже шаблон javascript? Если да, то что называется; и у вас есть ссылка, которая разрабатывается? Спасибо:)

4b9b3361

Ответ 1

Предполагая, что ваш объект поступает из некоторой библиотеки JSON, которая анализирует вывод сервера для создания объекта, он вообще не будет иметь ничего особенного в своем прототипе; и два объекта, сгенерированные для разных ответов сервера, не будут передавать цепочку прототипов (помимо Object.prototype, конечно;))

Если вы контролируете все места, в которых создается "Человек" из JSON, вы можете делать что-то наоборот: создать "пустой" объект Person (с помощью метода fullName в его прототипе) и расширить его с помощью объект, созданный из JSON (с использованием $.extend, _.extend или что-то подобное).

var p = { first : "John", last : "Doe"};

function Person(data) {
   _.extend(this, data);
}

Person.prototype.fullName = function() {
   return this.first + " " + this.last;   
}

console.debug(new Person(p).fullName());

Ответ 2

Здесь есть еще одна возможность. JSON.parse принимает второй параметр, который является функцией, используемой для оживления обнаруженных объектов, от листовых узлов до корня node. Поэтому, если вы можете распознавать свои типы на основе их внутренних свойств, вы можете построить их в функции reviver. Вот очень простой пример:

var MultiReviver = function(types) {
    // todo: error checking: types must be an array, and each element
    //       must have appropriate `test` and `deserialize` functions
    return function(key, value) {
        var type;
        for (var i = 0; i < types.length; i++) {
            type = types[i];
            if (type.test(value)) {
                return type.deserialize(value);
            }
        }
        return value;
    };
};

var Person = function(first, last) {
    this.firstName = first;
    this.lastName = last;
};
Person.prototype.fullName = function() {
    return this.firstName + " " + this.lastName;
};
Person.prototype.toString = function() {return "Person: " + this.fullName();};
Person.test = function(value) {
    return typeof value.firstName == "string" && 
           typeof value.lastName == "string";
};
Person.deserialize = function(obj) {
    return new Person(obj.firstName, obj.lastName);
};

var Dog = function(breed, name) {
    this.breed = breed;
    this.name = name;
}
Dog.prototype.species = "canine";
Dog.prototype.toString = function() {
    return this.breed + " named " + this.name;
};
Dog.test = function(value) {return value.species === "canine";};
Dog.deserialize = function(obj) {return new Dog(obj.breed, obj.name);};


var reviver = new MultiReviver([Person, Dog]);

var text = '[{"firstName": "John", "lastName": "Doe"},' +
            '{"firstName": "Jane", "lastName": "Doe"},' +
            '{"firstName": "Junior", "lastName": "Doe"},' +
            '{"species": "canine", "breed": "Poodle", "name": "Puzzle"},' +
            '{"species": "canine", "breed": "Wolfhound", "name": "BJ"}]';

var family = JSON.parse(text, reviver)
family.join("\n");

// Person: John Doe
// Person: Jane Doe
// Person: Junior Doe
// Poodle named Puzzle
// Wolfhound named BJ

Это зависит от того, сможете ли вы однозначно распознавать свои типы. Например, если бы существовал какой-то другой тип, даже подтип Person, который также имел свойства firstName и lastName, это не сработало. Но это может удовлетворить некоторые потребности.

Ответ 3

Если вы имеете дело с простыми данными JSON, прототип каждого объекта человека будет просто Object.prototype. Чтобы превратить его в объект с прототипом Person.prototype, вам в первую очередь понадобится конструктор и прототип Person (если вы используете Javascript OOP традиционным способом):

function Person() {
    this.firstName = null;
    this.lastName = null;
}
Person.prototype.fullName = function() { return this.firstName + " " + this.lastName; }

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

//example JSON object
var jsonPerson = {firstName: "Bjarne", lastName: "Fisk"};

var person = new Person();
mixin(person, jsonPerson);

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


Обновление: теперь, когда Object.assign() доступен в современных браузерах, вы можете использовать это вместо написания своей собственной функции mixin. Там также прокладка, чтобы сделать Object.assign() работать с более старыми браузерами; см. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill.

Ответ 4

Вероятно, вы не должны этого делать.

JSON позволяет сериализовать состояние, а не тип. Поэтому в вашем случае использования вы должны сделать что-то вроде этого:

var Person = function ( data ) {
    if ( data ) {
        this.firstName = data.firstName;
        this.lastName = data.lastName;
    }
};

Person.prototype.fullName = function ( ) {
    return this.firstName + ' ' + this.lastName;
};

//

var input = '{"firstName":"john", "lastName":"Doe"}';
var myData = JSON.parse( input );
var person = new Person( myData );

Ответ 5

Другими словами, вы хотите изменить прототип (класс a.k.a.) существующего объекта. Технически вы можете сделать это следующим образом:

var Person = {
  function fullName() { return this.firstName + " " + this.lastName; }
};

// that is your PROFIT function body: 
personData.__proto__ = Person ;

После этого, если вы получите true на personData instanceof Person

Ответ 6

Используйте new-ish Object.setPrototypeOf(). (Он поддерживается IE11 и всеми другими браузерами сейчас.)

Вы можете создать класс/прототип, который включает нужные вам методы, такие как fullName(), а затем

Object.setPrototypeOf( personData, Person.prototype );

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

Ответ 7

Я не думаю, что распространять методы транспорта с данными, но это кажется отличной идеей.

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

https://github.com/josipk/json-plus

Ответ 8

Анонимные объекты не имеют прототипа. Почему бы просто не сделать это:

function fullName(obj) {
    return obj.firstName + ' ' + obj.lastName;
}

fullName(person);

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

var Person = function (person) { this.person = person; }
Person.prototype.fullName = function () {
    return this.person.firstName + ' ' + this.person.lastName;
}
var person = new Person(personData);
person.fullName();

Ответ 9

Вам не нужно использовать прототипы для привязки настраиваемого метода к вашему barebone-объекту.

Здесь у вас есть элегантный пример, который не загрязняет ваш код, избегая избыточного кода

var myobj = {
  title: 'example',
  assets: 
  {
    resources: ['zero', 'one', 'two']
  }
}

var myfunc = function(index)
{
    console.log(this.resources[index]); 
}

myobj.assets.giveme = myfunc

myobj.assets.giveme(1);

Пример доступен в https://jsfiddle.net/bmde6L0r/