# JavaScript基础数据类型
# 1. 值类型与引用类型
# 1.1 数据类型分类
- 基本数据类型:
String Boolean Number Symbol(ES6新增) Undefined Null - 复杂数据类型:
Object - 基本数据类型中有两个为特殊数据类型:
null undefined - 常见内置对象:
Boolean Number String Array Math Date RegExp Function...
Symbol(ECMAScript 6 新定义数据类型)暂时不涉及
# 1.2 数据访问
- 基本数据类型在栈空间存储,访问方式是按值访问。基本类型(值类型)在调用函数的时候,传递的是值。
- 引用类型的对象在堆中存储,地址在栈中存储。引用类型,在函数调用的时候,传递的是地址(引用)。
具体如下图所示:

function addTen (num) {
num += 10;
return num;
}
const count = 20;
const result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30
function setName (obj) {
obj.name = 'liam';
// eslint-disable-next-line no-new-object
obj = new Object();
obj.name = 'tom';
}
// eslint-disable-next-line no-new-object
const person = new Object();
person.name = 'liam1';
setName(person);
console.log(person.name); // "liam"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2. 数据类型判断
# 2.1 typeof
typeof操作符是检测基本类型的最佳工具
| 返回结果 | 含义 |
|---|---|
| undefined | 未定义 |
| boolean | 布尔值 |
| string | 字符串 |
| number | 数值 |
| object | 对象或null |
| function | 函数 |
对未初始化的变量执行typeof操作符会返回undefined值,而对未声明的变量执行typeof操作符同样也会返回undefined值
let test; // 这个变量声明之后默认取得了 undefined 值
// var age // 下面这个变量并没有声明
console.log(typeof test); // "undefined"
console.log(typeof age); // "undefined"
2
3
4
typeof原理:不同的对象在底层都表示为二进制,在Javascript中二进制前(低)三位存储其类型信息
- 000: 对象
- 010: 浮点数
- 100:字符串
- 110:布尔
- 1:整数
null:null的二进制表示全为0,自然前三位也是0,所以执行typeof时会返回"object"
undefined:用 −2^30 整数来表示(一个超出整型范围的数)
ES6 的 typeof 是如何对待函数和对象类型的:
- 如果一个对象(Object)没有实现 [[Call]] 内部方法,那么它就返回 object
- 如果一个对象(Object)实现了 [[Call]] 内部方法,那么它就返回 function
一个对象如果支持了内部的 [[Call]] 方法,那么它就可以被调用,就变成了函数,所以叫做函数对象
注意首字母为小写字母 例如: typeof 'abc'=== 'string'
# 2.2 Instanceof
instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上
所以instanceof可以用于判断typeof返回object的引用类型存储值的类型
JavaScript 中一切皆对象,如下代码第3行返回false,其实问题的关键在于typeof str返回string并不是object
const str = 'This is a simple string';
const newStr = new String('String created with constructor');
str instanceof String; // 返回 false, 检查原型链会找到 undefined
newStr instanceof String; // 返回 true
2
3
4
这样 Boolean Number是一样的逻辑。还剩下两种基本类型:Null Undefined
undefined 当我们对变量只声明没有初始化时,输出为 undefined,typeof undefined 返回的是 undefined 也不是 object 类型,所以 undefined 并不是任何对象的实例。
null 表示的是空对象,虽然 typeof null 是 object,但是 null 和 undefined 一样并没有任何属性和方法,在 instanceof 定义中也有判断,如果类型不是 object(这个类型判断并不是跟 typeof 返回值一样),就返回 false。

使用 instanceof 就是确定原型和实例之间的关系
const Fn = function () {};
const f1 = new Fn();
console.log(f1 instanceof Fn); // true
console.log(f1 instanceof Function); // false
console.log(f1 instanceof Object); // true
2
3
4
5
instanceof 还可以在继承关系中用来判断一个实例是否属于它的父类型
// 判断 f2 是否是 Fn2 类的实例 , 并且是否是其父类型的实例
function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
console.log(f instanceof Son); // true
console.log(f instanceof Father); // true
2
3
4
5
6
7
# 2.3 instanceof 运算符代码
function instanceOfNew(L, R) { // L 表示左表达式,R 表示右表达式
const O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) {
if (L === null) return false;
if (O === L) { // 这里重点:当 O 严格等于 L 时,返回 true
return true;
}
L = L.__proto__;
}
}
2
3
4
5
6
7
8
9
10
11
# 2.4 constructor
constructor 属性返回对创建此对象的数组函数的引用。
object.constructor 其中object 是一个对象或函数的名称。
constructor 属性是每个具有原型的对象的原型成员。 这包括除 Global 和 Math 对象之外的所有内部 JavaScript 对象。
'abc'.constructor === String; // true
(123).constructor === Number; // true
true.constructor === Boolean; // true
[1, 2].constructor === Array; // true
({ name: 'liam' }).constructor === Object; // true
(() => {}).constructor === Function; // true
new Date().constructor === Date; // true
(/[a-z]/).constructor === RegExp; // true
2
3
4
5
6
7
8

