JS 变量与数据类型

JavaScript 数据类型

值类型:number、string、null、boolean、undefined、BigInt、Symbol
引用类型:object,包含 object、array、date、function 等

JavaScript 中的真假值

truly 值:两次取反(非运算)等于 true 的值,!!x === true

falsely 值:两次取反(非运算)等于 false 的值,!!x === false

假值如下:

  1. null
  2. undefined
  3. ‘’ 空字符串
  4. +0、-0
  5. NaN (Not a Number)
  6. false

除了以上的假值外,其他值都可以认为是truly

typeof 可以判断哪些类型

typeof 可以判断除了 null 以外的所有值类型,以及引用类型中的 function 和 object;

  1. number
  2. string
  3. undefined
  4. boolean
  5. bigInt
  6. symbol
  7. function
  8. object

要判断引用类型,可以使用 instanceof;

为什么 typeof null 返回的是 object

JavaScript 底层存储变量时,使用变量机器码的低 1 ~ 3 位存储其类型信息;

类型标签类型说明
000对象
1整数
010浮点数
100字符串
110布尔值
undefined用 -2^30 整数表示
null所有机器码为 0

上表可以看到,对象的类型标签是 0,而由于 null 的所有机器码均为 0,因此,null 的类型标签也可以认为是 0,所以 typeof 在判断类型时,就把 null 当作了对象;

原文 - The history of “typeof null”

值类型和引用类型的区别

  1. 存储位置不一样
    1. 值类型的变量保存在栈内存中,通常,在代码段执行结束后,值类型的变量会被销毁;
    2. 引用类型的变量名保存在栈内存中,但变量的值会存储在堆内存中,引用类型的变量不会自动销毁,而是在没有被引用时,由垃圾回收机制释放;
  2. 复制方式不一样
    1. 值类型的变量之间复制是深拷贝,修改不会相互影响;
    2. 引用类型的变量之间直接赋值实际上是引用传递,只是浅拷贝,修改会互相影响;
  3. 值类型的变量无法添加属性和方法,而引用类型则可以;
  4. 比较方式不一样
    1. 值类型的比较是值的比较,值相等则相等,== 在比较时可能会隐式类型转换,=== 则需要类型与值完全相等;
    2. 引用类型的比较是引用地址的比较,即使内容相同,但地址不同,也不相等;
  5. 当作函数的参数时,传递方式不同
    1. 值类型作为函数参数时,实参会复制给形参,数据独立,互不影响;
    2. 引用类型作为函数参数时,传递给形参的就是实参的引用地址,修改会相互影响;

简单实现深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
*
* @param {object} obj 要拷贝的对象
* @return 拷贝好的对象
*/
function deepClone(obj) {
// 1. 值类型和 null 直接返回,递归停止的条件
if (typeof obj !== "object" || obj === null) {
return obj;
}

// 2. 区分数组和对象
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}

// 3. 递归拷贝 obj 自有属性
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}

// 4. 返回结果
return result;
}

Object.assign 不是深拷贝

Object.assign 是浅拷贝,对于属性值为引用类型的值,只会拷贝其引用地址;

1
2
3
4
5
6
7
8
9
10
const obj1 = { a: 10, b: 20, c: { x: 100, y: 200 } };
const obj2 = Object.assign({}, obj1);

// 修改值类型的值
obj1.a = 11;
// 修改引用类型的值
obj2.c.x = 101;

console.log("obj1", obj1);
console.log("obj2", obj2);

结果如下:
Object.assign

修改值为基本类型的属性不会互相影响,而修改值为引用类型的属性时则会相互影响;

何时使用 ====,何时使用 ==

除了判断是否等于 null 或 undefined,其他地方一律用 ===;

  1. 判断对象的属性是否存在可以使用 ==;
  2. 判断函数的参数是否存在可以使用 ==;

var 和 let const 的区别

  1. var 属于 es5 规范;
  2. let、const 属于 es6 规范;
  3. var 有变量提升;
  4. let、const 有块级作用域;
  5. var、let 声明的是变量,可修改,const 声明的是常量,不可修改;

列举强制类型转换和隐式类型转换

  1. 强制类型转换:parseInt、parseFloat、toString、Number、String 等;
  2. 隐式类型转换:if、逻辑运算、==、+ 拼接字符串等;

手写深度比较,模拟 lodash 的 isEqual

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 工具函数,判断参数类型是否为对象
* @param {*} obj 要判断的参数
* @returns true 表示 obj 是对象
*/
function isObject(obj) {
return typeof obj === "object" && obj !== null;
}

/**
*
* @param {*} obj1 要比较的参数 1
* @param {*} obj2 要比较的参数 2
* @returns true 表示 obj1 === obj2
*/
function isEqual(obj1, obj2) {
// 1. 值类型,直接返回比较结果
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2;
}

// 2. 判断两个参数是否为同一对象
if (obj1 === obj2) {
return true;
}

// 3. 比较参数的元素数量
const obj1Keys = Object.keys(obj1);
const obj2Keys = Object.keys(obj2);
if (obj1Keys.length !== obj2Keys.length) {
return false;
}

// 4. 以参数 1 为基准,依次比较二者的元素
for (let key in obj1) {
const result = isEqual(obj1[key], obj2[key]);
// 5. 元素比较不相等直接返回 false
if (!result) {
return false;
}
}
// 6. 元素比较过程中没有返回,则全等
return true;
}