From: Kees on
In my html page I get from a PHP server a big JSON-encoded string. In
javascript I decode this string and I get a object with properties
foundPersons and errorMsg.
In my javascript code I have a Person prototype with handy methods
like fullName()...
Then I traverse the JSONDecodedObject.foundPersons and I retrieve the
foundPersons[i] object. This object (of course) does not know the
fullName() method. foundPersons[i] is a object not a Person.
It would be nice if I could change foundPersons[i].prototype to
Person.prototype. I've tried foundPersons[i].prototype=new Person().
But no luck: foundPersons[i].fullName is still not accessible.

Do I have to create a new Person and copy then all data to the new
created Person?

Any ideas?

Kees
From: Scott Sauyet on
On Jan 25, 2:41 pm, Thomas 'PointedEars' Lahn <PointedE...(a)web.de>
wrote:
> Scott Sauyet wrote:
>> Thomas 'PointedEars' Lahn wrote:

>>> In a case as well known as this it is rather easy to do in a
>>> cross-browser manner in several ways:
>>>
>>> var proto = Person.prototype;
>>>
>>> for (var p in proto)
>>> {
>>>   var o = foundPersons[i];
>>>
>>>   if (typeof proto[p] == "function")
>>>   {
>>>     o[p] = proto[p];
>>>   }
>>> }
>
>> This works as long as none of the functions defined for Person rely on
>> properties created during construction, right?
>>
>> That is, the following might not work (in any of the methods we've
>> been discussing):
>
>>     function Person(first, last, ...)
>>         this.first = first;
>>         this.last = last;
>>         this.created = new Date();
>>         // ...
>>     }
>>
>>     Person.prototype.toString = function() {
>>         return this.firstName + " " +  this.lastName + " (" +
>> this.created + ")";
>>     }
>
> There is no good reason why the approach above it should not work with this
> code.  [ ... ]

I guess it depends upon what you mean by "work". But perhaps I wasn't
being clear. I believe that for the object derived from this:

{"firstName": "Scott", "lastName": "Sauyet"}

the fullName function will return "Scott Sauyet (undefined)", which
may or may not be acceptable.

For completeness, here's a more complete script:

var people = [{first: "Thomas", last: "Lahn"},
{first: "Scott", last: "Sauyet"}];
function Person(first, last) {
this.first = first;
this.last = last;
this.created = new Date();
}
Person.prototype.fullName = function() {
return this.first + " " + this.last;
}
var person = people[0],
len = people.length,
proto = Person.prototype;

for (var i = 0; i < len; i++) {
var person = people[i];
for (var p in proto) {
if (typeof proto[p] == "function") {
person[p] = proto[p];
}
}
}
for (var i = 0; i < len; i++) {
alert(people[i].fullName());
}

This works fine, alerting "Thomas Lahn" and "Scott Sauyet" as
expected. But if you use this fullName function instead:

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

It alerts the (possibly incorrect) "Thomas Lahn (undefined)" and
"Scott Sauyet (undefined)".

And if you used this version instead:

Person.prototype.fullName = function() {
return ((this.created.getTime() >
new Date().getTime() - 1000 * 60 * 60)
? "NEW: "
: ""
) + this.first + " " + this.last;
}

the script will generate an error. The problem, of course, is that
the persistent data loaded from the JSON (or hardcoded in the example
above) might well not contain transient data generated in the
constructor.


>> But I think it doesn't have to be a doubling of storage space
>
> It is not a considerable doubling of storage space, though.  The Function
> objects are not copied by the assignment, only the reference value is.  
> That is, after the assignment both properties refer to the *same* object.

The doubling I was talking about had to do not with your method of
copying functions but with methods using the constructor. I was
trying to allay the OP's concern here:

| Do I have to create a new Person and copy then all data to
| the new created Person?

That concern might not be an issue of memory at all, just convenience,
but I wanted to point out that there are techniques which do not use
as much memory as the naive one would.

> [ ... ]
> Either constructor-based approach would require more memory than my
> suggestion, though, because only after the original object would have been
> garbage-collected memory could be saved here.  

Absolutely.

> They are also a lot less runtime-efficient.

What factors contribute to this?