下面是使用new创建的基础数据类型
const str = new String('abc');
str.constructor === String; // true
2
在继承关系中用来判断会出现错误
function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
console.log(f.constructor === Son); // false
console.log(f.constructor === Father); // true
2
3
4
5
6
解决construtor的问题通常是让对象的constructor手动指向自己:
function Father () {}
function Son () {}
Son.prototype = new Father(); // JavaScript 原型继承
const f = new Son();
f.constructor = Son; // 将自己的类赋值给对象的constructor属性
console.log(f.constructor === Son); // true
console.log(f.constructor === Father); // 基类不会报true了
2
3
4
5
6
7
# 2.5 Object.prototype.toString.call(obj)
Object.prototype.toString.call() 最准确最常用的方式。首先获取Object原型上的toString方法,让方法执行,让toString方法中的this指向第一个参数的值。
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(123); // "[object Number]"
Object.prototype.toString.call('abc'); // "[object String]"
Object.prototype.toString.call([]); // "[object Array]"
// eslint-disable-next-line no-new-func
Object.prototype.toString.call(new Function()); // "[object Function]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(new RegExp()); // "[object RegExp]"
Object.prototype.toString.call(new Error()); // "[object Error]"
Object.prototype.toString.call(document); // "[object HTMLDocument]"
Object.prototype.toString.call(window); // "[object Window]" window是全局对象global的引用
// 使用new创建的基础数据类型对象
const str = new String();
Object.prototype.toString.call(str); // "[object String]"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 3. 数据类型转换
# 3.1 基础介绍
js在一些操作符的作用下会进行隐式转换,涉及隐式转换最多的两个运算符 + 和 ==,下面来介绍一下。
+可能是字符串拼接操作,也可能是数字相加==不同于===存在数据类型转换- * % / > <等其他操作符只会针对number类型,所以其都是把其他类型转换成数字类型&& || !与或非操作符是进行布尔运算的,所以会把他们转换成布尔值
可以看一下下面的例子,下文中会依次介绍
'123' - 1; // 122
'123a' - 1; // NaN
`123${ 1}` // 1231
`${NaN }123a`; // NaN123a
Number(null); // 0
null == 0; // false
null >= 0; // true
2 == true; // false
'' == 0; // true
2
3
4
5
6
7
8
9
隐式转换中主要涉及到三种转换:
| 含义 | 函数名称 |
|---|---|
| 将值转为原始值 | ToPrimitive() |
| 将值转为数字 | ToNumber() |
| 将值转为字符串 | ToString() |
| 将值转为布尔值 | ToBoolean() |
# 3.2 类型转换涉及函数
# 3.2.1 通过ToNumber将值转换为数字
| 参数类型 | 转换结果 | 举例 |
|---|---|---|
| Undefined | NaN | undefined +0 // NaN |
| Null | +0 | null >=0 // true |
| Boolean | true转换1,false转换为+0 | true + 1 // 2 |
| String | 将字符串中内容转化为数字,转化失败返回NaN | '123'-1 // 122 |
| Object | 先用ToPrimitive(obj, Number)转换为原始值,再用ToNumber转换为数字 | [2]-1 // 1 |
''空字符串转换为数字为 0
# 3.2.2 通过ToString将值转换为字符串
| 参数类型 | 转换结果 | 举例 |
|---|---|---|
| Undefined | 'undefined' | undefined + '123' // undefined123 |
| Null | 'null' | null + '123' // null123 |
| Boolean | 转换为'true' 或 'false' | 'false' + '123' // false123 |
| Number | 数字转换字符串 | -17.5 + '123' // -17.5123 |
| Object | 先用ToPrimitive(obj, String)转换为原始值,再用ToString转换为数字 | [key='name',1]+'123a' // name,1123a |
# 3.2.3 通过ToBoolean将值转换为布尔值
| 参数类型 | 转换结果 | 举例 |
|---|---|---|
| Undefined | false | !!undefined // false |
| Null | false | !!null // false |
| Number | 0、NaN为false,其他为true | !!0 // false |
| String | 空字符串(长度为0)为false,其他为true | !!'' // false |
| Object | true | !![] !!{} // true |
除了这六个值(undefined、null 、false、0、NaN、'')转换为false,其他都为true
# 3.2.4 通过ToPrimitive转换值
调用 ToPrimitive 函数将对象转化为原始类型。先看一下下面的内容,是不是头大,没关系,我们一点点讲解
[2, 3] - 1 // NaN
[] + [] // ""
{} - 1 // -1
({}) - 1 // NaN
{} + 1 // 1
1 + {} // 1[object Object]
[] + {} // [object Object]
2
3
4
5
6
7
ToPrimitive(input, hint)
| 参数名称 | 参数含义 |
|---|---|
| input | 将要转换的值 |
| hint | 转换类型,有Number/String/default三个分类 |
# 3.2.4.1 如果hint被标记为Number
- input为原始值,直接返回input
- 否则,调用input对象的valueOf()方法,如果结果是原始类型,则返回这个原始值
- 否则,调用input对象的toString()方法,如果结果是原始类型,则返回这个原始值
- 否则,抛出TypeError异常
# 3.2.4.2 如果hint被标记为String
- input为原始值,直接返回input
- 否则,调用input对象的toString()方法,如果结果是原始类型,则返回这个原始值
- 否则,调用input对象的valueOf()方法,如果结果是原始类型,则返回这个原始值
- 否则,抛出TypeError异常
# 3.2.4.3 如果没有设置hint这个参数时,会按照如下来自动设置
- input对象为Date类型,则hint被设置为String
- 否则,hint被设置为Number
# 3.2.4.4 valueOf方法解析
- Number、Boolean、String这三种构造函数生成的基础值的对象形式,通过valueOf转换后会变成相应的原始值。
- Date这种特殊的对象,其原型Date.prototype上内置的valueOf函数将日期转换为日期的毫秒的形式的数值。
- 除此之外返回的都为this,即对象本身:(有问题欢迎告知)
const num = new Number('123');
num.valueOf(); // 123
const time = new Date();
time.valueOf(); // 1545879601992
// eslint-disable-next-line no-array-constructor
const arr = new Array();
arr.valueOf() === arr; // true
2
3
4
5
6
7
8
9
# 3.2.4.5 toString方法解析
- Number、Boolean、String、Array、Date、RegExp、Function这几种构造函数生成的对象,通过toString转换后会变成相应的字符串的形式,因为这些构造函数上封装了自己的toString方法。
- 除以上对象及其实例化对象之外,其他对象返回的都是该对象的类型,(有问题欢迎告知),都是继承的Object.prototype.toString方法。
// eslint-disable-next-line no-prototype-builtins
Date.prototype.hasOwnProperty('toString'); // true
// eslint-disable-next-line no-prototype-builtins
RegExp.prototype.hasOwnProperty('toString'); // true
// eslint-disable-next-line no-prototype-builtins
Function.prototype.hasOwnProperty('toString'); // true
const arr = new Array([1, 2, 3]);
arr.toString(); // '1,2,3'
const time = new Date();
time.toString(); // "Thu Dec 27 2018 11:06:31 GMT+0800 (中国标准时间)"
const func = function () {
return '123';
};
func.toString(); // "function () { return '123'}"
// eslint-disable-next-line no-new-object
const obj = new Object({});
obj.toString(); // "[object Object]"
Math.toString(); // "[object Math]"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
看一个经典的面试题:a == 1 && a == 2 && a == 3 什么情况下为 true ?
const a = {
i: 1,
toString () { // 或 valueOf
a.i += 1;
return a.i;
},
};
// eslint-disable-next-line eqeqeq
console.log(a == 1 && a == 2 && a == 3); // true
2
3
4
5
6
7
8
9
10
a.i 最开始为1,执行 a == 1时,会调用 toString() 方法,得到 true 后,a.i 自增为 2,然后继续执行 a.i == 2,如此依次执行。 如果已经掌握了本文,那么不难知道,这里的 toString 也可以是 valueOf 或 ES6 中的 [Symbol.toPrimitive] 。
# 3.3 类型转换与操作符
| 操作符 | 名称 | 转换类型 |
|---|---|---|
| ++/-- | 递增/递减 | Number |
| -/+ | 一元加/减 | Number |
| -/*// | 减法/乘法/除法 | Number |
| ! | 逻辑非 | Boolean |
# 3.3.1 expr1 + expr2加法操作符
- 如果expr1和expr2都是数字,执行常规的加法计算
- 如果expr1和expr2不全是数字,则把对象、数值或布尔值等,调用它们的 toString()方法取得相应的字符串值。
- 对于 undefined 和 null,则分别调用 String()函数并取得字符串"undefined"和"null",再将两个字符串拼接起来。
# 3.3.2 expr1 && expr2
- 如果expr1 能转换成false则返回expr1,否则返回expr2。
- 因此,与布尔值一起使用时,如果两个操作数都为true时&&返回true,否则返回false。
# 3.3.3 expr1 || expr2
- 如果expr1能转换成true则返回expr1,否则返回expr2。
- 因此,与布尔值一起使用时,如果任意一个操作数为true时,返回true。
# 3.3.4 小于(<)、大于(>)、小于等于(<=)和大于等于(>=)
- 操作符都返回一个布尔值
- 如果两个操作数都是数值,则执行数值比较。
- 如果两个操作数都是字符串,则比较两个字符串对应的字符编码值(可以通过字符串的 charCodeAt() 函数获取字符编码值)。
- 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。
- 如果一个操作数是对象,则调用这个对象的 valueOf() 方法,用得到的结果按照前面的规则执行比较。如果对象没有valueOf()方法,则调用 toString()方法,并用得到的结果根据前面的规则执行比较。
- 如果一个操作数是布尔值,则先将其转换为数值,然后再执行比较。
const result1 = 5 - true; // 4,因为true被转换成了1
const result2 = NaN - 1; // NaN
const result3 = '23' < '3'; // true
const result4 = '23' < 3; // false
const result5 = 'a' < 3; // false
// eslint-disable-next-line use-isnan
const result6 = NaN < 3; // false
// eslint-disable-next-line use-isnan
const result7 = NaN >= 3; // false
// eslint-disable-next-line no-constant-binary-expression
console.log(0 || 'NaN'); // 返回 NaN
console.log(NaN || ''); // 返回 '' 空字符串
// eslint-disable-next-line no-constant-binary-expression
console.log(null || 'undefined'); // 返回 undefined
const obj = {};
// eslint-disable-next-line no-constant-binary-expression
console.log(0 && obj); // 返回 0
console.log(obj && 0); // 返回 0
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.3.5 expr1 == expr2
- 如果一个值是null, 另一个是undefined,则它们相等
- 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较
- 如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较
- 如果一个值是对象,另一个值是数字或字符串,则将对象转换为原始值,然后再进行比较。对象通过toString()方法或者valueOf()方法转换为原始值,JavaScript语言核心的内置类先尝试使用valueOf(),再尝试使用toString(),除了日期类,日期类只能使用toString()转换,那些不是JavaScript语言核心中的对象则通过各自的实现中定义的方法转换为原始值
# 5. 面试题目
const x = {};
const y = { key: 'y' };
const z = { key: 'z' };
x[y] = 'fatfish';
x[z] = 'medium';
console.log(x[y]);
// 使用一个对象作为属性键,最终会转换为[object Object]
x[y] = 'fatfish'; // x => { [object Object]: "fatfish" }
x[z] = 'medium'; // x => { [object Object]: "medium" }
console.log(x[y]); // medium
2
3
4
5
6
7
8
9
10
11
const arr = [1, 30, 4, 21, 100000];
console.log(arr.sort());
// 指定定义排序顺序的函数。如果省略,数组元素将转换为字符串,然后根据每个字符的 Unicode 值进行排序。
2
3
4
const fn = () => {
// eslint-disable-next-line no-multi-assign
let x = y = 1000;
x += 1;
return x;
};
fn();
console.log(typeof x);
console.log(typeof y);
// let x = y = 1000
// 这段代码等同于如下代码;
const x = 1000;
// 在这里,我们其实是声明了一个全局变量 y
y === 1000;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5. 参考文章
【JavaScript:Object.prototype.toString方法的原理】 (opens new window)
【JavaScript instanceof 操作符】 (opens new window)
你所忽略的js隐式转换 (opens new window)
理清JS中等于(==)和全等(===)那些纠缠不清的关系 (opens new window)
JavaScript高级程序设计(第3版)