Why do String behaves like an Array ? - JS - javascript

We all know Strings somewhat behaves like an Array.You can even apply some of the array methods to it and take benefits, like the below example.
[].filter.call('abcdef',function(val){
return val<'e';
});
Also,
var a='xyz';
I can access first element using a[0] or I can call a.length like an Array
My question is why String behaves like an Array. and if it does, why do I get false below when I check if it is an instance of Array. Is String Array-like?
'a' instanceof Array

All that Array.prototype.filter really requires is that the variable being iterated over has a length property, and that the variable has numeric-indexed values. See (part of) the polyfill:
var len = this.length >>> 0,
res = new Array(len), // preallocate array
t = this, c = 0, i = -1;
if (thisArg === undefined){
while (++i !== len){
// checks to see if the key was set
if (i in this){
if (func(t[i], i, t)){
res[c++] = t[i];
}
}
}
}
Strings fulfill this condition - strings have a length property, and numeric indicies accessed on the string resolve to individual characters.
But you can do the same thing with arbitrary objects:
const obj = {
0: 'foo',
1: 'bar',
length: 2
};
const result = [].filter.call(obj, val => val.startsWith('f'));
console.log(result);
You could say that obj is array-like as well, since it has a length property and numeric indicies. Most array methods like .filter, .reduce, etc can be .called on array-like objects, even if those objects aren't actual arrays.
(Technically, you can also call array methods on non-array-like objects too, it just won't do anything useful - no iterations may be performed)

To use instanceof you need to create an instance of an Object and a is not an instance of one. It is a primitive or also known as string literal:
String literals (denoted by double or single quotes) and strings
returned from String calls in a non-constructor context (i.e., without
using the new keyword) are primitive strings. JavaScript automatically
converts primitives to String objects, so that it's possible to use
String object methods for primitive strings. In contexts where a
method is to be invoked on a primitive string or a property lookup
occurs, JavaScript will automatically wrap the string primitive and
call the method or perform the property lookup.
For example:
let foo = 'abc' // primitive
let boo = new String() // object
console.log(foo instanceof String) // false
console.log(boo instanceof String) // true
console.log(typeof foo) // 'string' <-- notice not String
console.log(typeof boo) // object
This is simply due to:
The instanceof operator tests the presence of constructor.prototype in
object's prototype chain
But as we explained above we are dealing with string literals which are created without a constructor call (new keyword) and only boxed for our convenience when operating on them. They are not actual instances of String and thus instanceof returns false.
The reason you can use Array.filter on the string primitive is simply due to the fact that it was boxed for you to a String from where it got the length property at the time of execution.
For example in the case of V8 engine string primitive is parsed/boxed to String and a String to a StringObject. Notice they are actually different instances.
Array.filter only cares about that length property and numeric indicies as pointed nicely by CertainPerformance which are provided by the boxing to String. Example:
console.log(Object.getOwnPropertyNames('a')) // ["0", "length"]
However String is not StringObject and thus instanceof would return false.

Related

Object.keys() array with one element treated as string?

What kind of magic is that, when you Object.keys() on an object with one key only, it will be treated as a string when you reference later using square brackets?
See below for example:
let chosenProducts = [
{
toys: 20
}
];
let toys = "toys";
chosenProducts.forEach(product => {
let name = Object.keys(product);
console.log(Array.isArray(name)); // true
console.log(name) // ['toys']
console.log(toys) // "toys" - string
console.log(product[name]); // 20 - array used = wtf?
console.log(product[toys]); // 20 - string used
});
Object.keys returns an array.
All objects have a toString method.
If you use an object in string context, toString is called implicitly.
Square-bracket property accessor notation is, essentially, string context (unless you pass a Symbol)
The default toString method on an array looks something like: function () { return this.join(","); }
const example1 = ["foo", "bar"];
console.log("" + example1);
const example2 = ["baz"];
console.log("" + example2);
Object.keys returns an array of keys. Your name variable is an array with one element ['toys']. When you access product[name] implicit coercion of name array to string happens which results in product["toys"]
In javascript there are two types of coercions implicit and explicit coercion
When you access with Object's properties they will be string, in case of an array they will be indexes (numbers), but array is also an object, when you add any property in array it can be act as object property(string) and will not get counted towards the length of array.
let arr = [1,3];
arr.val = 20;
console.log(arr.length); // 2
In your example you are accessing object's property product[name] here name is an array. What will happen here is implicit coercion of array to string.
Javascript will automatically call toString method on your object which is an array in this case. There is toString method for Array which is linked to their prototype Array.prototype.toString. You can override that if you want to get different result.
In simple terms when you try to access variable in if conditional expression javascript automatically coerce to Boolean value.
let a = 10;
if(a){
console.log("implicit coercion");
}
if(Boolean(a)){
console.log("explicit coercion");
}
To learn more about Javascript grammar and types.

Are array keys implicitly coerced to numbers?

I'm in the book, This & Object Prototypes, Chapter 3: Objects.
In the arrays section, the author says if you add a property to an array, but the array looks like a number, it will end up as part of the array index:
myArray["3"] = "baz";
console.log(myArray.length);
console.log(myArray[3]);
It looks like JavaScript is implicitly coercing the "3" string into the number 3, then it's saying place "baz" in index 3 of the array.
Actually, it's the other way around. All keys in JavaScript objects are strings, and arrays are objects. That means myArray[3] is the same as myArray["3"] because all keys, if not already strings, are coerced into strings because all JavaScript object keys are strings. Per MDN:
An object property name can be any valid JavaScript string, or anything that can be converted to a string, including the empty string.
And:
Please note that all keys in the square bracket notation are converted to String type, since objects in JavaScript can only have String type as key type. For example, in the above code, when the key obj is added to the myObj, JavaScript will call the obj.toString() method, and use this result string as the new key.
For example:
const obj = {
toString() {
return 'foobar';
}
};
const anotherObj = {};
anotherObj[obj] = 'baz';
//anotherObj.foobar is now 'baz'
Since obj is converted to a string implicitly, obj.toString is called which returns 'foobar', which is used as the key value. The same applies for arrays -- when using bracket notation. All keys are strings, and accessing using bracket notation coerces the expression in the brackets into a string.

Why does '.keys' return 'undefined' when applied to the String object in Javascript?

I am trying to understand why the 'keys' method does not return the properties and methods of the String object. In other words what is unique about this object? I tested this theory by creating a generic object, giving it 1 property and 1 method and then running the .keys method on it, and it returned both the property and the method. Since String is an object in Javascript, assumed applying .keys method to it would do the same —at the least returning the .length method in the returned set.
Using Chrome's console I ran the following cases:
typeof String // "function"
"function" == typeof String // true
"object" == typeof String // false
Two notes in addition to my main question:
In the scope of JavaScript:
Is a function not an object?
Aren't most things objects outside primitives and some other special cases?
Some background information
Effectively Object.keys returns a list of own enumerable properties on the Object instance. These are properties belonging only to that instance of the Object class, excluding any prototypically inherited properties and or methods. These are values that would return true in the following examples:
// Create an Object with own properties
var obj = {
foo: 'bar'
}
obj.hasOwnProperty('foo') // => true, brilliant
As many will know, this would still hold true when properties are added after the Object has been constructed. As can be seen in this example:
// Create an Object the retroactively add an enumerable property
var obj = {}
obj.foo = 'bar'
obj.hasOwnProperty('foo') // => true, great
Objects, functions, and arrays all behave this way; whereas strings don't. You can easily see this by trying to add own properties to a string and then reading them back, like this:
var str = 'foo'
str.bar = 'baz'
console.log(str) // => undefined, hmm
// What about "hasOwnProperty"?
str.hasOwnProperty('bar') // => false... too bad
So to answer the question...
Own enumerable properties inherently cannot exist on instances of the String type and cannot be added retrospectively either, because own properties are not assignable to strings, period.
Although this explanation doesn't explain the decisions made whilst implementing this, it certainly gives some underlying sanity as to why String.keys doesn't, or simply can't, exist; and why Object.keys always returns undefined when supplied with a string.
Functions are objects. The defined behavior of typeof is somewhat idiosyncratic. Values in JavaScript are either objects or primitives.
The "keys" property of String instances is undefined because, well, it is not defined on the String prototype. You can add such a property if you like, though working with String instances instead of string primitives is a recipe for lots of weird bugs.
console.log(String)
>function String() { [native code] }
var a = new String()
console.log(a)
>String {length: 0, [[PrimitiveValue]]: ""}
typeof a
>"object"
it should be "class" instead of "function", as like in other languages, but function in js are first-class object (more details here What is meant by 'first class object'?)
object are the instanced classes

When is obj.length not equal to +obj.length?

I was perusing the underscore.js annotated source when I encountered this:
if (obj.length === +obj.length) {...}
I now know from this stackoverflow question that the plus sign (+) operator returns the numeric representation of the object.
That said, obj.length returns a number. When would obj.length not be equal to +obj.length?
The === operator does not make any typecast when it checks, so different types of data will immediately return false even if '5' == 5. The + as you said typecasts the object into number. If you typecast a number into a number, it is still a number, so you basically check if your object.length exists and is a number. Values like undefined, NaN, null, string and others will return false. You are not sure what happens with obj, so you have to check...
When, for example:
var obj = {
0: 'first',
length: '1'
};
alert(obj.length === +obj.length);
Underscore's each is a generic, therefore can work with other objects other than an array. Just like ECMA5 forEach
The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent.
So underscore are checking the validity of an object's length property. And they deem an object arrayLike, for that method of iteration, only if the object's length returns a number which is not NaN, and is certainly not a string. So in my above example, obj would fall through to their keys iteration, if there is no native/polyfilled forEach.

Are strings objects? [duplicate]

This question already has answers here:
How is a Javascript string not an object?
(2 answers)
Closed 7 years ago.
Here are two reasons to think strings are objects. First, you can create a string in the following way:
var mystring = new String("asdf");
I'm under the impression that the constructor function following the new operator has to return an object. Second, strings seem to have properties and methods. For example:
mystring.toUpperCase();
BUT, if strings were objects, then we'd expect something like the following to work:
function string_constructor() {
return "asdf";
}
var mystring = new string_constructor();
But it doesn't, and I've been told it doesn't because strings aren't objects. So are strings objects or not? And, either way, how can I make sense of everything I've listed?
Speaking about language types, Strings are values of the String type.
The language has five primitive types, which are String, Number, Boolean, Null and Undefined.
There are String objects (also for Number or Boolean), they are called primitive wrappers, they are created when you use the constructor function associated with them, for example:
typeof new String('foo'); // "object"
typeof 'foo'; // "string"
But don't get confused with them, you will rarely need to use primitive wrappers, because even if primitive values are not objects, you can still access their inherited properties, for example, on a string, you can access all members of String.prototype, e.g.:
'foo'.indexOf('o'); // 2
That's because the property accessor (the dot in this case) temporarily converts the primitive value to an object, for being able to resolve the indexOf property up in the prototype chain.
About the constructor function you have in your question, as you know, it won't return the string.
Functions called with the new operator return an implicit value, which is a new object that inherits from that function's prototype, for example:
function Test () {
// don't return anything (equivalent to returning undefined)
}
new Test() instanceof Test; // true, an object
If an object is returned from the constructor, that newly created object (this within the constructor) will be lost, so the explicit returned object will come out the function:
function Test2() {
return {foo: 'bar'};
}
new Test2().foo; // 'bar'
But in the case of primitive values, they are just ignored, and the new object from the constructor is implicitly returned (for more details check the [[Construct]] internal operation, (see step 9 and 10)).
In JavaScript, strings come in two flavors:
There is a String language type which contains values like "foo" and 'bar'. Those values are primitive values. Read about the String type here
Then there is a String constructor. (A constructor is a function object which is used to create new instances of a certain "class" (or pseudo-class)). So this: new String("foo")
will create a new object (a value of the type Object), which contains the primitive value "foo". Read about the String constructor here
In practice you don't use the new String('foo') notation, but the string literal notation 'foo'.
So to answer your question:
In JavaScript, strings are not objects. They are primitive values. However, there exist String objects which can be used to store string values, but those String objects are not used in practice.
Primitive strings behaves like objects in JavaScript because they are automatically converted to objects when you call an object method:
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/String
String objects may be created by
calling the constructor new String().
The String object wraps JavaScript's
string primitive data type with the
methods described below. The global
function String() can also be called
without new in front to create a
primitive string. String literals in
JavaScript are primitive strings.
Because JavaScript automatically
converts between string primitives and
String objects, you can call any of
the methods of the String object on a
string primitive. JavaScript
automatically converts the string
primitive to a temporary String
object, calls the method, then
discards the temporary String object.
For example, you can use the
String.length property on a string
primitive created from a string
literal
String are both primitive and object type. Think about int, float, char ... which have real Object classes like Integer, Float and Char in Java.
String is a wrapper around the primitive string datatype. When you do something like var s = "My String" then a String wrapper object is created behind the scenes when needed..
It is interesting however that typeof s = 'string' instead of 'object'. Anybody know why that is?
Strings are objects, but what you are doing in your example is not creating a new String. By using 'new' you are creating an instance of a Object, not a 'string'
var F = function() {
return 'c';
};
var a = new String('a');
var b = 'b';
var c = new F();
alert("a is: " + typeof a + ", b is: " + typeof b + ", c is: " + typeof c);
// alerts: a is: object, b is: string. c is: object
You shouldn't use 'new' for strings regardless (or arrays, or 'simple' objects.)

Categories