4 methods of judging whether an object belongs to the Array type in JavaScript and the principles and limitations behind it

Preface

There is no doubt that Array.isArray is the first choice for judging whether an object belongs to the Array type in JavaScript today, but I think it is necessary to understand the rest of the methods in this article and the principles and limitations behind them, because most of them in JavaScript The reference type does not provide an isArray judgment method like the Array type. At this time, it is necessary to use the other methods to draw inferences.

Duck model

When an animal walks like a duck and calls like a duck, then it is a duck.
When an object contains attributes of the Array type (such as'splice','join' or'length'), it belongs to the Array type.

The 1.6.0.3 version of prototypejs uses this logic, the code is as follows:

isArray: function(object) {
    
    
    return object != null && typeof object == "object" && 'splice' in object && 'join' in object;
}

However, there is a problem with the duck mode. When an animal walks like a duck and screams like a duck, in addition to being a duck, it may also be a'Donald Duck'.
image

This is like an object that contains the two properties of the Array type, " splice" and " join" . In addition to the Array type, it may also be the Person type.

function isArray(object) {
    
    
    return object != null && typeof object == "object" && 'splice' in object && 'join' in object;
}

function Person(){
    
    
    /**
    code
    */
}

Person.prototype.splice = function(){
    
    
    /**
    code
    */
}

Person.prototype.join = function(){
    
    
    /**
    code
    */
}

let p = new Person();

let isArr = isArray(p);

console.log('isArray : ' + isArr);//isArray : true

The duck mode is more used to judge'like Array ', such as the isArrayLike method in jquery , the code is as follows:

