The introduction

One of the classic front-end interview questions is, how do you tell if an object is an array?

ES5 provides a function to determine whether an object is an array

Array.isArray(object);
Copy the code

Object is required and represents the object to be tested

Array.isArray([]); //true
Array.isArray({}); //false 
Array.isArray(' '); //false
Copy the code

However, when it comes to browser compatibility, we need a more secure approach

Object.prototype.toString.call/apply(object);
Copy the code

The comparison results are as follows

Object.prototype.toString.call([]); <! --"[object Array]"--> Object.prototype.toString.call({}); <! --"[object Object]"-->
Copy the code

As to why you should use this method to determine whether an object is an array, just understand the data type judgments for Typeof and Instanceof.

Here are some thoughts on the toString() method.


thinking

First, let’s talk about the toString() method, which converts to a string

In ECMAScript, every instance of type Object has a toString() method that returns a string representation of the Object, so every instantiated Object can call the toString() method.

The result of the call is

var obj = {a: 1};
obj.toString(); //"[object Object]"
Copy the code

So where does obj’s toString() method come from?

Object. Prototype => obj. Proto => object.prototype Every instantiation objects so that the Object can be Shared Object. The prototype, the toString () method.

If not via the prototype chain, how to invoke the Object directly. The prototype. The toString () method?

Object.prototype.toString(); <! --"[object Object]"-->
Copy the code

Is that right? The toString() call in this code has nothing to do with the obj object. Why do we get the same result? This is because Object.prototype is also an Object, so returns a string representation of the Object!

Through the call Object obj Object. The prototype. The toString () method as shown in the right way to the following:

Object.prototype.toString.call/apply(obj); <! --"[object Object]"-->
Copy the code

Next, let’s examine how different types of “objects” call toString(). What is the difference in the return value?

Let’s clarify the ECMAScript data types. There are seven

  • Undefined
  • Null
  • String
  • Number
  • Boolean
  • Object
  • Symbol (ES6 introduced)

Object, as a reference type, is a data structure that is often referred to as the Object class (but this is a misnomer, as there are no classes in JS, everything is just syntactic sugar).

In addition, based on the Object type, JS also implements other commonly used Object subtypes (i.e. different types of objects).

  • Object
  • Array
  • Function
  • String
  • Boolean
  • Number
  • Date
  • RegExp
  • Error
  • .

We can say that the Object class is the parent of all subclasses

Object instanceof Object; //true
Function instanceof Object; //true
Array instanceof Object; //true
String instanceof Object; //true
Boolean instanceof Object; //true
Number instanceof Object; //true
Copy the code

So the toString() method defined in Object.prototype is the original toString() method, and all other types more or less rewrite toString(). Causes different types of objects to call toString() to return different values.

It should also be noted that instance objects can be created in two forms, constructor and literal.

Let’s examine the results of overriding toString() for different object subtypes

  1. Object (Object class)

ToString () : Returns a string representation of an object

var obj = {a: 1}; obj.toString(); //"[object Object]"Object.prototype.toString.call(obj); //"[object Object]"
Copy the code

Here, we are thinking about a problem, any object object may be bound by this call object. The prototype, the toString () method? The answer is yes, and here’s the result

Object.prototype.toString.call({}); <! --"[object Object]"--> Object.prototype.toString.call([]); <! --"[object Array]"-->
Object.prototype.toString.call(function() {}); <! --"[object Function]"-->
Object.prototype.toString.call(' '); <! --"[object String]"--> Object.prototype.toString.call(1); <! --"[object Number]"-->
Object.prototype.toString.call(true); <! --"[object Boolean]"--> Object.prototype.toString.call(null); <! --"[object Null]"--> Object.prototype.toString.call(undefined); <! --"[object Undefined]"--> Object.prototype.toString.call(); <! --"[object Undefined]"--> Object.prototype.toString.call(new Date()); <! --"[object Date]"--> Object.prototype.toString.call(/at/); <! --"[object RegExp]"-->
Copy the code