-- Scott
From: Thomas 'PointedEars' Lahn on
Scott Sauyet wrote:

> function Person(firstName, lastName) {
> if (/* test first format */) {
> this.data = firstName;
> } else {
> this.data = {firstName: firstName, lastName: lastName};
> }
> // ...
> }
>
> This could then take advantage of the reviver parameter to JSON.parse
> wherever it's avaialable.

I have just read about the `reviver' _argument_, thanks for the hint:

<http://msdn.microsoft.com/en-us/library/cc836466%28VS.85%29.aspx>
<https://developer.mozilla.org/En/Using_JSON_in_Firefox#Converting_objects_into_JSON>

However, AIUI, `Person' as you define it above cannot be used for that
argument.

First of all, according to the MSDN Library the reviver function is "called
for each member of the [deserialized] object in post-order"; one would need
to make sure that it only operates on those "members" that hold person
data.

Second, `this' does not always refer to the passed object in the reviver
function; in JavaScript 1.8.1,

JSON.parse('{"foo": {"bar": "baz"}}',
function(k, v) {
console.log("debug:", this.foo, k, v);
return v;
});

displays:

| debug: undefined bar baz
| debug: Object { bar="baz"} foo Object { bar="baz"}
| debug: undefined Object { foo=Object}

Third, the reviver function needs to return something; constructors usually
return nothing, i.e. they implicitly return `undefined', by which the
property would be deleted here. And even though if a function returns a
primitive value it is ignored if the function is called as a constructor
(and so we could return `lastName' which would be the value of the "member"
if the constructor was called as a reviver function), we have still no
reliable way to refer to the object in the reviver function.

Fourth, the reviver function is passed two arguments, the property name and
the property value.


PointedEars
--
Use any version of Microsoft Frontpage to create your site.
(This won't prevent people from viewing your source, but no one
will want to steal it.)
-- from <http://www.vortex-webdesign.com/help/hidesource.htm> (404-comp.)
From: Thomas 'PointedEars' Lahn on
Scott Sauyet wrote:

> Thomas 'PointedEars' Lahn wrote:
>> Scott Sauyet wrote:
>>> Thomas 'PointedEars' Lahn wrote:
>>>> In a case as well known as this it is rather easy to do in a
>>>> cross-browser manner in several ways:
>>>>
>>>> var proto = Person.prototype;
>>>>
>>>> for (var p in proto)
>>>> {
>>>> var o = foundPersons[i];
>>>>
>>>> if (typeof proto[p] == "function")
>>>> {
>>>> o[p] = proto[p];
>>>> }
>>>> }
>>>
>>> This works as long as none of the functions defined for Person rely on
>>> properties created during construction, right?
>> [...]
>>> That is, the following might not work (in any of the methods we've
>>> been discussing):
>>>
>>> function Person(first, last, ...)
>>> this.first = first;
>>> this.last = last;
>>> this.created = new Date();
>>> // ...
>>> }
>>>
>>> Person.prototype.toString = function() {
>>> return this.firstName + " " + this.lastName + " (" +
>>> this.created + ")";
>>> }
>>
>> There is no good reason why the approach above it should not work with
>> this code. [ ... ]
>
> I guess it depends upon what you mean by "work". But perhaps I wasn't
> being clear. I believe that for the object derived from this:
>
> {"firstName": "Scott", "lastName": "Sauyet"}
>
> the fullName function will return "Scott Sauyet (undefined)", which
> may or may not be acceptable.

Yes, but I think it goes without saying that a function should only be
transferred for use as a method if it fits the purpose at hand. That
includes that all the properties of the calling object that the method
reads from when called are there on the calling object, and that all the
properties of the calling object that the method writes to when called are
not read-only or otherwise used on the calling object.

>>> But I think it doesn't have to be a doubling of storage space
>>
>> It is not a considerable doubling of storage space, though. The
>> Function objects are not copied by the assignment, only the reference
>> value is. That is, after the assignment both properties refer to the
>> *same* object.
>
> The doubling I was talking about had to do not with your method of
> copying functions but with methods using the constructor.

I beg your pardon? (And the functions are still _not_ copied.)

> I was trying to allay the OP's concern here:
>
> | Do I have to create a new Person and copy then all data to
> | the new created Person?
>
> That concern might not be an issue of memory at all, just convenience,
> but I wanted to point out that there are techniques which do not use
> as much memory as the naive one would.

I beg your pardon? Your constructor-based approach (IIUC) does nothing
else than what the OP described, and it must fail to save memory as it
creates a new object, which allocates more memory.

>> [Constructor-based approaches] are also a lot less runtime-efficient.
>
> What factors contribute to this?

Mostly object creation and method calls, as compared to operations. We can
also safely assume that if the prototype method is called it takes slightly
longer for it to be looked up in the prototype chain of the instance.

That is not to say these approaches you suggested are nonsense; they are
just not as efficient, and they might easily be overkill here.


PointedEars
--
Prototype.js was written by people who don't know javascript for people
who don't know javascript. People who don't know javascript are not
the best source of advice on designing systems that use javascript.
-- Richard Cornford, cljs, <f806at$ail$1$8300dec7(a)news.demon.co.uk>
From: Scott Sauyet on
On Jan 25, 4:33 pm, Thomas 'PointedEars' Lahn <PointedE...(a)web.de>
wrote:
> Scott Sauyet wrote:
>> This could then take advantage of the reviver parameter to JSON.parse
>> wherever it's avaialable.
>
> I have just read about the `reviver' _argument_, thanks for the hint:
>
> <http://msdn.microsoft.com/en-us/library/cc836466%28VS.85%29.aspx>
> <https://developer.mozilla.org/En/Using_JSON_in_Firefox#Converting_objects_into_JSON>
>
> However, AIUI, `Person' as you define it above cannot be used for that
> argument.
>
> First of all, according to the MSDN Library the reviver function is "called
> for each member of the [deserialized] object in post-order"; one would need
> to make sure that it only operates on those "members" that hold person
> data.

Well, you'd have to return the original value for Strings, Numbers,
etc. But there is a pattern that I've seen a few places ...
searching, searching, ah yes... such as JSON.org [1]:

| myData = JSON.parse(text, function (key, value) {
| var type;
| if (value && typeof value === 'object') {
| type = value.type;
| if (typeof type === 'string' &&
| typeof window[type] === 'function') {
| return new (window[type])(value);
| }
| }
| return value;
| });

You could use this by adding "type":

var people = [{type: "Person", first: "Thomas", last: "Lahn"}];

> Second, `this' does not always refer to the passed object in the reviver
> function; in JavaScript 1.8.1,
>
>   JSON.parse('{"foo": {"bar": "baz"}}',
>     function(k, v) {
>       console.log("debug:", this.foo, k, v);
>       return v;
>     });
>
> displays:
>
> | debug: undefined bar baz
> | debug: Object { bar="baz"} foo Object { bar="baz"}
> | debug: undefined Object { foo=Object}

Okay, I didn't realize this, but I'm not sure it matters. I hadn't
thought of using "this" inside the function.

> Third, the reviver function needs to return something; constructors usually
> return nothing, i.e. they implicitly return `undefined', by which the
> property would be deleted here.  And even though if a function returns a
> primitive value it is ignored if the function is called as a constructor
> (and so we could return `lastName' which would be the value of the "member"
> if the constructor was called as a reviver function), we have still no
> reliable way to refer to the object in the reviver function.

.... but they can be used with "new"...


> Fourth, the reviver function is passed two arguments, the property name and
> the property value.

Right, but I don't see the issue.

I think this would work:

function Person(data) {
this.first = data.first;
this.last = data.last;
this.created = new Date();
}

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

var text = '[{"type":"Person","first":"Thomas", ' +
'"last":"Lahn"},' +
'{"type":"Person","first": "Scott", ' +
'"last": "Sauyet"}]',

people = JSON.parse(text, function (key, value) {
var type;
if (value && typeof value === 'object') {
type = value.type;
if (typeof type === 'string' &&
typeof window[type] === 'function') {
return new (window[type])(value);
}
}
return value;
}),
len = people.length;

for (var i = 0; i < len; i++) {
alert(people[i].fullName());
}

This of course doesn't address the legitimate concerns you raise about
possible performance problems.

-- Scott
____________________
[1] http://www.json.org/js.html