function isArrayLike( obj ) {
    
    

	var length = !!obj && obj.length,
		type = toType( obj );

	if ( typeof obj === "function" || isWindow( obj ) ) {
    
    
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}

The length === 0 and typeof length === "number" && length> 0 && (length-1) in obj two judgment logics are to judge whether the object conforms to the ' like Array ' through the'length' property .

instanceof keyword

The content of the instanceof keyword has been explained in detail in the article "In- depth understanding of the usage scenarios and precautions of typeof and instanceof , so only a brief description is given here. The instanceof keyword is used to determine whether the prototype property value of the function Array exists on the prototype chain of the object. If it exists, it means that the object is of type Array, otherwise it is not.

However, instanceof is not completely credible. For example, the judgment result of instanceof can be affected by the Symbol.hasInstance property:

function Person(){
    
    
}

Object.defineProperty(Person,Symbol.hasInstance,{
    
    
    value : function(){
    
    
        return false;
    }
})

let p = new Person();

p instanceof Person;//false

When the data and type are not under a global variable, it can also affect the judgment result of instanceof. For example, now define two html files, main.html and iframe.html, the code is as follows:

main.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>main</title>      
    </head>
    <body>
        <iframe src="./iframe.html"></iframe>
        <script type="text/javascript">
            window.onload = function(){
     
     
                console.log('document.domain : ' + document.domain);
                let mainArr = [1,2,3,4,5];
                console.log('mainArr instanceof Array : ' + (mainArr instanceof Array));//
                let iframe = document.documentElement.getElementsByTagName('iframe')[0];
                let iframeWin = iframe.contentWindow.window;
                let iframeArr = iframeWin.arr;
                console.log('iframeArr : ' + JSON.stringify(iframeArr));
                console.log('iframeArr instanceof Array : ' + (iframeArr instanceof Array));//
                console.log('iframeArr instanceof iframeWin.Array : ' + (iframeArr instanceof iframeWin.Array));//
            }
        </script>  
    </body>
</html>

iframe.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>iframe</title>  
    </head>
    <body>
        <p>iframe</p>
        <script type="text/javascript">
            window.onload = function(){
     
     
                window.arr = [6,7,8,9,10];
            }
        </script>  
    </body>
</html>

After npx http-server opens main.html, we get the result:

Insert picture description here

It can be seen that: constructors with the same name under different global variables are not the same function. It is not feasible to use instanceof to judge when the data and type are not under the same global variable.

Object.prototype.toString方法

Object.prototype.toString is not affected by cross-global variables.

main.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>main</title>      
    </head>
    <body>
        <iframe src="./iframe.html"></iframe>
        <script type="text/javascript">
            window.onload = function(){
     
     
                let toString = Object.prototype.toString;
                console.log('document.domain : ' + document.domain);
                let mainArr = [1,2,3,4,5];
                console.log('toString.call(mainArr) : ' + (toString.call(mainArr)));//
                let iframe = document.documentElement.getElementsByTagName('iframe')[0];
                let iframeWin = iframe.contentWindow.window;
                let iframeArr = iframeWin.arr;
                console.log('toString.call(iframeArr) : ' + (toString.call(iframeArr)));//
            }
        </script>  
    </body>
</html>

iframe.html

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
        <title>iframe</title>  
    </head>
    <body>
        <p>iframe</p>
        <script type="text/javascript">
            window.onload = function(){
     
     
                window.arr = [6,7,8,9,10];
            }
        </script>  
    </body>
</html>

After npx http-server opens main.html, we get the result:
Insert picture description here

However, using Symbol.toStringTag will affect the output of Object.prototype.toString.

let toString = Object.prototype.toString;

function Person(){
    
    

}

let p = new Person();

console.log('toString.call(p) : ' + toString.call(p));//toString.call(p) : [object Object]

Object.defineProperty(p,Symbol.toStringTag,{
    
    
    get(){
    
    
        return "Person";
    }
})

console.log('toString.call(p) : ' + toString.call(p));//toString.call(p) : [object Person]

You can also:

let toString = Object.prototype.toString;

function Person(){
    
    

}

let p = new Person();

console.log('toString.call(p) : ' + toString.call(p));//toString.call(p) : [object Object]

Object.defineProperty(Person.prototype,Symbol.toStringTag,{
    
    
    get(){
    
    
        return "Person";
    }
})

console.log('toString.call(p) : ' + toString.call(p));//toString.call(p) : [object Person]

You can also write:

let toString = Object.prototype.toString;

class Person{
    
    
    get [Symbol.toStringTag](){
    
    
        return 'Person';
    }
}

let p = new Person();

console.log('toString.call(p) : ' + toString.call(p));//toString.call(p) : [object Person]

Array.isArray method

Array.isArray can be modified because its writable attribute value is true.

Object.getOwnPropertyDescriptor(Array,'isArray');
//{writable: true, enumerable: false, configurable: true, value: ƒ}

Array.isArray = function(data){
    
    
    return null !== data && typeof data === 'object';
}

console.log(Array.isArray(window));//true

Array.isArray will not be affected by cross-global variables, and modifying Symbol.toStringTag will not affect the judgment of Array.isArray.

let toString = Object.prototype.toString;

Object.defineProperty(Array.prototype,Symbol.toStringTag,{
    
    
    get(){
    
    
        return "Person";
    }
})

let arr = new Array();

console.log(Array.isArray(arr));//true

console.log('toString.call(arr) : ' + toString.call(arr));//toString.call(arr) : [object Person]

Specific Array.isArray judgment logic I found v8 in the array-isarray.tq file.

// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

namespace runtime {
    
    
  extern runtime ArrayIsArray(implicit context: Context)(JSAny): JSAny;
}  // namespace runtime

namespace array {
    
    
  // ES #sec-array.isarray
  javascript builtin ArrayIsArray(js-implicit context:
                                      NativeContext)(arg: JSAny): JSAny {
    
    
    // 1. Return ? IsArray(arg).
    typeswitch (arg) {
    
    
      case (JSArray): {
    
    
        return True;
      }
      case (JSProxy): {
    
    
        // TODO(verwaest): Handle proxies in-place
        return runtime::ArrayIsArray(arg);
      }
      case (JSAny): {
    
    
        return False;
      }
    }
  }
}  // namespace array

end

Due to the lack of knowledge of this talent, if problems are found, I hope to point them out to me, thank you.

Guess you like

Origin blog.csdn.net/qq_35508835/article/details/113871051
Recommended