Can see from the above code, because the Object is all parents of children, so any type of Object Object may be bound by this call Object. The prototype. The toString () method, which returns a string representation of this Object.

  1. Array (Array class)

ToString () : Returns a comma-separated string concatenated from the string form of each value in the array

var array = [1, 's'.true, {a: 2}]; array.toString(); //"1,s,true,[object Object]"Array.prototype.toString.call(array); //"1,s,true,[object Object]"
Copy the code

Can a non-array object call array.prototype.toString () with this binding? The answer is yes, and here’s the result

Array.prototype.toString.call({}); <! --"[object Object]"-->
Array.prototype.toString.call(function() {}) <! --"[object Function]"--> Array.prototype.toString.call(1) <! --"[object Number]"-->
Array.prototype.toString.call(' ') <! --"[object String]"-->
Array.prototype.toString.call(true) <! --"[object Boolean]"--> Array.prototype.toString.call(/s/) <! --"[object RegExp]"--> Array.prototype.toString.call(); <! --Cannot convert undefined or null to object at toString--> Array.prototype.toString.call(undefined); Array.prototype.toString.call(null);Copy the code

Array.prototype.tostring (); array.prototype.toString (); array.prototype.toString (); array.prototype.toString (); Returns a string representation of the object, and null and undefined cannot be bound to call array.prototype.toString ().

  1. Function (function class)

ToString () : Returns the code for the function

function foo(){
    console.log('function'); }; foo.toString(); <! --"function foo(){--> <! -- console.log('function'); -- > <! -}"--> Function.prototype.toString.call(foo); <! --"function foo(){--> <! -- console.log('function'); -- > <! -}"-->
Copy the code

One more thing to note here is that all of the “classes” mentioned above are constructors in nature, so calls to the toString() method return function code.

Object.toString();
//"function Object() { [native code] }"
Function.toString();
//"function Function() { [native code] }"
Array.toString();
//"function Array() { [native code] }".Copy the code

Also, can a non-function object call array.prototype.toString () from this binding? The answer is no, and here’s the result

Function.prototype.toString.call({}); <! --Function.prototype.toString requires that'this' be a Function-->
Copy the code

In addition, tests on other Object subclasses show that none of the classes, except Object and Array, allow non-instances of the class to call the toString() method on the prototype Object through this binding. This means that when overriding the toString() method, It explicitly limits the type of object on which the method is called. It cannot be called without an instance of its own object. So, normally we only use the Object. The prototype. ToString. Call/apply () method.

  1. Date (Date class)

ToString () : Returns the date and time with time zone information

The Date type has only a construct, not a literal

var date = new Date();
date.toString();
//"Fri May 11 2018 14:55:43 GMT+0800"
Date.prototype.toString.call(date);
//"Fri May 11 2018 14:55:43 GMT+0800"
Copy the code
  1. Regular expressions (RegExp class)

ToString () : Returns the literal of a regular expression

var re = /cat/g; re.toString(); //"/cat/g"RegExp.prototype.toString.call(re); //"/cat/g"
Copy the code
  1. Basic wrapper types (Boolean/Number/String classes)

ECMAScript provides three special reference types, Boolean, Number, and String, with special behavior corresponding to their respective base types.

Take the String type as an example

var str = 'wangpf'; str.toString(); //"wangpf"
Copy the code

There are questions about the above code. First, I define a string variable of primitive type STR. It is not an object, but why can I call toString(), and where does the toString() method come from?

Let’s first look at the difference between STR and strObject:

var str = 'I am a string';
typeof str; //"string"
str instanceof String; //false

var strObject = new String('I am a string');
typeof strObject; //"object"
strObject instanceof String; //true
strObject instanceof Object; //true
Copy the code

Because of the String primitive wrapper type, the JS engine converts a String literal into a String object when necessary, which can perform operations that access properties and methods as follows:

(1) Create a String instance; (2) Call the specified method on the instance; (3) Destroy the instance.Copy the code

So the toString() method is called as follows:

var strObject = new String('wangpf');
strObject.toString(); //'wangpf'
strObject = null;
Copy the code

Note that the above code is automatically executed by the JS engine, you cannot access the strObject object, it only exists at the moment the code is executed and then destroyed immediately, so we can no longer add properties and methods to the base type at runtime, unless we create the object directly by calling the base wrapper type through the new display, which we do not recommend!!

  1. String string (string class)

ToString () : Returns a copy of the string

var str = "a";
str.toString(); //"a"
String.prototype.toString.call(str); //"a"
Copy the code
  1. Number (number class)

ToString () : Returns a numeric value as a string

var num = 520;
num.toString(); //"520"
Number.prototype.toString.call(num); //"520"
Copy the code
  1. Boolean (Boolean class)

ToString () : Returns the string “true” or “false”

var boo = true;
boo.toString(); //"true"
Boolean.prototype.toString.call(boo); //"true"
Copy the code
  1. Null, and undefined

Null and undefined have no constructors, so they do not and cannot call the toString() method, which means they cannot access any properties and methods, just primitive types.

  1. Global object Window (Window class)

The Global object Global is one of the most special objects in ECMAScript. It does not exist, but acts as the ultimate “backstop object”. All properties and methods that do not belong to other objects are ultimately its properties and methods.

ECMAScript does not specify how to access the Global object directly, but Web browsers implement the Global object as part of the Window object. So all of the above mentioned Object types, such as Object, Array, and Function, are properties of the Window Object.

ToString (): Returns a string representation of an object

window.toString(); <! --"[object Window]"--> Window.prototype.toString.call(window); // There is something wrong with <! --"[object Window]"-->
Copy the code

Upon inspection, Winodw class was not in the Window. The prototype prototype Object to rewrite the toString () method, it will follow the prototype chain lookup call Object. The prototype. The toString ().

Any object, therefore, the object may be bound by this call Window. The prototype. The toString () method, which is also called object. The prototype. The toString () method, results and object class.

So the above code is essentially

Object.prototype.toString.call(window); <! --"[object Window]"-->
Copy the code

Finally, let’s talk about executing the toString() method directly

Execute toString() directly, and the output is as follows

toString(); <! --"[object Undefined]"-- -- > (function(){ console.log(toString()); }) (); <! --[object Undefined]-->Copy the code

That is, calling toString() directly is equivalent to

Object.prototype.toString.call(); <! --"[object Undefined]"--> Object.prototype.toString.call(undefined); <! --"[object Undefined]"-->
Copy the code

So calling toString() directly should be a variant of undefined. ToString ().

Note that calling toString() directly cannot be interpreted as calling toString() globally, i.e. Window.tostring ();

Also, say something about the toString.call/apply(this) method

toString.call({}); <! --"[object Object]"--> toString.call([]); <! --"[object Array]"-->
Copy the code

People often use the toString. Call/apply (this) to replace the Object. The prototype. ToString. Call/apply (this), I think it is not rigorous, easy to cause some problems, as shown below

function toString(){
    console.log("wangpf") } toString(); //"wangpf"toString.call({}); //"wangpf"toString.call([]); //"wangpf"
Copy the code

We can see that when we call toString() directly, instead of calling the toString() method of the Object class by default, we will use our own method, which may not get the result we want. So we still should try to use the Object. The prototype. ToString. Call/apply (this).


expand

Like the toString() method, different subtypes of Object override toLocaleString(), valueOf(), etc. The point here is that no matter how Object subtypes override methods, as long as we understand where they come from and how they are called, You’ll have a good understanding of the results of these method calls!

At the end of the day, understanding objects and prototypes in JS is really, really important!


reference

  • JavaScript Advanced Programming (3rd Edition)
  • JavaScript you Don’t Know (Volume 1)