在进行深拷贝的时候经常需要判断数组和对象,这里提供几个常用方法。

首先,typeof 可以判断除了 null 之外所有的基本类型,以及函数。返回值为 7 个字符串:

string
 boolean
 number
 symbol (ES2015 新增)
 bigint (ES2020 新增)
 undefined
 function

注意:基本类型中的 null 会被判断为 object

typeof null === 'object'

在 JavaScript 中不同的对象在底层都表示为二进制,其中前三位表示类型信息,二进制前三位是 000 的话会被判断为 object 类型,null 的二进制表示是全 0, 自然前三位也是 0, 所以执行 typeof 时会返回“object”。

因此 typeof 在判断基本类型 null ,引用类型 ArrayObject 、基本类型的包装类型以及函数实例 (new + 函数) 时,得到的都是 object ,局限性很大。下面介绍几种判断数组的常用方法。

1. 使用Array.isArray()

Array.isArray() 用于确定传递的值是否是一个 Array 。这个方法是 Array 的静态方法,不能在实例上访问,只能通过 Array 进行调用。

const arr = [1, 2]
const obj = {a: 1}
Array.isArray(arr) // true
Array.isArray(obj) // false

2. 使用instanceof判断Array和Object

instanceof用来判断一个变量是否为一个构造函数创建的实例,代码形式为obj1 instanceof obj2(obj1是否是obj2的实例)判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在obj1的原型链上,(obj1 instanceof obj2)值为true。

注意:不管判断数组还是对象,都必须 xxx instanceof Array

arr = [1, 2, 3, 4, 5]
obj = {name: "dby", age: 23}
arr instanceof Array // true,即为数组
obj instanceof Array // false,即为对象

在 JS 中 Array 继承了 Object,所以数组也会被判断为对象,因此 xxx instanceof Object无法区分数组和对象

arr = [1, 2, 3, 4, 5]
obj = {name: "dby", age: 23}
Array instanceof Object // true,数组继承了Obejct的原型对象
arr instanceof Object // true,数组也会被判断为对象
obj instanceof Object // true

通过上面的分析,有的同学应该会发现,instanceof判断数组和对象其实还是有限制的,xxx instanceof Array其实只能区分数组,因为JS中有12种常见的内置对象Arguments, Boolean, Date, Error, Function, JSON, Math, Number, Array, Object, RegExp, String,这些对象全都继承了Object()的原型对象,而instanceof又会一直沿着原型链去查找,所以当xxx instanceof Array返回false的时候,只能确定不是数组,但无法确定是不是形如{name: "dby", age: 23}的对象。

String instanceof Object // true
Function instanceof Object // true

这里再补充一下instanceof的原理,其实就是沿着原型链向上查找。

function new_instance_of(leftValue, rightValue) {
	let rightProto = rightValue.protytype, // 获取构造函数原型对象
		leftValue = leftValue__proto__; // 获取实例原型指针
	while(leftValue !== null) {
		if(leftValue === rightProto) {
			return true; // 如果实例proto指向构造函数原型对象
		}
		leftValue = leftValue.__proto__; // 如果不是,就继续沿着原型链向上查找
	}
	return false; // 实例proto最终指向null,如果还是没有就返回false
}

3. 根据实例上的constructor指针判断

构造函数的prototype上有一个constructor指针,这个指针指回构造函数。

Array.prototype.constructor // ƒ Array() { [native code] }
Object.prototype.constructor // ƒ Object() { [native code] }

通过构造函数创建出来的实例对象上的__proto__指针会指向构造函数的prototype,因此通过__proto__可以访问到constructor,根据constructor就能判断这个实例是由哪个构造函数创建的。

const arr = [1, 2]
const obj = {a: 1}
arr.constructor == Array // true
arr.constructor == Object // false
obj.constructor == Object // true
obj.constructor == Array // false

需要注意的是,像上面这样xxx.constructor的写法,等价于xxx.__proto__.constructor,表示查找创建这个实例的构造函数的prototype,例如arr的构造函数是Array(),obj的构造函数是Object()。但是通过观察arr的原型会发现,arr的__proto__里面还有__proto__,如下图所示(太长了,中间省略):

怎样判断数组 让数组内的内容按条件显示Python 判断数组是否为0_原型对象


第一个__proto__里的constructor指向构造函数Array,第二个__proto__里的constructor指向构造函数Object,这就说明构造函数Array实际上是继承了构造函数Object,所以在原型中可以访问到。当然这里我们只关心第一层__proto__里的constructor,如果要访问更里面的constructor,可以使用xxx.__proto__.proto__.constructor。顺便这也解释了为什么arr instanceof Object也会返回true的原因。

4. 直接尝试调用数组的操作方法

刚才观察了数组的原型对象,发现上面挂载了很多操作数组的属性方法,那么只要在变量上调用数组的操作方法,就能知道这个变量是不是数组,如下所示:

const a = {a: 1}
a.length // undefined,即对象上没有length属性
a.slice // undefined,即对象上没有slice方法

这里slice后面没加括号,函数不会执行,只会返回表达式,但是因为对象上没有这个方法,所以是undefined。为什么这边不让函数执行呢?下面是加了括号的结果:

a.slice() // Uncaught TypeError: a.slice is not a function

可以看到执行就报错了,提示方法不存在,所以当作一个属性去访问的时候不会报错。如果slice作用在数组上会先浅拷贝,并根据传入的参数分割数组。

5. 使用Object.prototype.toString.call()

使用toString()给对象转字符串的时候,发现返回的不是对象的内容,而是数据类型,如下所示:

const a = {a: 1}
a.toString() // "[object Object]"

Object.prototype.toString()方法其实返回一个对象的内部属性[[class]]

由此得到启发,只要让别的类型的数据也能调用对象上的这个方法,就可以判断类型了。实现这个很简单,改变Object.prototype.toString()这个函数的this指向就可以了,可以用call()方法来实现。这个功能非常强大,各种类型都能判断。

Object.prototype.toString.call() // "[object Undefined]"
Object.prototype.toString.call([1, 2]) // "[object Array]"
Object.prototype.toString.call(new Object()) // "[object Object]"
Object.prototype.toString.call(666) // "[object Number]"
Object.prototype.toString.call("666") // "[object String]"
Object.prototype.toString.call(/\s/g) // "[object RegExp]"

如果将对象的内容转为字符串,可以使用 JSON 序列化,即 JSON.stringify() 。