数据类型的判断与转换

89 阅读7分钟

内存结构

程序运行时,计算机内存会为程序开辟出一块专用内存,用于存储数据。在JavaScript中,变量名和变量值都是数据,但是它们存储的位置是不同的,变量名存储在内存的一个空间中,变量值是存储在另一个空间中。

每一个值都有一个内存地址,变量名实际上保存的就是值的内存地址,这样就建立了名字和值的联系。

值分为原始类型和对象类型,内存分为栈内存与堆内存。

  • 栈内存用于保存变量名,内存地址和原始类型。
  • 堆内存用于保存对象类型。

内存中不会创建重复的原始类型,可以理解为原始类型是不可变的。

let a = 'HelloWorld';
let b = 'HelloWorld';

栈内存.png

但是内存中可以创建重复的对象类型,并且它们的内存地址不同。

  • 给不同变量赋值同样的原始类型,它们是相等的,它们保存的是同一个内存地址。
  • 给不同变量赋值同样的对象类型,它们是不相等的,因为它们比较的是内存地址。
let a = 1;
let b = 1;
console.log(a === b); // true
let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];
console.log(arr1 === arr2); // false

堆内存.png

  • 变量arr1赋值给变量arr2,实际上是把arr1保存的数组[1, 2, 3, 4]的内存地址赋给arr2。它们俩指向同一个地址的数组,所以arr1修改对arr2是有影响的。
var arr1 = [1, 2, 3, 4];
var arr2 = arr1;
arr1.push(5);
// arr1和arr2的地址是同一个
console.log(arr1 + '|' + arr2); // 1,2,3,4,5|1,2,3,4,5
  • 重新赋值的过程是指向了新的内存地址。
var arr1 = [1, 2, 3, 4];
var arr2 = arr1;
arr1 = [1, 2];
console.log(arr1 + '|' + arr2);
// 1,2|1,2,3,4,5

类型判断

typeof

typeof(data)方法 返回一个字符串用于表示参数的数据类型,但是对于引用类型它只能区分是函数还是其它引用类型,比如以下代码中的对于数组的返回结果就是object

console.log(typeof(123)); // "number"
console.log(typeof("hello")); // "string"
console.log(typeof(true)); // "boolean"
console.log(typeof(undefined)); // "undefined"
console.log(typeof(null)); // "object"
console.log(typeof({})); // "object"
console.log(typeof([])); // "object"
console.log(typeof(function(){})); // "function"
console.log(typeof(null)); // "object"
console.log(typeof(NaN)); // "number"
console.log(typeof(document.all)); // "undefined"

注意null是一个object类型,它最早是空对象的指针,目前来说是一个历史遗留问题。

console.log(typeof(null)); //object

如果一个变量a未定义,使用typeof()返回的是undefined,注意这个undefined是字符串格式的。

console.log(typeof(a)); // undefined
console.log(typeof(typeof(a))); // string

constructor

constructor 指向创建该实例对象的构造函数。

var arr = [];
console.log(arr.constructor); // ƒ Array() { [native code] }

instanceof

instanceOf()方法 判断一个对象是否是右边的实例。

function Car(){}
var car = new Car();

console.log(car instanceof Car); // true

并且只要该对象能通过原型链访问到右边,都会返回true。

  • 第一条语句打印true,因为Car构造函数是由Function构造函数创建的实例。
  • 第二条语句打印fasle,因为car对象和Function之间并不存在原型链上的联系。
  • 第三条语句打印true,因为car对象可以找到Car构造函数的原型,然后接着找到Object
function Car(){}
var car = new Car();

console.log(Car instanceof Function); // true
console.log(car instanceof Function); // false
console.log(car instanceof Object); // true

Object.prototype.toString

toString()方法 不接收参数,返回表示调用它的对象的值的字符串。

  • 注意的是它返回的字符串格式为[object 对象类型]
var obj = {name: 'Li'};
console.log(obj.toString()); // [object Object]

所以虽然默认的toString()方法不会显示太多信息,但是可以用于判断数据的类型,所以可以结合call使用。

对于 Object.prototype.toString.call(arg),若参数arg为 null 或 undefined,直接返回结果。

console.log(Object.prototype.toString.call(undefined)) // "[object Udefined]"
console.log(Object.prototype.toString.call(null)) // "[object Null]"

对于其他数据类型,如果是原始数据类型,那么首先是进行包装类,如果是引用对象类型则直接使用。

console.log(Object.prototype.toString.call(123)) // "[object Number]"
console.log(Object.prototype.toString.call('HelloWorld')) // "[object String]"
console.log(Object.prototype.toString.call(true)) // "[object Boolean]"
console.log(Object.prototype.toString.call({name: 'Li'})) // "[object Object]"
console.log(Object.prototype.toString.call([1, 2, 3])) // "[object Array]"
console.log(Object.prototype.toString.call(function(){var a = 1;})) // "[object Function]"
  • 在实际项目中会经常使用这个方式判断数据类型,使用时要注意变量缓存。
var arr = [];

var str = Object.prototype.toString,
    trueTip = '[object Array]';
if(str.call(arr) === trueTip){
    console.log('是数组');
}else{
    console.log('不是数组');
}

