JavaScript的数据类型主要分为两大类:
1. 基本数据类型:
有如下6种:
boolean
、number
、string
、undefined
、null
、symbol
(es6
新增)
- 基本数据类型存储的是保存在变量中的实际的值。
- 变量是对值的一个具名引用,所以变量本身是没有类型的,只有存储的值有类型。
2. 引用类型:
有如下1种 :
对象object
,对象又可以分为三个子类型:object
、array
、function
- 引用类型存储的是保存在一个地址中的值。
- 包含引用类型值的变量实际上存储的不是值本身,而是一个指向值的指针。
var a = [];
var b = a;
b.push(1);
a // [1]
b // [1]
从上面代码可以看出,复制一个引用类型的值,复制的其实是变量的指针指向,所以两个变量指向的都是同一个对象。
Javascript有三种方法判断一个值属于什么类型:
typeof
instanceof
Object.prototype.toString
1. typeof
1.1 number string boolean
console.log(typeof 10); // number
console.log(typeof 'abc'); // string
console.log(typeof true); // boolean
数值、字符串、布尔值分别返回number
、string
、boolean
// 字面量式声明变量
var v1 = 123;
var v2 = 'abc';
var v3 = true;
console.log(typeof v1); // number
console.log(typeof v2); // string
console.log(typeof v3); // boolean
// 函数式声明变量
var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);
console.log(typeof Number); // function
console.log(typeof String); // function
console.log(typeof Boolean); // function
console.log(typeof v1); // object
console.log(typeof v2); // object
console.log(typeof v3); // object
上面代码中,两种变量声明的方式是等价的。
- 函数式声明中返回的值类型是
object
- 函数式声明中使用的函数是
JS
自带的原生函数,使用原生函数可以把基本数据类型的值包装成对象 - 作为普通函数使用时(不带有
new
),可以将任意类型的值,转为原始类型的值,如Number('123')
,是把123
转换为数值类型。
'abc'.length // 3
abc
是一个基本类型的字符串,它不是一个对象,是不能调用length属性的。上面的代码中为什么可以调用,是因为JS
会调用new String('abc')
的方式。自动将abc
字符串转换为包装对象实例,在这个对象上调用length
属性,调用结束后,这个临时对象就会自动销毁。
var v1 = 'abc';
v1.length // 3
v1.name = 'emoji';
v1.name // undefined
// 等同于
var v1Obj = new String(v1); // String { 0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc" }
v1Obj.length // 3
v1Obj.name = 'emoji';
自动转换生成的包装对象是只读不能修改的,且在创建并调用属性结束就会自动销毁,下一次调用字符串的属性时,实际调用的是一个新生成的对象,而不是上一次调用时生成的那个对象,所以字符串无法添加新属性也无法取到赋值在上一个对象的属性,如果要为字符串添加属性,只能在它的原型对象上定义String.prototype
var v1 = 'abc';
v1.length // 3
v1.name = 'emoji';
v1.name // undefined
包装对象可以使用Object
对象提供的原生方法
valueOf
方法返回包装对象实例对应的原始类型的值。
new Number(123).valueOf() // 123
new String('abc').valueOf() // "abc"
new Boolean(true).valueOf() // true
toString()方法返回对应的字符串形式
new Number(123).toString() // "123"
new String('abc').toString() // "abc"
new Boolean(true).toString() // "true"
1.2 undefined
console.log(typeof undefined); // undefined
上面代码中undefined
返回undefined
console.log(x); // ReferenceError: x is not undefined
console.log(typeof x); // undefined
var x;
console.log(x); // undefined
console.log(typeof x); // undefined
- 直接访问没有声明的变量会报
ReferenceError: x is not undefined
的错误 - 变量
x
声明了但是没有赋值,JS
会默认赋初始值为undefined
不管变量有没有声明,typeof
判断都会返回undefined
,但是undefined
和not defined
是不同的: -
not defined
表示:变量未声明 -
undefined
表示:变量声明了但未赋值
我们可以用typeof
来检测一个变量是否存在,换句话说就是一个变量有没有被声明:
// 错误的写法
if (v) {
// ...
}
// ReferenceError: v is not defined
// 正确的写法
if (typeof v === "undefined") {
// ...
}
1.3 null
console.log(typeof null); // object
null
的类型是object
,但null
是基本数据类型。
《你不知道的Javascript》译者:
原理是这样的,不同的对象在底层都表示为二进制,在Javascript
中二进制前三位都为0
的话会被判断为Object
类型,null
的二进制表示全为0
,自然前三位也是0
,所以执行typeof
时会返回object
。
null和undefined
null
和undefined
都可以表示“没有”,含义非常接近。==
运算符判断两者相等
null == undefined
不过它们是有区别的null
表示空值,即该
Number(null); // 0
Number(undefined); // NaN
1.4 object
console.log(typeof [1, 'a', true]); // object
console.log(typeof {a:10, b:20}); // object
console.log(typeof new Number(10)); // object
数组、对象都返回object
1.4 function
console.log(typeof function(){}); // function
函数返回function
为什么typeof function
返回的是function
而不是object
?
function
也是一个对象,但是function
对象与普通对象相比,它的内部有一个[[Call]]
方法,用来表示这个对象是可调用的,typeof
在判断对象时,如果内部实现了[[Call]]
方法,就返回function
。函数是一个可调用对象。
1.5 object
console.log(typeof [1, 'a', true]); // object
console.log(typeof {a:10, b:20}); // object
console.log(typeof new Number(10)); // object
数组、对象都返回object
2. instacneof
对于值类型,通过 typeof
判断string/number/boolean
都很清楚,但是typeof
在判断到引用类型的时候,返回值只有object/function
,你不知道它到底是一个object
对象,还是数组,还是new Number
等等。
这个时候就需要用到instanceof
console.log(function(){} instanceof Function); // true
console.log([1, 'a', true] instanceof Array); // true
console.log({a:10, b:20} instanceof Object); // true
console.log(new Number(10) instanceof Object); // true
内部实现:
// L即fn1 R即Fn
function instance_of(L, R) {
// O为Fn.prototype
var R = R.prototype;
// L为fn1.__proto__
L = L.__proto__;
//执行循环
while (true) {
// 不通过
if (L === null)
return false;
// 判断 L.__proto__ === R.prototype
if (L === R)
return true;
L = L.__proto__;
}
}
Instanceof
的判断原则是:沿着A
的__proto__
这条线来找,同时沿着B
的prototype
这条线来找,如果两条线能找到同一个引用,即同一个对象,那么就返回true
。如果找到终点还未重合,则返回false
。
具体关于原型(链)相关的知识,可以详见另一篇JS基础知识 - 原型及原型链
3. Object.prototype.toString
- 为什么可以使用对象原型上的方法来判断数据类型呢?
对象类型都包含一个内部属性[[class]]
,这个属性不能直接访问,一般通过Object.prototype.toString
来查看,会返回
Object.prototype.toString.call([1,2,3]); // [object Array]
基本类型也可以通过这个方法来查看,
涉及包装对象的概念:
基本类型值是没有对象上的属性和方法的,js
内部会对基本数据类型进行包装,把基本数据类型使用原生函数封装成一个对象
基本类型值被各自的封装对象自动包装,包装成了一个对象,所以它们但内部属性有[[class]]
toString
方法的作用是返回一个对象的字符串形式,默认情况下返回类型字符串。toString
是Object.prototype
上定义的方法,所有Object
的实例对象都继承了这些方法。
var fn = new Object();
fn.toString() // "[object Object]"
var obj = {a:1};
obj.toString() // "[object Object]"
上面代码,一个对象调用toString
方法,会返回字符串[object Object]
,该字符串说明对象的类型。
字符串[object Object]
本身没有太大的用处,但是通过自定义toString
方法,可以让对象在自动类型转换时,得到想要的字符串形式。
var fn = new Object();
fn.toString = function () {
return 'hello';
};
fn + ' ' + 'world' // "hello world"
上面代码,当对象用于字符串加法时,会自动调用toString
方法。由于自定义了toString
方法,所以返回字符串hello world
。
数组、字符串、函数、Date
对象都分别定义了自定义的toString
方法,覆盖了Object.prototype.toString
方法。
[1, 2, 3].toString() // "1,2,3"
'123'.toString() // "123"
(function () {
return 123;
}).toString()
// "function () {
// return 123;
// }"
(new Date()).toString()
// "Tue May 10 2016 09:11:31 GMT+0800 (CST)"
上面代码中,数组、字符串、函数、Date
对象调用toString
方法,并不会返回[object Object]
,因为它们都自定义了toString
方法,覆盖原始方法。
为什么字符串、数字这种基本类型可以直接调用
toString
方法?
涉及包装对象的知识,在另一篇中说明
用来判断一个值的类型时:
var obj = {};
obj.toString() // "[object Object]"
上面代码调用空对象的toString
方法,结果返回一个字符串object Object
,其中第二个Object
表示该值的构造函数。这是一个十分有用的判断数据类型的方法。
由于实例对象可能会自定义toString
方法,覆盖掉Object.prototype.toString
方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString
方法。通过函数的call
方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。
Object.prototype.toString.call(value)
上面代码表示对value
这个值调Object.prototype.toString
方法。
不同数据类型的Object.prototype.toString
方法返回值如下。
数值:返回[object Number]。
字符串:返回[object String]。
布尔值:返回[object Boolean]。
undefined:返回[object Undefined]。
null:返回[object Null]。
数组:返回[object Array]。
arguments 对象:返回[object Arguments]。
函数:返回[object Function]。
Error 对象:返回[object Error]。
Date 对象:返回[object Date]。
RegExp 对象:返回[object RegExp]。
其他对象:返回[object Object]。
这就是说,Object.prototype.toString
可以看出一个值到底是什么类型。
console.log(Object.prototype.toString.call(undefined)); // [object Undefined]
console.log(Object.prototype.toString.call(0)); // [object Number]
console.log(Object.prototype.toString.call('abc')); // [object String]
console.log(Object.prototype.toString.call(true)); // [object Boolean]
console.log(Object.prototype.toString.call(function(){})); // [object Function]
console.log(Object.prototype.toString.call([1, 'a', true])); // [object Array]
console.log(Object.prototype.toString.call({a:10, b:20})); // [object Object]
console.log(Object.prototype.toString.call(null)); // [object Null]
console.log(Object.prototype.toString.call(new Number(10))); // [object Number]