一、数据类型
JavaScript定义了7种基本数据类型:
- Symbol (ES6)
- String
- Number
- Null
- Boolean
- Undefined
- Object
JavaScript是一种弱类型脚本语言
,即定义变量时不需要声明变量的类型,在程序运作过程中会自动判断变量的类型,定义变量的方法:
- var
- let (ES6)
- const (ES6)
不添加关键词var/let/const也能定义变量,此时会创建一个全局变量,且变量不会提升
二、值类型和引用类型
除了以上类型的划分,变量还分为值类型和引用类型,在传递过程中分为值传递和引用传递。值传递只会传递变量的值,引用传递会传递变量的存储地址(指针)
var a = 1,
b = {name: 1};
var c = a; //值传递,a会赋值给c
console.log(c); // 1
c = 2;
console.log(a); // 1 值传递相当于内存中复制了一份,c和a没有关系
var d = b;
d.name = 2;
console.log(b.name); // 2 引用传递传递的是地址,所以b和d指向同一内存地址
三、判断数据类型的方法
我们很多时候需要对数据的类型进行判断,然后进行相关的操作,判断数据类型常见有以下几种方法:
- typeof
- instanceof / constructor
- Object.prototype.toString.call()
四、typeof
调用typeof方法时会返回以下几种值之一:
- symbol
- string
- number
- boolean
- undefined
- object
- function
typeof Symbol(); // symbol
typeof 'abc'; // string
typeof 123; // number
typeof NaN; // number
typeof true; // boolean
typeof undefined; // undefined
typeof {a:1}; // object
typeof [1,2,3]; // object
typeof function a(){}; // function
typeof null; // object
// typeof检测一个未声明的变量时会返回undefined
typeof undeclare_variable; // undefined
- typeof null => object 这是typeof 的一个bug,null并不是引用类型
Javascript语言第一版只设计了五种数据类型(对象、整数、浮点数、字符串、布尔值),没考虑null,只把它当作object的一种特殊值,后来null独立出来,作为一种单独的数据类型,为了兼容以前的代码就没有改变null的类型- typeof [1,2,3] => object 除了函数返回的是function,其他引用类型返回的都是object,所以
typeof无法区分array和object
五、instanceof / constructor
instanceof和constructor都是用于实例和构造函数的对应,可以弥补typeof无法区分数组和对象的问题
但是,instanceof只能用于非对象类型数据的判断
[1,2,3] instanceof Array; // true
var a = {name: 1};
a instanceof Object; // true
1 instanceof Number; // false
constructor可以用于任何类型的判断
var num = 1;
num.constructor === Number; // true
var arr = [1, 2, 3];
arr.constructor === Array; // true
但是,这两种方法都不能用于跨iframe的情况
,例如
//pageOne中
...
<div>父页面</div>
<iframe src="pageB.html" frameborder="0"></iframe>
<script>
//这里定义了一个数组a
var a = [1, 2, 3];
console.log( a.constructor === Array ); // true
</script>
...
//pageTwo中
...
<div>子页面</div>
<script>
//子页面获取父页面中的值
var c = top.a;
console.log( c.constructor === Array ); // false
</script>
...
这是因为浏览器会把不同页面中的Array做区分
,即pageOne和pageTwo的Array是不同的
两个构造器,所以跨iframe的变量无法通过这两种方法来判断
六、Object.prototype.toString.call()
使用typeof无法区分null、array、date
instanceof无法运用在值类型区分
没事,我们还有一种方法,Object.prototype.toString.call()
Object.prototype.toString.call()是一种比较常用,也是比较有效的方法
var a = {name: 1};
Object.prototype.toString.call(a); // [object Object]
var b = new Date();
Object.prototype.toString.call(b); // [object Date]
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(undefined); // [object Undefined]
function a(){
console.log(Object.prototype.toString.call(arguments)); // [object Arguments]
}
通过这种方式我们就可以解决常见问题中的类型判断
那么,为什么Object.prototype.toString.call()可以区分数据类型?
七、探索为什么Object.prototype.toString.call()可以区分数据类型
我们先来了解一下在调用Object.prototype.toString.call()的时候发生了什么
1.在ECMA5中,调用Object.prototype.toString会执行以下步骤:
(1).如果值为undefined
,则返回"[object Undefiend]"
(2).如果值为null
,则返回"[object Null]"
(3).将传递的值调用ToObject
方法,并赋给O作为调用后的结果
(4).将O的内部属性[[Class]]
的值赋给class
(5).结果返回字符串,该字符串由三个字符串"[object ", class, and "]"
拼接而成
根据上面的描述我们可以看出来,主要通过对象的内置属性
[[Class]]
来判断
2.内置属性[[Class]]
ECMA规范中这么介绍[[Class]]
翻译:本规范针对每种内置对象定义了[[Class]]内部属性的值。对象的[[Class]]内部属性的值可以是除了。。。之外的任何字符串值。[[Class]]内部属性的值用于内部区分不同类型的对象。 请注意,除了通过Object.prototype.toString外,本规范没有提供任何方法让程序访问该值。
我们再来看规范中,在创建某种类型变量的时候,会对对象内置属性[[Class]]
进行赋值
-
创建数组时:
-
创建函数时:
-
创建字符串时:
3.ES6中不再使用[[Class]]内置属性进行判断
最新规范中不再使用[[Class]]内置属性进行判断,在调用Object.prototype.toString时执行了以下步骤:
(这里不详细讲,有兴趣的可以去了解下)
综上,大家应该了解了为什么Object.prototype.toString.call()可以用来区分数据类型
一些问题
1.null和undefined不是对象,为什么Object.prototype.toString.call()返回的值要采用[object *]的格式
这是ES5.1规范中的修正。 在ES5之前,将null或undefined传递给toString总是导致全局对象被传递。 ES5中严格模式相关的更改会导致null和undefined被传递而不进行修改。 按照ES5的规定,将null或undefined传递给Object.prototype.toString会导致TypeError异常。 这个例外破坏了一些现有的代码,所以ES5.1修复这个规范,以防止这种情况发生。
那么,为什么toString返回null和undefined? 事实证明,很多现有的代码也期望Object.prototype.toString始终返回形式为“[object *]”的字符串。 因此,ES5.1规范中决定将传入null和undefined的值“[object Null]”和“[object Undefined]”。
2.Object.prototype.toString(‘foo’)和Object.prototype.toString.call(‘foo’)的区别
前者传入的参数实际是Object.prototype,而Object.prototype是个对象,所以始终返回’[object Object]’,其中的’foo’会被忽略;而后者通过call,硬绑定到传入的’foo’;所以千万不要把call忘了