[前端面试题]你可能需要掌握的27个javascript数据类型问题

前言

本文给大家带来的是,你可能需要掌握的27个javascript数据类型问题。也是在面试中经常会遇到的问题。

1. 什么是数据类型?

JavaScript 中的每个值都有特定的类型,此类型决定了可以对值执行哪些操作以及如何存储它,这就是数据类型。

2. 有多少种数据类型?

JavaScript 有 8 种数据类型,分别是: number, string, boolean, null, undefined, bigint, symbol 以及 object。

3. 什么是原始数据类型?

除了对象类型之外的所有类型。

因此,有 7 种原始数据类型:number, string, boolean, null, undefined, bigint, symbol。

原始值具有以下特征:

  • 它们没有方法或属性。
  • 它们是不可变的:一旦创建,你就无法修改该值。
  • 它们直接存储在内存堆栈中。
let a = "eveningwater";
console.log(a); // "不可变的"
a[0] = "E"; // 第一个字符变成E
console.log(a); //"不可变的"(因为字符串不能改变)

4. 内存堆栈到底是什么?

JavaScript 程序具有内存,用于存储变量/值以供以后访问。

该内存由栈和堆组成。

  • 栈:存储静态数据(具有固定大小的数据)。由于原始值是不可变的且不会改变,因此它们将存储在栈中。
  • 堆:存储对象值。堆的大小可以在代码执行期间发生变化,以适应对象值的变化。

5. 什么是非原始数据类型?

对象类型是唯一的非原始数据类型。

这包括:字面量对象({ name:"eveningwater" })、函数(const sayHello = () => {})、数组([1,2,3])等。

原始数据类型和非原始数据类型之间存在关键区别,当变量包含非原始数据类型时,它不保存实际的对象值,相反,它包含内存中(即堆中)对该对象的引用,将引用想象成地址。

就像是房子!=地址一样,但是,如果我做以下事情:

  • 向地址发送礼物。
  • 装饰包含该地址的房子。

这会影响你的房子。

在 JavaScript 世界中,这相当于以下代码:

// 创建一个房子对象
const house = {
  flooring: 2, // 房子包含2层楼
  gift: [],
  color: "blue",
};
// 发送礼物
house.gifts.push("Red dress");
// 装饰房子,粉刷颜色为红色
house.color = "red";

将对象传递给函数时,请记住这一区别。

在下面的示例中,代码修改了原始数组,因为它作为地址传递给函数:

function addUser(arr) {
  const randomId = "123";
  arr.push({ id: randomId });
  return arr;
}

const users = [];
console.log(users); // []
const newUsers = addUser(users);
console.log(newUsers); // [{ id: ... }]
console.log(users); // [{ id: ... }](因为 users 已被修改)

6. 如何获取值的类型?

你可以使用 typeof 操作符来获取值的类型。

const name = "eveningwater";
console.log(typeof "Hello"); // "string"
console.log(typeof "Hi"); // "string"
console.log(typeof name); // "string"

注意: 这个操作符不适用于 null,因为 typeof null 返回 object。要安全地检查 value 是否为对象,你需要执行 value != null && typeof value === "object"

7. typeof (() => {}) 返回值是什么?

这是 typeof 的另一个异常,typeof fn返回函数而不是对象。

const sayHello = (name) => console.log(`Hello, ${name}`);
console.log(typeof sayHi); // "function"

8. JavaScript 中如何判断一个值是否是数组?

typeof arr 返回 object,因为数组也是对象。因此,你可以使用 Array.isArray(value)来检测一个值是否是数组。

const nums = [1, 2, 3];
console.log(Array.isArray(nums)); // true
console.log(Array.isArray({})); // false

9. number 和 bigint 有什么不同?

在 JavaScript 中,只有 [-9007199254740991, 9007199254740991] 范围内的整数才可以安全操作。此范围之外的整数可能会产生错误的数学结果。例如,9007199254740993 - 9007199254740992 将返回 0 而不是 1。BigInt 是针对此范围之外的整数引入的。任何整数都可以通过在其后附加 n 或使用 BigInt(...) 转换为 bigint9007199254740993n - 9007199254740992n 将正确返回 1n

提示:你可以使用 Number.MIN_SAFE_INTEGERNumber.MAX_SAFE_INTEGER 获取最小/最大安全整数。

