写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。Github地址。
变量类型
JS有8种内置变量类型,按照存储方式可以划分为两大类型:基本数据类型(值类型)和引用数据类型(对象类型)。
数据类型的分类和判断
基本数据类型有7种:
- Number --- 任意数值 --- typeof
- String --- 任意字符串 --- typeof
- Boolean --- true/false --- typeof
- undefined --- undefined --- typeof/===
- null --- null --- ===
- Symbol(ES6)
- BigInt
引用数据类型包括:
- Object --- 任意对象,内部数据是无序的 --- typeof/instanceof
- Array --- 一种带数值下标的特别对象,内部数据是有序的 --- instanceof
- Function --- 一种特别的对象(可以执行) --- typeof/instanceof
使用数据前要做哪些工作,为什么?
- 对数据类型进行判断
- 判断的目的是分类
- 分类是为了明确当前类型的功能然后确定如何使用
基本类型在内存中占有固定大小的空间,它们的值保存在栈空间中,我们通过按值来访问的。
引用类型的值,存储在堆内存中。由于引用类型值的大小不固定(对象有若干个属性和方法,而且还可以动态地添加属性和方法),因此不能把他们保存到栈内存中。但内存地址大小是固定的,因此内存地址是保存在栈内存中的。
总结如下: 栈内存中存放的是基本数据类型值,堆内存中存放的是引用类型值,引用类型值在内存中的地址值存放在栈内存中,也就是我们常说的对象引用(指针)。
其中js的数字类型是浮点类型的,没有整型。并且浮点类型基于IEEE 754标准实现,在使用中会遇到某些Bug。NaN也属于number类型,并且NaN不等于自身(ES5)。
需要注意:在ES6中Object.is(NaN, NaN); // true
不仅对象可以有属性,数组和函数也可以有属性,都是公用内存空间。如下图所示:
变量复制
// 值类型
var a = 5;
var b = a;
console.log(a + '---' + b); // 5---5
b = 6; // 这里重新给b赋值,a值并没有改变
console.log(a + '---' + b); // 5---6
2
3
4
5
6
// 引用类型
var obj = {name: 'lisi'};
var obj2 = obj; // 这里是引用赋值,obj和obj2指向同一个对象
console.log(obj.name + '---' + obj2.name); // lisi---lisi
obj2.name = 'wangwu';
console.log(obj.name + '---' + obj2.name); // wangwu---wangwu
2
3
4
5
6
obj和obj2指向了内存空间中的同一区域。引用类型包括:对象、数组和函数等。为什么要这样设计呢?因为,假如obj对象有很多属性(占用内存多),又将obj赋值给obj2,obj2也会占用很多内存空间,为了使得内存空间可以公用,设计了引用类型赋值指向同一内存区域。
从上面例子可以看出:在变量复制方面,基本类型和引用类型也有所不同,基本类型复制的是值本身,而引用类型复制的是内存地址。
undefined和null的区别
- undefined:表示变量定义了但未赋值;
- null:变量定义了且值是null。
undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。用法:
- 变量被声明了,但没有赋值时,就等于undefined。
- 调用函数时,应该提供的参数没有提供,该参数等于undefined。
- 对象没有赋值的属性,该属性的值为undefined。
- 函数没有返回值时,默认返回undefined。
Null类型也只有一个值,即null。null用来表示尚未存在的对象,常用来表示函数企图返回一个不存在的对象。用法
- 作为函数的参数,表示该函数的参数不是对象。
- 作为对象原型链的终点。
什么时候数据为undefined?
- 定义变量未赋值;
- 函数定义形参调用的时候没有传入实参;
- 函数没有指定返回值的时候获取的值都为undefined(即没有显式的return语句);
- 获取对象未定义的属性;
- undefined表示当前值不一定需要,可暂时不存在,当浏览器引擎解析不了的时候会抛出undefined甚至报错。所以在定义变量的时候不建议赋值为undefined。
什么时候数据为null?
- 当某一个变量应该存在但不确定具体值的时候;
- 对象的某一个属性值需要通过后期计算得到可暂时赋值为null;
- null表示空的对象,浏览器引擎在解析的时候不会报错;
- 当某一个变量指向的对象不再使用的时候,可将变量赋值为null,使得之前的对象为垃圾对象被回收;
- 和undefined相比,null表示确确实实存在的对象,当某一个变量未确定值的时候可暂时赋值为null
什么时候使用null?
- 给对象初始赋值,表示将要赋值为对象;
- 让对象成为垃圾对象,被垃圾回收器回收
严格区别变量类型与数据类型
数据类型
- 基本类型
- 对象类型
变量类型(变量内存值的类型)
- 基本类型变量:保存的是基本类型的数据值;
- 引用类型变量:保存的是地址。
数据类型判断
typeof运算符(返回字符串)
需要注意以下几点:
- typeof返回六种数据类型:'string','number','boolean','undefined','object','function'
- 可以区别判断的数据类型:数值,字符串,布尔值,undefined,symbol,bigint,function
- 无法区分判断:null与对象(都是'object'),一般对象与数组(都是'object')
- typeof运算符返回的数据类型本身是字符串 typeof对于基本类型,除了null都可以显示正确的类型。typeof返回的是数据类型的字符串表达。
typeof 1 // 'number'
typeof 'abc' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof a // 'undefined' a虽然没有声明,但是还会显示undefined
typeof BigInt(123) // 'bigint'
typeof null // 'object'
2
3
4
5
6
7
8
typeof NaN // 'number'
需要注意:typeof对于引用类型,除了函数都会显示object,无法区分Object、Array和null。
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
typeof function(){} // 'function'
// Object和Number都是构造函数
typeof Object // 'function'
typeof Number // 'function'
2
3
4
5
6
7
特别注意:对于null来说,虽然它是基本类型,但是会显示object。typeof null === 'object'
。所以,typeof只能用来判断除了null以外的基本数据类型,无法判断null和Object引用类型。
instanceof(判断对象的具体类型)
instanceof运算符专门用来判断对象数据的类型: Object,Array与Function。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
2
3
4
5
6
let arr = [1, 2, 2];
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
2
3
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象。 缺点:Number,Boolean,String等基本数据类型不能判断。
// instanceof左边是实例对象,右边是实例对象的构造函数
// 判断给定实例是不是构造函数的实例对象
// 实例 instanceof 构造函数
const obj = {
arr: [123, 'test', console.log],
fn: function() {
console.log('111');
}
}
console.log(obj instanceof Object, obj instanceof Array); // true false
console.log(obj.arr instanceof Array, obj.arr instanceof Object); // true true
console.log(obj.fn instanceof Function, obj.fn instanceof Object); // true true
console.log(typeof obj.fn); // 'function'
console.log(typeof obj.arr); // 'object'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
如何获取一个变量的正确类型(Object.prototype.toString.call())
如果想获得一个变量的正确类型,可以通过Object.prototype.toString.call(xx)
。这样我们就可以获得类似[object Type]
的字符串。
const toString = Object.prototype.toString;
console.log(toString.call(2)); // [object Number]
console.log(toString.call(true)); // [object Boolean]
console.log(toString.call('str')); // [object String]
console.log(toString.call([])); // [object Array]
console.log(toString.call(function(){})); // [object Function]
console.log(toString.call({})); // [object Object]
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(null)); // [object Null]
2
3
4
5
6
7
8
9
10
Object.prototype.toString.call(12); // "[object Number]"
Object.prototype.toString.call({name: 'lisi'}); // "[object Object]"
Object.prototype.toString.call(null); // "[object Null]"
// 对于未声明的变量
Object.prototype.toString.call(a); // "[object Undefined]"
Object.prototype.toString.call(NaN); // "[object Number]"
2
3
4
5
6
优点:精准判断数据类型。 缺点:写法繁琐不容易记,推荐进行封装后使用。
强制类型转换
强制类型转换场景:
- 字符串拼接
- ==运算符
- if语句
- 逻辑运算符
- 三元运算符
// 字符串拼接
console.log(100 + 10); // 110
console.log(100 + '10'); // '10010'
console.log(100 - '10'); // 90
2
3
4
// ==运算符(慎用)
// 数字100会转换为字符串'100'
console.log(100 == '100'); // true
// 0和''都会转换为false
console.log(0 == ''); // true
// null和undefined都会转换为false
console.log(null == undefined); // true
2
3
4
5
6
7
// if语句
var flag = true;
if (flag) {}
2
3
var a = 0;
// 逻辑运算符
console.log(100 && 0); // 0
console.log('' && 'test'); // 'test'
console.log(!a); // true
2
3
4
5
转Boolean
在条件判断时,除了undefined,null,false,NaN,'',0,-0,其他所有值都转为true,包括所有对象。
var a = 123;
// 转Boolean
console.log(!!a); // true
2
3
对象转基本类型
对象在转换基本类型时,首先会调用valueOf然后调用toString,并且这两个方法是可以重写的。
let a = {
valueOf() {
return 0;
}
}
2
3
4
5
当然也可以重写Symbol.toPrimitive
,该方法在转基本类型时调用优先级最高。
let a = {
valueOf() {
return 0;
},
toString() {
return '1';
},
[Symbol.toPrimitive]() {
return 2;
}
}
1 + a // => 3
'1' + a // => '12'
2
3
4
5
6
7
8
9
10
11
12
13
四则运算符
只有当加法运算时,其中一方是字符串类型,就会把另一个也转为字符串类型。其他运算只要其中一方是数字,那么另一方就转为数字。并且加法运算会触发三种类型转换:将值转换为原始值,转换为数字,转换为字符串。
1 + '1' // '11'
2 * '2' // 4
[1, 2] + [2, 1] // '1,22,1'
// [1, 2].toString() -> '1,2'
// [2, 1].toString() -> '2,1'
// '1,2' + '2,1' = '1,22,1'
2
3
4
5
6
对于加号需要注意这个表达式 'a' + + 'b'
'a' + + 'b' // -> "aNaN"
// 因为 + 'b' -> NaN
// 你也许在一些代码中看到过 + '1' -> 1
('ba' + + 'b').toLowerCase() // 'banan'
2
3
4
5
==操作符
这里来解析一道题目[] == ![] // -> true
,下面是这个表达式为何为true的步骤:
// [] 转成 true,然后取反变成 false
[] == false
// 根据第 8 条得出
[] == ToNumber(false)
[] == 0
// 根据第 10 条得出
ToPrimitive([]) == 0
// [].toString() -> ''
'' == 0
// 根据第 6 条得出
0 == 0 // -> true
2
3
4
5
6
7
8
9
10
11
比较运算符
- 如果是对象,就通过toPrimitive转换对象;
- 如果是字符串,就通过unicode字符索引来比较
何时使用===何时使用==
console.log(undefined == null); // true
console.log(undefined === null); // false
2
if (obj == null) {
// 这里相当于 obj === null || obj === undefined 的简写形式
// 这是jQuery源码中的推荐写法
}
2
3
4
只有在判断变量是否为null或者undefined的时候用==
,其他情况一律用===
。
==
会发生强制类型转换,===
不会。
==和===区别
- ==:两边值类型不同的时候,要先进行类型转换,再比较
- ===:不做类型转换,类型不同的一定不等。
==类型转换过程:
- 如果类型不同,进行类型转换
- 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
- 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
- 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
- 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断
经典面试题:[] == ![] 为什么是true 转化步骤:
- !运算符优先级最高,![]会被转为为false,因此表达式变成了:[] == false
- 根据上面第(4)条规则,如果有一方是boolean,就把boolean转为number,因此表达式变成了:[] == 0
- 根据上面第(5)条规则,把数组转为原始类型,调用数组的toString()方法,[]转为空字符串,因此表达式变成了:'' == 0
- 根据上面第(3)条规则,两边数据类型为string和number,把空字符串转为0,因此表达式变成了:0 == 0
- 两边数据类型相同,0==0为true
ES6中Object.is
Object.is(NaN, NaN); // true
参考文档
- 内存管理 阅读量:
评 论: