切換語言為:簡體

你可能需要掌握的27個javascript資料型別問題

  • 爱糖宝
  • 2024-09-03
  • 2060
  • 0
  • 0

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); //"不可變的"(因為字串不能改變)

記憶體堆疊到底是什麼?

JavaScript 程式具有記憶體,用於儲存變數/值以供以後訪問。

該記憶體由棧和堆組成。

  • 棧:儲存靜態資料(具有固定大小的資料)。由於原始值是不可變的且不會改變,因此它們將儲存在棧中。

  • 堆:儲存物件值。堆的大小可以在程式碼執行期間發生變化,以適應物件值的變化。

4. 什麼是非原始資料型別?

物件型別是唯一的非原始資料型別。

這包括:字面量物件({ 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 已被修改)

5. 如何獲取值的型別?

你可以使用 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"

6. typeof (() => {}) 返回值是什麼?

這是 typeof 的另一個異常,typeof fn返回函式而不是物件。

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

7. JavaScript 中如何判斷一個值是否是陣列?

typeof arr 返回 object,因為陣列也是物件。因此,你可以使用 Array.isArray(value)來檢測一個值是否是陣列。

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

8. number 和 bigint 有什麼不同?

在 JavaScript 中,只有 [-9007199254740991, 9007199254740991] 範圍內的整數纔可以安全操作。此範圍之外的整數可能會產生錯誤的數學結果。例如,9007199254740993 - 9007199254740992 將返回 0 而不是 1。BigInt 是針對此範圍之外的整數引入的。任何整數都可以透過在其後附加 n 或使用 BigInt(...) 轉換為 bigint。9007199254740993n - 9007199254740992n 將正確返回 1n

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

9. null 和 undefined 有什麼不同?

有一點不同。undefined 表示尚未定義的值。這就像你剛買了一棟新房子,有人問起游泳池。你甚至還沒有想過——它是未定義的!null 表示已設定為“空”的值。這就像你買了房子,但決定不建游泳池(因為你太窮了)。

typeof null 返回 "object",typeof undefined 返回 "undefined"。 注意:不要在程式碼中依賴這種區別。只需將任何值為 null 或 undefined 視為空即可。

10. 什麼是屬性?

物件具有屬性。每個屬性將一個鍵與一個值關聯起來。例如,物件 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"

11. 什麼是方法?

方法是一種特別的屬性,作為屬性和函式進行關聯。該函式只能透過物件訪問,如下所示:

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。你可以在這裏瞭解有關此規則的更多資訊。

12. 型別轉換

還記得問題 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

注意:型別轉換規則是存在的,但你不需要全部記住,當然,應避免型別轉換的操作。

13. 什麼是假值?它的反義詞是什麼?你能說出所有假值的名字嗎?

最簡單的答案: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、-0、0n、NaN。

  • 空字串 :"",''等。

  • null。

  • undefined。

  • document.all:唯一的假值物件。

如果值不是假值(即不存在於此列表中),則它為真值。

14. 如何將值轉換為布林值?

有兩個選擇:

  • 選項 #1:Boolean(value)

  • 選項 #2(更好):!!value

15. == 和 === 有什麼區別?

=== 類似於 ==,只是沒有型別轉換,並且型別必須相同。事實上,如果 JavaScript 型別轉換後兩個值相等,則 == 將返回 true。例如,1 == true 返回 true,因為 JavaScript 會將 1 型別轉換為布林值。由於 1 為真(參見問題 13),因此它相當於 true。相反,1 === true 將返回 false,因為型別不同。

提示:除非用於檢查值是否為 null/undefined,否則切勿使用 ==。否則,型別轉換可能會啟動,導致意外結果。例如 [] == ![] 返回 true,因為兩邊都被型別轉換為數字([] => 0![] => false => 0,因為 [] 為真)

16. 為什麼 0.1 + 0.2 === 0.3 返回 false?

0.1 和 0.2 是浮點數。在 JavaScript(以及許多其他語言)中,浮點數使用 IEEE 754 標準表示。不幸的是,像 0.1 和 0.2 這樣的數字無法在此標準中精確表示,從而導致精度損失。因此,0.1 + 0.2 相當於近似(0.1)+近似(0.2),其結果與 0.3 略有不同。

17. 為什麼 {} === {} 返回 false?

如果你理解問題 4,這個問題就很簡單。{}是物件。因此,每個物件都儲存指向記憶體堆中不同值的不同引用。由於這些引用(即地址)不同,因此比較返回 false。

18. 訪問物件屬性有哪些不同的方法?

有兩種選擇(使用點符號或括號符號)。假設我們有以下物件,並且我們想要訪問 name 屬性:

const me = {
  name: "eveningwater",
  job: "web前端開發工程師",
  "way of life": "Coding",
};

我們可以這樣做:

me.name;
me["name"];

注意:對於帶有空格的屬性,如"way of life",我們只能使用 me["way of life"]

19. 當 obj 可能未定義或為 null 時,如何安全地執行 obj.value?

我們有兩個選項:

  • 選項 #1:obj != null ? obj.value : undefined

  • 選項 #2(更好):使用可選鏈式運算子,例如 obj?.value

20. 如何迴圈遍歷陣列或物件的值?

遍歷陣列:

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,因為它可能會產生意外的結果。

21. 什麼是原型繼承?

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 建立物件
js程式碼解讀複製程式碼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: false,
};
const whh = Object.create(water); // whh.[[Prototype]]和water相等
whh.drink = "true";

