目录

  1. 分类
    1. 基本数据类型
    2. 复杂数据类型
    3. 引用类型
    4. 基本类型与引用类型
  2. 变量
  3. 类型检测
    1. typeof
    2. instanceof
      1. instanceof 实现原理
    3. constructor
    4. Object.prototype.toString.call()
    5. 用Array对象的isArray方法判断
    6. 封装可以分辨所有数据类型的方法

参考:
  类型检测
  浅谈 instanceof 和 typeof 的实现原理
  在JavaScript中,如何判断数组是数组?

分类

基本数据类型

NumberStringBooleanUndefinedNull Symbol [ES6]
Number
  Number类型包含整数和浮点数(浮点数数值必须包含一个小数点,且小数点后面至少有一位数字)两种值。
NaN: 非数字类型。特点:① 涉及到的任何关于NaN的操作,都会返回NaN ②NaN不等于自身。
isNaN() 函数用于检查其参数是否是非数字值。
isNaN(123) //false isNaN("hello") //true
String
  字符串有length属性。
字符串转换:转型函数String(),适用于任何数据类型(null,undefined 转换后为null和undefined);toString()方法(null,defined没有toString()方法)。
Boolean
  该类型只有两个值,true和false
Undefined
  只有一个值,即undefined值。使用var声明了变量,但未给变量初始化值,那么这个变量的值就是undefined。
Null
  null类型被看做空对象指针,前文说到null类型也是空的对象引用。

复杂数据类型

Object
  Object本质上是由一组无序的名值对组成的。
  js中对象是一组属性与方法的集合。这里就要说到引用类型了,引用类型是一种数据结构,用于将数据和功能组织在一起。引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。

引用类型

ObjectArrayFunctionDateRegExp

基本类型与引用类型

基本类型又叫原始类型(primitive type)
栈:原始数据类型(Undefined,Null,Boolean,Number、String)
堆:引用数据类型(Object、Array、Function)
两种类型的区别是 存储位置不同
  原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

变量

  ECMAScript中用var关键字来定义变量,因为js是弱类型的,所以无法确定变量一定会存储什么值,也就不知道变量到底会是什么类型,而且变量的类型可以随时改变。
这就是ECMAScript是松散类型的来由,所谓松散类型就是可以用来保存任何类型的数据。
ps:
es6中新增了let命令来声明变量、const命令声明一个只读的常量。
let的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。
const一旦声明,常量的值就不能改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const是用来定义常量的,而且定义的时候必须初始化,且定义后不可以修改。
对于基本类型的数据来说,自然很好理解了,例如 const PI = 3.14。
如果定义的时候不初始化值的话就会报错,错误内容就是没有初始化。

const p = {name: 'www', age: '22'}

p.name = 'dfsfs'

p: // {name: 'dfsfs', age: '22' }

因为对象是引用类型的,P中保存的仅是对象的指针,这就意味着,const仅保证指针不发生改变,
修改对象的属性不会改变对象的指针,所以是被允许的。也就是说const定义的引用类型只要指针不发生改变,
其他的不论如何改变都是允许的。
即使对象的内容没发生改变,指针改变也是不允许的。

类型检测

typeofObject.prototype.toString()

typeof

  typeof返回一个表示数据类型的字符串,返回结果包括:numberbooleanstringsymbolobjectundefinedfunction等7种数据类型,但不能判断nullarray等。

1
2
3
4
5
6
7
8
9
10
typeof Symbol();    // symbol 有效
typeof ''; // string 有效
typeof 1; // number 有效
typeof true; //boolean 有效
typeof undefined; //undefined 有效
typeof new Function(); // function 有效
typeof null; //object 无效
typeof [] ; //object 无效
typeof new Date(); //object 无效
typeof new RegExp(); //object 无效

instanceof

  instanceof 主要的作用就是判断一个实例是否属于某种类型。是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是B的实例,则返回true,否则返回false。instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性,但它不能检测nullundefined