由于这个默认方法不会显示太多有用的信息,所以很多类都会重新定义自己的toString()方法。比如,在把数组转换为字符串时,会得到数组元素的一个列表,每个元素都会转换为字符串。而把函数转换为字符串时,可以得到函数的源代码。

  • 原始类型重新定义的toString()方法,会返回当前值的字符串。
  • 注意undefinednull并没有自己的toString()方法,直接调用会报错。
var a = 'HelloWorld',
    b = 123,
    c = true,
    d = undefined,
    e = null,
    f = [4, 5, 6],
    g = function(){
         var str = 'demo';
    };
console.log(a.toString()); // 'HelloWorld'
console.log(b.toString()); // '123'
console.log(c.toString()); // 'true'
// console.log(d.toString()); // error
// console.log(e.toString()); // error
console.log(f.toString()); // '4, 5, 6'
console.log(g.toString()); // 'function(){ var str = 'demo'; }'

类型转换

显示类型转换

Number(data)方法 将目标转换为数字类型。

let a = '123';
console.log(Number(a) + '-' + typeof(Number(a))); // 123-number
let b = 'true';
console.log(Number(b) + '-' + typeof(Number(b))); // NaN-number
let c = false;
console.log(Number(c) + '-' + typeof(Number(c))); // 0-number
let d = null;
console.log(Number(d) + '-' + typeof(Number(d))); // 0-number
let e = undefined;
console.log(Number(e) + '-' + typeof(Number(e))); // NaN-number

parseInt(data, radix)方法 将目标转换为整型,参数data表示转换的元数据,radix参数表示基底。用于指定目标转换的进制,范围是2-36

let a = 13.14;
console.log(parseInt(a)); // 13
let b = '10';
console.log(parseInt(b, 16)); // 十进制中10对应十六进制的16
  • 如果目标是混合型的字符串,那么会从第一位开始看,如果第一位不是数字,那就直接转换为NaN。如果是数字那就一直判定到非数为止。
let a = 'abc123';
console.log(parseInt(a)); // NaN
let b = '123a456';
console.log(parseInt(b)); // 123
  • 注意它和Number()方法的区别,对于所有非数类型的值,它不会考虑转换为一个具体的数字,而是只需要转换为整型数字类型就行,所以以下几种的结果都是NaN
let a = true;
console.log(typeof(parseInt(a)) + '-' + parseInt(a)); // number-NaN
let b = null;
console.log(typeof(parseInt(b)) + '-' + parseInt(b)); // number-NaN
let c = undefined;
console.log(typeof(parseInt(c)) + '-' + parseInt(c)); // number-NaN
let d = NaN;
console.log(typeof(parseInt(d)) + '-' + parseInt(d)); // number-NaN

parseFloat()方法 将目标转换为小数。

toFixed(num)方法 设置要保留的小数位。

let num = parseFloat('3.1415926'); 
console.log(num.toFixed(2)); // 设置要保留的小数位

String()方法 将目标转换为字符串类型。

let num = 123456; 
console.log(String(num)); // 123456
console.log(typeof(String(num))); // string

toString(radix)方法 将目标转换为的字符串类类型,它也有参数radix,用于设置转换的进制。注意nullundefined没有该方法。

let str = 100;
console.log(str.toString()); // 100

Boolean()方法 将目标转换为布尔值。

console.log(Boolean(1)); // true
console.log(Boolean(0)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(null)); // false
console.log(Boolean('abc')); // true

隐式类型转换

如果对一个字符串进行++操作,那就会对这个字符串先进行Number()隐式转换,再进行++操作。

let a = '123'; // 隐式进行了Number(a)
a++;
console.log(a); // 124
let b = 'abc';
b++;
console.log(b); // NaN

其它运算符比如减、乘除,取余和正负号也都是会隐式转换,将字符串先进行Number(),再运算。

let a = '3' * 2;
let b = '10' / 2;
let c = '10' % 3;
console.log(a); // 6
console.log(b); // 5
console.log(c); // 1

之前说到的任何数值和字符串相加+,都会变成字符串拼接,本质上也是存在String()隐式转换的。

let a = '123';
let b = 456; // 隐式进行了String(b);
console.log(a + b); // 123456

比较运算符也存在隐式转换,但要注意全等===是需要看数据类型的,所以它不会隐式转换。

let bool2 = 1 === '1';
console.log(bool2); // false
  • 注意以下代码的过程是,2 > 1 = true; -> true == 1; 所以b的输出结果是true。
let a = 2 > 1 > 3;
console.log(a); // false
let b = 2 > 1 == 1;
console.log(b); // true
  • undefinednull都不等于0,但是它们俩自身相等,相等的原因并不是因为两个值都可以转换为false,而是因为ECMA-262规定对它们的相等性测试要返回true。并且要注意它们不全等,压根就不是同一数据类型。
let a = undefined == 0;
console.log(a); // false
let b = null == 0; 
console.log(b); // false
let c = undefined == null;
console.log(c); // true

isNaN()方法 判断目标是否是NaN,这个过程中也存在隐式转换,比如字符串'123'会转换为123null会转换为数字0undefined是转换为NaN

console.log(isNaN(123)); // false
console.log(isNaN('123')); // false
console.log(isNaN('a')); // true
console.log(isNaN(null)); // false
console.log(isNaN(undefined)); // true