34

假设我从服务器接收到一些 JSON 对象,例如 Person 对象的一些数据:

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

现在,我想要一些基于这些数据的方法,例如用于计算全名:

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

...会导致大量冗余;并且可以说“污染”数据对象。

将此类方法添加到简单数据对象的当前最佳实践方法是什么?

编辑:

有点跑题了,但是如果上面的问题可以解决,就可以做一些不错的伪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如果不是,则返回 falsy(即数据不匹配/一致)。

奖励问题:有谁知道使用或启用此功能(即使其变得容易)的库?它已经是javascript模式了吗?如果是这样,它叫什么;你有详细说明的链接吗?谢谢 :)

4

9 回答 9

24

假设您的 Object 来自某个解析服务器输出以生成 Object 的 JSON 库,那么它的原型中通常不会有任何特殊内容;并且为不同的服务器响应生成的两个对象将不共享原型链(当然,除了 Object.prototype ;) )

如果您控制从 JSON 创建“Person”的所有位置,则可以反过来做:创建一个“空”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());
于 2013-02-24T18:00:47.067 回答
9

这里还有另一种可能。 JSON.parse接受第二个参数,这是一个用于恢复从叶节点到根节点遇到的对象的函数。因此,如果您可以根据它们的内在属性识别您的类型,则可以在 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 属性,这将不起作用。但它可能满足一些需求。

于 2013-02-24T19:44:26.833 回答
5

如果您处理的是纯 JSON 数据,那么每个 person 对象的原型就是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 函数。还有一个 shim 可以Object.assign()在较旧的浏览器上工作;请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill

于 2013-02-24T18:03:43.560 回答
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 );
于 2013-02-24T18:06:18.083 回答
3

换句话说,您想更改现有对象的原型(又名类)。从技术上讲,您可以这样做:

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

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

在那之后,如果你true继续personData instanceof Person

于 2013-02-24T18:16:55.650 回答
1

使用新的Object.setPrototypeOf()。(现在 IE11 和所有其他浏览器都支持它。)

您可以创建一个包含所需方法的类/原型,例如您的 fullName(),然后

Object.setPrototypeOf( personData, Person.prototype );

正如警告(在上面链接的 MDN 页面上)所暗示的那样,不能轻易使用此功能,但当您更改现有对象的原型时,这很有意义,这就是您所追求的。

于 2016-07-02T14:59:14.847 回答
0

我认为使用数据传输方法并不常见,但这似乎是个好主意。

该项目允许您将函数与数据一起编码,但它不被视为标准,当然需要使用相同的库进行解码。

https://github.com/josipk/json-plus
于 2013-02-24T18:01:26.430 回答
0

匿名对象没有原型。为什么不只是有这个:

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();
于 2013-02-24T18:04:00.783 回答
0

您不需要使用原型来在准系统对象中绑定自定义方法。

在这里,您有一个优雅的示例,它不会污染您的代码,避免冗余代码

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/中提供的示例

于 2016-01-04T14:11:01.450 回答