10. null 和 undefined 有什么不同?

有一点不同。undefined 表示尚未定义的值。这就像你刚买了一栋新房子,有人问起游泳池。你甚至还没有想过——它是未定义的!null 表示已设置为“空”的值。这就像你买了房子,但决定不建游泳池(因为你太穷了)。

typeof null 返回 "object",typeof undefined 返回 "undefined"。 注意:不要在代码中依赖这种区别。只需将任何值为 nullundefined 视为空即可。

11. 什么是属性?

对象具有属性。每个属性将一个键与一个值关联起来。例如,对象 me 具有属性 name、age 等。

const me = {
  name: "eveningwater",
  age: 28,
  job: "web前端开发工程师",
};

关于属性需要了解的三件事

  • 仅允许将字符串或符号值用作属性名。任何其他值都将通过类型转换为字符串(参见问题12)
  • 它们区分大小写
  • 你需要使用[]来访问带有空格分隔的属性名
const obj = {
  "first name": "evening"
};
console.log(obj.first name); // 无效
console.log(obj["first name"]); // "eveningwater"

12. 什么是方法?

方法是一种特别的属性,作为属性和函数进行关联。该函数只能通过对象访问,如下所示:

const me = {
  name: "eveningwater",
  sayHi() {
    console.log(`Hi, my name is ${this.name}`);
  },
  sayHelloWorld() {
    console.log("Hello World");
  },
};

me.sayHi(); // Hi, my name is eveningwater
const sayHelloFn = me.sayHelloWorld;
sayHelloFn();

注意:在 sayHi 函数中,要小心使用 this 对象。如果函数被这样调用,const fn = me.sayHi; fn();,this != me。你可以在这里了解有关此规则的更多信息。

13. 类型转换

还记得问题 1 吗?数据类型仅支持某些操作。例如,我们可以将数字相加,例如 1 + 2。当我们尝试“无效”操作(例如"8"+ 9)时会发生什么?---类型转换。

当 JavaScript 隐式将值从一种数据类型转换为另一种数据类型时,就会发生类型转换。在上面的例子中,JavaScript 将 9 转换为字符串“9”,并返回“8”+“9”=>“89”

更多类型转换示例:

const result ="10"-5; // 返回 5,因为"10"被类型转换为数字9
const sum = true + false; // 返回 1,因为 true 被类型转换为 1,false 被类型转换为 0

注意:类型转换规则是存在的,但你不需要全部记住,当然,应避免类型转换的操作。

14. 什么是假值?它的反义词是什么?你能说出所有假值的名字吗?

最简单的答案:if(Boolean(value)) 返回 false,则 value 为假值。

因此,当 JavaScript 需要布尔值时,每个假值都将被强制为 false(参见问题 12)。

// value 为假值
if (value) {
  // 无法访问,因为 `value` 将被强制为 `false`
}
const name = value && "myValueHere"; // `name` 将等于 `value`,因为 `value` 将被强制为 false

只有 10 个假值:

  • false
  • 0+0-00nNaN
  • 空字符串:""''等。
  • null
  • undefined
  • document.all:唯一的假值对象。

如果值不是假值(即不存在于此列表中),则它为真值。

15. 如何将值转换为布尔值?

有两种方法:
方法1:Boolean(value)
方法2(更好):!!value

16. == 和 === 有什么区别?

=== 类似于 ==,只是没有类型转换,并且类型必须相同。事实上,如果 JavaScript 类型转换后两个值相等,则 == 将返回 true

例如,1 == true 返回 true,因为 JavaScript 会将 1 类型转换为布尔值。由于 1 为真(参见问题 13),因此它相当于 true。相反,1 === true 将返回 false,因为类型不同。

提示:除非用于检查值是否为 null/undefined,否则切勿使用 ==。否则,类型转换可能会启动,导致意外结果。例如 [] == ![] 返回 true,因为两边都被类型转换为数字([] => 0![] => false => 0,因为 [] 为真)

17. 为什么 0.1 + 0.2 === 0.3 返回 false?

0.10.2 是浮点数。在 JavaScript(以及许多其他语言)中,浮点数使用 IEEE 754 标准表示。不幸的是,像 0.10.2 这样的数字无法在此标准中精确表示,从而导致精度损失。因此,0.1 + 0.2 相当于近似(0.1)+近似(0.2),其结果与 0.3 略有不同。

