javascript 数据类型、类型检测、类型检测函数的封装(包括纯粹对象、空对象、window 对象)。

一、基础类型及类型检测

1、基础类型

JavaScript 数据类型包括:Null、Undefined、Boolean、Number、String、Symbol、Bigint(7 种原始类型)、Object(引用类型,包括 Object/Array/Date/Function/RegExp 等)

原始类型保存在栈内存。

引用类型保存在堆内存,不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存中的引用地址。

引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。

2、类型检测

(1)、typeof

7 种原始类型可以使用 typeof 操作符检查数据类型(其中,typeof null === ‘object’)

检查 Object 派生出来的结构类型,使用 typeof 是不行的, 会一直 === ‘object’,通常使用 instanceof 检查 Object 种类(这样还是存在误差)。

(2)、instanceof

instanceof 运算符是用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

Tips: 检测对象不是某个构造函数的实例

1
2
3
4
5
6
if (!(mycar instanceof Car)) {
// do something
}
// 上面代码和下面代码完全不同,下面的代码!mycar会在instanceof之前被验证
if (!mycar instanceof Car) {
}
(3)、Object.prototype.toString().call()

每个对象都有一个 toString()方法,可以通过 toString() 来获取每个对象的类型。为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。

调用 Object.prototype.toString 会返回一个由 “[object “ 和 class 和 “]” 组成的字符串,而 class 是要判断的对象的内部属性。

(看了别人翻译的es5规范,讲解这个toString()函数,如下:)
当toString方法被调用的时候,执行步骤为:

1、如果 this 值是 undefined,就返回 [object Undefined]
2、如果 this 的值是 null,就返回 [object Null]
3、让 O 成为 ToObject(this) 的结果
4、让 class 成为 O 的内部属性 [[Class]] 的值
5、最后返回由 “[object “ 和 class 和 “]” 三个部分组成的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 以下是11种:
var number = 1; // [object Number]
var string = "123"; // [object String]
var boolean = true; // [object Boolean]
var und = undefined; // [object Undefined]
var nul = null; // [object Null]
var obj = { a: 1 }; // [object Object]
var array = [1, 2, 3]; // [object Array]
var date = new Date(); // [object Date]
var error = new Error(); // [object Error]
var reg = /a/g; // [object RegExp]
var func = function a() {}; // [object Function]

function checkType() {
for (var i = 0; i < arguments.length; i++) {
console.log(Object.prototype.toString.call(arguments[i]));
}
}

checkType(
number,
string,
boolean,
und,
nul,
obj,
array,
date,
error,
reg,
func
);

// 除了以上11种,还包括
console.log(Object.prototype.toString.call(Math)); // [object Math]
console.log(Object.prototype.toString.call(JSON)); // [object JSON]

// 还可以这样
function a() {
console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
a();
(4)、封装一个 type 函数

写一个 type 函数检测各种类型的值,考虑到兼容性(IE6,null 和 undefined 会被 Obejct.prototype.toString 识别为[object Obejct])

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var class2type = {};

// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error"
.split(" ")
.map(function (item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
});

function type(obj) {
// 一箭双雕
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function"
? class2type[Object.prototype.toString.call(obj)] || "object"
: typeof obj;
}
(5)、函数类型的检测
1
2
3
4
// type函数
function isFunction(obj) {
return type(obj) === "function";
}
(6)、纯粹对象的检测

什么是纯粹对象?

该对象是通过 “{}” 或 “new Object” 创建的,该对象含有零个或者多个键值对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 上面写 type 函数时,用来存放 toString 映射结果的对象
var class2type = {};

// 相当于 Object.prototype.toString
var toString = class2type.toString;

// 相当于 Object.prototype.hasOwnProperty
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
var proto, Ctor;

// 排除掉明显不是obj的以及一些宿主对象如Window
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}

/**
* getPrototypeOf es5 方法,获取 obj 的原型
* 以 new Object 创建的对象为例的话
* obj.__proto__ === Object.prototype
*/
proto = Object.getPrototypeOf(obj);

// 没有原型的对象是纯粹的,Object.create(null) 就在这里返回 true
if (!proto) {
return true;
}

/**
* 以下判断通过 new Object 方式创建的对象
* 判断 proto 是否有 constructor 属性,如果有就让 Ctor 的值为 proto.constructor
* 如果是 Object 函数创建的对象,Ctor 在这里就等于 Object 构造函数
*/
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;

// 在这里判断 Ctor 构造函数是不是 Object 构造函数,用于区分自定义构造函数和 Object 构造函数
return (
typeof Ctor === "function" &&
hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)
);
}
(7)、空对象的判断
1
2
3
4
5
6
7
function isEmptyObject(obj) {
var name;
for (name in obj) {
return false;
}
return true;
}
(8)、window 对象的判断
1
2
3
function isWindow(obj) {
return obj != null && obj === obj.window;
}
(9)、数组类型的检测
1
2
3
4
5
6
// 判断Array.isArray()方法是否存在,如果存在就使用该方法,不存在就使用type函数
var isArray =
Array.isArray ||
function (obj) {
return type(obj) === "array";
};
(10)、类数组的判断

满足的三个条件:

1、是数组
2、长度为 0
3、lengths 属性是大于 0 的数字类型,并且 obj[length - 1]必须存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function isArrayLike(obj) {
// obj 必须有 length属性
var length = !!obj && "length" in obj && obj.length;
var typeRes = type(obj);

// 排除掉函数和 Window 对象
if (typeRes === "function" || isWindow(obj)) {
return false;
}

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

函数库的实现,如:underscore

1
2
3
4
5
6
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

var isArrayLike = function (collection) {
var length = getLength(collection);
return typeof length == "number" && length >= 0 && length <= MAX_ARRAY_INDEX;
};
(11)、判断是不是 DOM 元素
1
2
3
var isElement = function (obj) {
return !!(obj && obj.nodeType === 1);
};