1
2
3
4
5
6
7
8
[] instanceof Array; //true
# 在数组的原型链上也能找到Object构造函数
[] instanceof Object;//true
{} instanceof Object;//true
new Date() instanceof Date;//true
new RegExp() instanceof RegExp//true
null instanceof Null//报错
undefined instanceof undefined//报错
1
2
3
let person = function () {}
let nicole = new person()
nicole instanceof person // true
instanceof 实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function new_instance_of(leftVaule, rightVaule) { 
// 取右表达式的 prototype 值
let rightProto = rightVaule.prototype;
// 取左表达式的__proto__值
leftVaule = leftVaule.__proto__;
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}

  其实 instanceof 主要的实现原理就是只要右边变量的prototype在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

constructor

  constructor作用和instanceof非常相似。但constructor检测 Objectinstanceof不一样,还可以处理基本数据类型的检测

1
2
3
let a = []
console.log(a.constructor);
// ƒ Array() { [native code] }

  不过函数的 constructor 是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的constructor给覆盖了,这样检测出来的结果就是不准确的。

1
2
3
4
5
6
7
8
9
10
//定义一个数组
const a = [];
//作死将constructor属性改成了别的
a.contrtuctor = Object;
console.log(a.constructor == Array);
//false (哭脸)
console.log(a.constructor == Object);
//true (哭脸)
console.log(a instanceof Array);
//true (instanceof火眼金睛)

  可以看出,constructor属性被修改之后,就无法用这个方法判断数组是数组了,除非你能保证不会发生constructor属性被改写的情况,否则用这种方法来判断数组也是不靠谱的。

Object.prototype.toString.call()

  toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]]。
这是一个内部属性,其格式为 [object xxx] ,其中 xxx 就是对象的类型。

1
2
3
4
5
6
7
8
9
10
var gettype=Object.prototype.toString;

gettype.call('aaaa') 输出 [object String]
gettype.call(2222) 输出 [object Number]
gettype.call(true) 输出 [object Boolean]
gettype.call(undefined) 输出 [object Undefined]
gettype.call(null) 输出 [object Null]
gettype.call({}) 输出 [object Object]
gettype.call([]) 输出 [object Array]
gettype.call(function(){}) 输出 [object Function]

用Array对象的isArray方法判断

  当参数为数组的时候,isArray方法返回true,当参数不为数组的时候,isArray方法返回false。

1
2
3
4
const a = [];
const b = {};
Array.isArray(a);//true
Array.isArray(b);//false

我试着在调用这个方法之前重写了Object.prototype.toString方法:

1
2
3
4
5
Object.prototype.toString = ()=>{
console.log('Hello Howard');
}
const a = [];
Array.isArray(a);//true

并不影响判断的结果。
我又试着修改了constructor对象:

1
2
3
4
const a = [];
const b = {};
a.constructor = b.constructor;
Array.isArray(a);//true

OK,还是不影响判断的结果。
  可见,它与instance运算符判断的方法以及Object.prototype.toString法并不相同,一些列的修改并没有影响到判断的结果。
  Array.isArray是ES5标准中增加的方法,部分比较老的浏览器可能会有兼容问题,所以为了增强健壮性,建议还是给Array.isArray方法进行判断,增强兼容性,重新封装的方法如下:

1
2
3
4
5
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}

封装可以分辨所有数据类型的方法
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
var typeName = {
'[object Function]': 'function',
'[object Boolean]': 'boolean - object',
'[object Number]': 'number - object',
'[object String]': 'string - object',
'[object Object]': 'object',
'[object RegExp]': 'regExp',
'[object Array]': 'array',
'[object Error]': 'error',
'[object Date]' : 'date'
};

//获取Object的toString方法,通过call调用
var toStringFn = Object.prototype.toString;

function checkType(obj){
if( obj == null ){
//js自带的的String方法,用于检测null和undefined
return String( obj );
}
//safari5及之前版本,Chrome7, typeof RegExp返回的是function
return typeof obj === 'object' ||
typeof obj === 'function' ?
typeName[toStringFn.call(obj)]:typeof obj;
}
// 调用函数
checkType("123")