18. 为什么 {} === {} 返回 false?

如果你理解问题 4,这个问题就很简单。{}是对象。因此,每个对象都存储指向内存堆中不同值的不同引用。由于这些引用(即地址)不同,因此比较返回 false

19. 访问对象属性有哪些不同的方法?

有两种选择(使用点符号或括号符号)。假设我们有以下对象,并且我们想要访问 name 属性:

const me = {
  name: "eveningwater",
  job: "web前端开发工程师",
  "way of life": "Coding",
};

我们可以这样做:

me.name;
me["name"];

注意:对于带有空格的属性,如"way of life",我们只能使用 me["way of life"]

20. 当 obj 可能未定义或为 null 时,如何安全地执行 obj.value?

我们有两种方法:

  • 方法1:obj != null ? obj.value : undefined

  • 方法2(更好):使用可选链式运算符,例如 obj?.value

21. 如何循环遍历数组或对象的值?

遍历数组:

const arr = [1, 2, 3, 4, 5];

// 使用for...of
for (let x of arr) {
  console.log(x);
}

// 使用for循环
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// 使用数组相关方法,例如: forEach方法
arr.forEach((x) => {
  console.log(x);
});

// 如果需要返回值做什么,可以使用其它方法,例如map方法
const res = arr.map((x) => console.log(x));
console.log(1111, res);

遍历对象:

const obj = {
  name: "eveningwater",
  age: 28,
};

// 使用Object.keys
Object.keys(obj).forEach((k) => {
  console.log(obj[k]);
});

// 使用Object.values
Object.values(obj).forEach((x) => {
  console.log(x);
});

// 使用Object.entries
Object.entries(obj).forEach(([k, x]) => {
  console.log(x);
});

// 使用 for...in (不推荐)
for (const key in obj) {
  console.log(key);
}

// 将object构造成可迭代对象,再使用for..of遍历
let obj = {
  name: "eveningwater",
  age: 28,
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const keys = Object.keys(this),
      len = keys.length;
    return {
      next: () => {
        const key = keys[index++];
        const value = this[key];
        if (index <= len) {
          return { value: [key, value], done: false };
        } else {
          return { done: true };
        }
      },
    };
  },
};

for (let [k, v] of obj) {
  console.log(k, v); // 输出 name,eveningwater,age,28,data,[1,2,3]
}

重要提示:避免使用 for...in,因为它可能会产生意外的结果。

22. 什么是原型继承?

JavaScript 中的每个对象都有一个隐藏属性 [[Prototype]],称为原型,它要么是对象,要么未定义。可以通过 obj.__proto__ 访问此属性。

原型从哪里来?

设置对象原型有多种方法:

方法 1:直接创建对象

每个对象在设置时都会有一个默认原型。

例如: 数组对象的原型是 Array.prototype。像 { name: "eveningwater"} 这样的对象的原型是 Object.prototype 等等。

方法 2:明确设置原型

const water = {
  nums: 4,
};
const drink = {
  name: "wahaha",
};

drink.__proto__ = water; // drink.[[Prototype]] 和 water 相等

方法 3:使用给定函数创建对象

function Water(drink) {
  this.drink = drink;
}
const Whh = new Water("wahaha"); // Whh.[[Prototype]]和water相等

方法 4:使用 Object.create 创建对象

const water = {
  drink: false,
};
const whh = Object.create(water); // whh.[[Prototype]]和water相等
whh.drink = "true";

原型是用来做什么的?
因此,每个对象都有一个原型。当你尝试访问对象上的方法或属性时,JavaScript 将首先检查该对象。当找不到任何属性/方法时,JavaScript 将查看原型对象。可能发生三件事:

  • 原型对象上存在属性/方法 => JavaScript 将返回它。
  • 原型方法未定义 => JavaScript 将返回未定义。
  • 原型方法未定义 => 继承开始。由于原型是一个对象,它有自己的原型。因此 JavaScript 将查看原型的原型。它将继续这样做,直到找到属性/方法或原型变为未定义。

这是原型继承。我们继承了原型的属性/方法。最终的原型是 Object.prototypeObject.prototype.__proto__ === undefined

在下面的例子中,whh 对象没有 drink 属性,因此将返回 water 上的属性。

