The documentation on mozilla.org states: "Every object has a toString() method that is automatically called when the object is to be represented as a text value or when an object is referred to in a manner in which a string is expected. By default, the toString() method is inherited by every object descended from Object. If this method is not overridden in a custom object, toString() returns "[object type]", ..."
Sounds logical: A toString() method will be called. So I defined an own "class" and defined a toString() method:
function DataField(name, dataType) {
// ....
}
DataField.prototype.toString = function() {
return this.type.name + " " + this.name;
}
I instantiate an object like this:
var dataField = new DataField("myint", DataType.INTEGER);
I try to output the object respectively the string representation of the object in NodeJS:
console.log(dataField);
console.log("" + dataField);
console.log(dataField.toString());
The first output differs from the other ones. The first one will give some kind of JSON.stringify() representation of the object. toString() is not called automatically as I'd expect it to be. The second and third output will result in exactly what I'd expect:
INTEGER myint
So I'm a bit confused. That means that toString() will not automatically be called though the explanation of mozilla.org suggests. Is this a regular behaviour in NodeJS or did I missinterpret the explanation on mozilla.org or am I missing something else here?
You might think: Well, then let's just call toString() explicitely if necessary and everything is fine. But there is some inconvenience with that. If I create an array of objects and use console.log() to ouput that array, no toString() will be called. Which means: The output generated is useless. This would mean that if I store objects with explicite implementations of toString() within an array in a container object I will have to implement toString() of the container object in a very specific and incovenient way: I must write code to explicitely create a string representations of any array of objects contained in the container object myself. Of course I could do that, but I would intuitively assume a toString() method to be called automatically therefor eliminating such need for explicite implementations.
Regarding NodeJS it seems that finding information is not that easy on this specific problem. So if you know about this topic any help is appreciated. The question is: What is the most elegant way to create a (human readable) string representation of a data model (= a data container object containing lists of other data objects)?
In Node.js, console.log calls util.format, which does not call toString().
Related
I am studying prototyping in Javascript, and for an experiment was trying to strip some basic objects of their information as much as possible. However, I ran into an issue in the following code segment:
let x = [];
Object.setPrototypeOf(x, null);
console.log(Object.prototype.toString.call(x));
>>> [object Array]
My confusion here: after removing the prototype, how does Object.prototype.toString still know that x is of Array type? Where is this information coming from, if not any of the properties? Running Object.getOwnPropertyNames(x) and Object.getOwnPropertySymbols(x), I do not see any properties left on x which contain any path to the string Array. Where is this information stored?
And if this is one of those "internal slots" (although I'm not sure which it would be), is there no way to modify it? Can I at least access it directly?
Update:
Yousaf has noted that the property String.toStringTag is normally accessed by Object.prototype.toString to provide the string which goes in place of "Array" in the above example. Indeed, setting x[Symbol.toStringTag] = 'my_string' allows us to change the .toString() value. However, I think it still does not solve the mystery, as x[Symbol.toStringTag] is undefined before we set it, not containing the desired "Array" string.
When .toString() is called on any object, it creates a string of the form [object x] where x is the name of the constructor in-case of built-in constructors like Date and Array.
When .toString() is called, there are several steps taken and one of those steps is checking if the value of this inside .toString() is an array. If its an array, tag is set to the string 'Array'.
From the Spec - 19.1.3.6 Object.prototype.toString ( ):
If isArray is true, let builtinTag be "Array".
Before ES6, when .toString() was called on the instances of user defined constructors, [object Object] was logged. ES6 provided a way to override the tag using a well-known Symbol Symbol.toStringTag.
As per this documentation,
The string representations of each of these objects are appended
together in the order listed and output.
Also as per answer
The + x coerces the object x into a string, which is just [object
Object]:
So, my question is
If I do
str = new String("hello")
console.log(str) //prints the string object but not 'hello'
console.log(""+str) //prints "hello"
So, in first case, it simply prints the object (doesn't invoke the toString() method).
But in second case, it doesn't coerce but simply print the primitive value. Why is that so?
Which method does console.log invokes to print the object?
Please note that - this is not a duplicate of this question.
Console API is not a standard API that is defined in any specification but is something that is implemented across all browsers, so vendors are usually at their liberty to implement in their own fashion as there's no standard spec to define the output of any methods in API.
Unless you check the actual implementation of the Console API for a particular browser, you can never be sure. There's a tracker on GitHub listing the differences between implementation from major browsers.
If you look at the implementation in FF (available here - search for log), it has a comment below
A multi line stringification of an object, designed for use by humans
The actual implementation checks for the type of argument that is passed to log() and based on it's type, it generates a different representation.
Coming to your case, log() prints two different values for strings created using literal notation and strings created using String constructor because they are two different types. As explained here, Strings created using literal notation are called String Primitives and strings created using String constructor are called String Objects.
var str1 = 'test';
var str2 = new String('hello');
typeof str1 // prints "string"
typeof str2 // prints "object"
As the types differ, their string representation differs in the Console API. If you go through the code for FF's Console implementation, the last statement is
return " " + aThing.toString() + "\n";
So to answer your question, Console API in FF calls toString() on the argument only if the argument type is not one of {undefined,null,object,set,map} types. It doesn't always call toString() or valueOf() methods. I didn't check the implementation of Chrome, so I won't comment on that.
It does not utilize toString, you can do something like this
clog = function(msg){console.log(msg.toString());}
clog(myObj);
This is more typing but will invoke obj.toString() as well:
console.log(`${obj}`);
console.log(str) calls str.valueOf() I guess.
From JavaScript- The Definitive Guide
Its job is to convert an object to a primitive value. The valueOf() method is invoked automatically when an object is used in a numeric context, with arithmetic operators (other than +) and with the relational operators, for example. Most objects do not have a reasonable primitive representation and do not define this method.
---edit----Sorry,copy the wrong line, I mean the ""+str,since there's a type converting
I was just wondering, in most of my projects I have been able to use:
the String() function,
the toString() method and
the JSON.stringify() method (I don't really use this),
to convert data in JavaScript to string without much difference and I was wondering what exactly the difference was between using each of them.
Thanks for reading through, I'll really appreciate your answer.
the String() function
This is the explicit use of the native constructor function that creates and returns a string object when used with the new operator or the string value only when used without new.
the toString() method
This invokes the object's toString() method which returns the string representation of the object, which, if not overridden, usually is something like [object Object], which indicates the instance and the type. Custom objects often will override this inherited method to be able to display the best string representation of that particular object.
JSON.stringify()
This takes an object and converts it into the JSON data format. Without using an optional "replacer" function, all properties that store functions will be stripped out of the string. This is generally used when an object is packaged to hold data and then that data is to be sent over HTTP to another location.
String() is a global string constructor, that accepts anything htat should be converted to string ad returns primitive string. Under the hood this calls Object.prototype.toString()(https://www.ecma-international.org/ecma-262/5.1/#sec-15.5.1.1)
Object.prototype.toString() is a method that return string representation of an object: the value of type String that is the result of concatenating the three Strings "[object ", class, and "]", where class is internal property of object. This function can be overriden
JSON.stringify() method converts a JavaScript value to a JSON string; Boolean, Number, and String objects are converted to the primitive values during the process
As per this documentation,
The string representations of each of these objects are appended
together in the order listed and output.
Also as per answer
The + x coerces the object x into a string, which is just [object
Object]:
So, my question is
If I do
str = new String("hello")
console.log(str) //prints the string object but not 'hello'
console.log(""+str) //prints "hello"
So, in first case, it simply prints the object (doesn't invoke the toString() method).
But in second case, it doesn't coerce but simply print the primitive value. Why is that so?
Which method does console.log invokes to print the object?
Please note that - this is not a duplicate of this question.
Console API is not a standard API that is defined in any specification but is something that is implemented across all browsers, so vendors are usually at their liberty to implement in their own fashion as there's no standard spec to define the output of any methods in API.
Unless you check the actual implementation of the Console API for a particular browser, you can never be sure. There's a tracker on GitHub listing the differences between implementation from major browsers.
If you look at the implementation in FF (available here - search for log), it has a comment below
A multi line stringification of an object, designed for use by humans
The actual implementation checks for the type of argument that is passed to log() and based on it's type, it generates a different representation.
Coming to your case, log() prints two different values for strings created using literal notation and strings created using String constructor because they are two different types. As explained here, Strings created using literal notation are called String Primitives and strings created using String constructor are called String Objects.
var str1 = 'test';
var str2 = new String('hello');
typeof str1 // prints "string"
typeof str2 // prints "object"
As the types differ, their string representation differs in the Console API. If you go through the code for FF's Console implementation, the last statement is
return " " + aThing.toString() + "\n";
So to answer your question, Console API in FF calls toString() on the argument only if the argument type is not one of {undefined,null,object,set,map} types. It doesn't always call toString() or valueOf() methods. I didn't check the implementation of Chrome, so I won't comment on that.
It does not utilize toString, you can do something like this
clog = function(msg){console.log(msg.toString());}
clog(myObj);
This is more typing but will invoke obj.toString() as well:
console.log(`${obj}`);
console.log(str) calls str.valueOf() I guess.
From JavaScript- The Definitive Guide
Its job is to convert an object to a primitive value. The valueOf() method is invoked automatically when an object is used in a numeric context, with arithmetic operators (other than +) and with the relational operators, for example. Most objects do not have a reasonable primitive representation and do not define this method.
---edit----Sorry,copy the wrong line, I mean the ""+str,since there's a type converting
I've recently read the article about Object.prototype.valueOf() on MDN and feel like they got something completely wrong by saying:
The valueOf() method returns the primitive value of the specified object.
I mean, this sentence would make some sense if we were talking about the more specific versions of this method, for example String.prototype.valueOf(), which is closer in the prototype chain and therefore would be called instead of the Object.prototype method, when the valueOf method of a String object is invoked. In this case, the internal algorithm thisStringValue would be run and the value of the internal property [[StringData]] of the object, i.e. the primitive string value would be returned. So this would be indeed a conversion from object to primitive value.
But as far as I understand it, the valueOf method of Object.prototype works exactly the opposite way, by calling the internal method ToObject.
Now, if Object.prototype.valueOf() is called upon a plain object, it would invoke ToObject passing in the this value of the method, which points to the object itself, and in this case, ToObject would just return the reference on the object, which is displayed via toString() as [object Object].
Supposed we would have overridden the value of String.prototype.valueOf() with the related method of Object.prototype for example and then create a String object by calling the constructor. - If we now invoke valueOf(), we would get an object in return and NOT the primitive string value we passed in as an argument. There's just ToObject in Object.prototype.valueOf(), no conversion from object to primitive value, as far as I see it.
So did I get this right and they're wrong, or is it me who didn't understand it?
The valueOf() method returns the primitive value of the specified object.
Remember, MDN is written by mere mortals such as you and I. I would not have chosen this way to summarize valueOf. A more meaningful summary would have been (borrowing from the "Description" below):
The valueOf() method returns the value to be used when the specified object needs to be coerced to a primitive.
The rest of the description on the MDN page seems pretty reasonable and clear to me.
From your comment:
I think it is wrong to treat all valueOf-methods of built-in objects as one, because they do not act the same way, which is implied in the article I linked in my question.
But the article itself says
Every built-in core object overrides this method to return an appropriate value.
So it is not treating them all as one. Yes, there is one valueOf interface, but it has different implementations on different objects.
it is wrong to say THIS method would be "called to convert an object to a primitive value"
I don't see that exact phrase in the article, but it sounds right to me.
The purpose of Object.prototype.valueOf is just to serve as a default method for objects that don't override it with a more specific method. You shouldn't call this function directly, it's just called as a result of not having something more specific in the prototype chain when you call the valueOf() method of an object.
By definition, if an object doesn't override it, its "primitive value" is just the object itself.
The statement is not exactly accurate, and it is "more true" for specific implementations as you correctly said, but it is a summary and explains the intention of the method.
it is wrong to say THIS method would be "called to convert an object to a primitive value",
No, it's correct. To be more precise: It's called to convert an object to a number. From the specs:
When the abstract operation OrdinaryToPrimitive is called with arguments O and hint, the following steps are taken:
Assert: Type(O) is Object
Assert: Type(hint) is String and its value
is either "string" or "number".
If hint is "string", then Let
methodNames be «"toString", "valueOf"».
Else, Let methodNames be
«"valueOf", "toString"».
For each name in methodNames in List order, do
...
iii. If Type(result) is not Object, return result.
Throw a TypeError exception.
There is no guarantee, of course, that an implementation of valueOf actually returns a primitive value, but that's what the method is for. And even Object.prototype.valueOf will in fact be called to convert an object to a primitive value, if there is no other implementation in the prototype chain. It won't work, as you noticed, but it will be called nonetheless.
You can try it yourself:
var myObject = {
valueOf: function() {
console.log('valueOf');
return this;
},
toString: function() {
console.log('toString');
return this;
}
};
1 + myObject;
Will print:
valueOf
toString
Uncaught TypeError: Cannot convert object to primitive value