2

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
Amar Singh
  • 5,464
  • 2
  • 26
  • 55
  • Since a String is an array of characters it makes sense to let it support Array methods. It's still not an Array instance though. You're asking "why was JS designed this way" though, and I'm not sure this website is the place for that. –  May 30 '19 at 06:45
  • Strings can use `.filter` upon a `call` because they are **iterable**, hence they provide an **iterator**. However, that doesn't mean that they behave like arrays. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator – briosheje May 30 '19 at 06:45
  • Possible duplicate of [Who can help to explain this JavaScript algorithm \[\].filter.call()](https://stackoverflow.com/questions/40531611/who-can-help-to-explain-this-javascript-algorithm-filter-call) and [how does Array.prototype.slice.call() work?](https://stackoverflow.com/questions/7056925) – adiga May 30 '19 at 06:57

2 Answers2

5

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)

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Ok so since the design of `String` is similar to 'Array' i.e iterable and has length, it supports some Array methods – Amar Singh May 31 '19 at 11:26
  • A string *happens* to be iterable, but it is not necessary. See how the ´obj´ is just a (non-iterable) object, but can be ´.call´ed as well. – CertainPerformance May 31 '19 at 11:31
1

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.

Akrion
  • 18,117
  • 1
  • 34
  • 54