const water = {
  drink: 4,
};
const whh = {
  name: "whh",
  log() {
    console.log(
      `My favorite drink is ${this.name} and I have ${this.drink} drinks!`
    );
  },
};

whh.__proto__ = water; // 设置 whh的原型为water
whh.log(); //"My favorite drink is whh and I have 4 drinks!"

23. 如何检查对象中是否存在某个属性?

你的第一反应可能是使用 obj.value !== undefined,但在以下情况下可能会出错:

  • obj 未定义
  • obj.valueundefined 提供
  • value 存在于原型链中(参见问题 21)

我们可以在 obj 中尝试 value,但当 obj 未定义或为 null 时,此方法会中断。

实现此目的最安全的方法是什么?obj?.hasOwnProperty(value)

24. 为什么我们可以在字符串值上调用 .trim() 之类的方法?

像字符串这样的原始值没有方法。那么,这到底是怎么回事呢?

嗯,这是通过一个叫做自动装箱的巧妙功能。

25. JavaScript 中的自动装箱是什么?

除 null 和 undefined 之外的每个原始类型都有一个关联的对象包装器类(见下表)。

数据类型对象包装器
nullN/A
undefinedN/A
numberNumber
stringString
booleanboolean
bigintBigint
symbolSymbol

自动装箱是指当访问某些方法或属性时,JavaScript 会将原始类型临时转换为其对应的对象包装器。让我们看一个例子:

const name = "eveningwater   ";
const cleanName = name.trim(); // cleanName = "eveningwater"

当我们执行 name.trim() 时,name 是一个原始值,没有方法。然后 JavaScript 做了以下三件事:

  • name 转换为 new String(name),它是一个对象。
  • 访问 String 对象上的 trim 方法,返回"eveningwater"
  • 返回结果。

26. 你能给出 3 种创建未定义值的方法吗?

方法 1:明确将值设置为未定义。

let name = undefined;

方法 2:声明一个没有初始值的变量。

let name, age;
let hobby;

方法 3:访问不存在的属性/方法。

const me = {
  name: "eveningwater",
};

const age = me.age; // undefined,因为age属性在对象me中不存在

27. 浅拷贝和深拷贝有什么区别?

首先,什么是副本?假设我有一个数组 arr1,我想创建该数组 arr2 的副本。我不能执行 const arr2 = arr1,因为数组将指向同一个对象,并且对 arr2 的任何修改都会影响 arr1(参见问题 3)。那么解决方案是什么?const arr2 = [...arr1];这将确保两个数组指向内存中的不同对象。但有一个问题 。

当数组包含原始值(如 const arr1 = [1, 2, 3])时,这种方法可以正常工作,但如果数组包含对象(如 const arr1 = [ { name: "eveningwater"}])怎么办?

当我执行 arr2[0].name = "water" 时会发生什么?arr1[0] 会受到影响吗?---是。因为 arr2 仍然包含与 arr1 中相同的引用列表。因此,任何修改都会影响这些引用指向的对象。我们进行的复制是浅拷贝。深拷贝是指我们为每个对象创建新的引用,因此任何修改都不会影响原始对象。在上面的例子中,我们可以这样进行深拷贝:

const arr1 = [{ name: "eveningwater" }];

// 方法1:这仅在每个值都可以序列化时才有效
const arr2 = JSON.parse(JSON.stringify(arr1));

// 方法2:使用 `structuredClone`,https://developer.mozilla.org/en-US/docs/Web/API/structuredClone
const arr2 = structuredClone(arr1);

重要提示:深拷贝比浅拷贝有更昂贵的开销。在使用它之前,请确保你确实需要它。

28. Symbol(“name”) === Symbol(“name”) 的结果是什么?

false。因为每次调用 Symbol 都会返回不同的值,即使参数值相同。

29. 你能改变变量的数据类型吗?

变量没有类型但是变量值有,事实上,当我们有以下代码时,typeof name 相当于 typeof "eveningwater",都返回“string”

const name = "eveningwater";
const type = typeof name; // returns "string"

因此,由于用 varlet 声明的变量的值可以改变,因此它们的类型也可以改变。

let x = "eveningwater";
console.log(typeof x); // 打印"string"

x = 45;
console.log(typeof x); // 打印"number"
关于我
loading