22. 如何檢查物件中是否存在某個屬性?

你的第一反應可能是使用 obj.value !== undefined,但在以下情況下可能會出錯:

  • obj 未定義

  • obj.value 由 undefined 提供

  • value 存在於原型鏈中(參見問題 21)

我們可以在 obj 中嘗試 value,但當 obj 未定義或為 null 時,此方法會中斷。

實現此目的最安全的方法是什麼?obj?.hasOwnProperty(value)

23. 為什麼我們可以在字串值上呼叫 .trim() 之類的方法?

像字串這樣的原始值沒有方法。那麼,這到底是怎麼回事呢?

嗯,這是透過一個叫做自動裝箱的巧妙功能。

JavaScript 中的自動裝箱是什麼?

除 null 和 undefined 之外的每個原始型別都有一個關聯的物件包裝器類(見下表)。

資料型別 物件包裝器
null N/A
undefined N/A
number Number
string String
boolean Boolean
bigint BigInt
symbol Symbol

自動裝箱是指當訪問某些方法或屬性時,JavaScript 會將原始型別臨時轉換為其對應的物件包裝器。讓我們看一個例子:

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

當我們執行 name.trim() 時,name 是一個原始值,沒有方法。然後 JavaScript 做了以下三件事:

  • 將 name 轉換為 new String(name),它是一個物件。

  • 訪問 String 物件上的 trim 方法,返回"eveningwater"。

  • 返回結果。

24. 你能給出 3 種建立未定義值的方法嗎?

  • 方法 1:明確將值設定為未定義。

let name = undefined;

  • 方法 2:宣告一個沒有初始值的變數。

let name, age;
let hobby;

  • 方法 3:訪問不存在的屬性/方法。

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

const age = me.age; // undefined,因為age屬性在物件me中不存在

25. 淺複製和深複製有什麼區別?

首先,什麼是副本?假設我有一個數組 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);

重要提示:深複製比淺複製有更昂貴的開銷。在使用它之前,請確保你確實需要它。

26. Symbol(“name”) === Symbol(“name”) 的結果是什麼?

false。因為每次呼叫 Symbol 都會返回不同的值,即使引數值相同。

27. 你能改變變數的資料型別嗎?

變數沒有型別但是變數值有,事實上,當我們有以下程式碼時,typeof name 相當於 typeof "eveningwater",都返回“string”。

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

因此,由於用 var 和 let 宣告的變數的值可以改變,因此它們的型別也可以改變。

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

x = 45;
console.log(typeof x); // 列印"number"

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.