面試說:聊聊JavaScript中的數據類型

来源:https://www.cnblogs.com/gopal/archive/2022/09/24/16725158.html
-Advertisement-
Play Games

前言 請講下 JavaScript 中的數據類型? 前端面試中,估計大家都被這麼問過。 答:Javascript 中的數據類型包括原始類型和引用類型。其中原始類型包括 null、undefined、boolean、string、symbol、bigInt、number。引用類型指的是 Object。 ...


前言

請講下 JavaScript 中的數據類型?

前端面試中,估計大家都被這麼問過。

答:Javascript 中的數據類型包括原始類型和引用類型。其中原始類型包括 nullundefinedbooleanstringsymbolbigIntnumber。引用類型指的是 Object

沒錯,我也是這麼回答的,只是這通常是第一個問題,由這個問題可以引出很多很多的問題,比如

  • NullUndefined 有什麼區別?前端的判空有哪些需要註意的?
  • typeof null 為什麼是 object?
  • 為什麼 ES6 要提出 Symbol
  • BigInt 解決了什麼問題?
  • 為什麼 0.1 + 0.2 !== 0.3? 你如何解決這個問題?
  • 如何判斷一個值是數組?
  • ...

弱類型語言

因為 JavaScript 是弱類型語言或者說是動態語言。這意味著你不需要提前聲明變數的類型,在程式運行的過程中,類型會被自動確定,也就是說你可以使用同一個變數保存不同類型的值

var foo = 42;  // foo is a Number now
foo = "bar";  // foo is a String now
foo = true;   // foo is a Boolean now

這一特性給我們帶來便利的同時,也給我們帶來了很多的類型錯誤。試想一下,假如 JS 說是強類型語言,那麼各個類型之間沒法轉換,也就有了一層隔閡或者說一層保護,會不會更加好維護呢?——這或許就是 TypeScript 誕生的原因。

JavaScript 的數據類型掌握,是一個前端最基本的知識點

null 還是 undefinded

定義

undefined 表示未定義的變數。null 值表示一個空對象指針。

追本溯源: 一開始的時候,JavaScript 設計者 Brendan Eich 其實只是定義了 nullnull 像在 Java 里一樣,被當成一個對象。但是因為 JavaScript 中有兩種數據類型:原始數據類型和引用數據類型。Brendan Eich 覺得表示"無"的值最好不是對象。

所以 Javascript 的設計是 null是一個表示"無"的對象,轉為數值時為0;undefined是一個表示"無"的原始值,轉為數值時為NaN。

Number(null)
// 0

5 + null
// 5

Number(undefined)
// NaN

5 + undefined
// NaN

Null 和 Undefined 的區別和應用

null表示"沒有對象",即該處不應該有值。,典型的用法如下

  1. 作為函數的參數,表示該函數的參數不是對象。
  2. 作為對象原型鏈的終點。
Object.getPrototypeOf(Object.prototype)
// null

undefined表示"缺少值",就是此處應該有一個值,但是還沒有定義。典型用法是:

  1. 變數被聲明瞭,但沒有賦值時,就等於 undefined
  2. 調用函數時,應該提供的參數沒有提供,該參數等於undefined
  3. 對象沒有賦值的屬性,該屬性的值為 undefined
  4. 函數沒有返回值時,預設返回 undefined
var i;
i // undefined

function f(x){console.log(x)}
f() // undefined

var  o = new Object();
o.p // undefined

var x = f();
x // undefined

判空應該註意什麼?

javaScript 五種空值和假值,分別為 undefined,null,false,"",0,NAN

這有時候很容易導致一些問題,比如

let a = 0;
console.log(a || '/'); // 本意是只要 a 為 null 或者 Undefined 的時候,輸出 '/',但實際上只要是我們以上的五種之一就輸出 '/'

當然我們可以寫成

let a = 0;
if (a === null || a === undefined) {
  console.log('/');
} else {
  console.log(a);
}

始終不是很優雅,所以 ES規範 提出了空值合併操作符(??)

空值合併操作符(??)是一個邏輯操作符,當左側的操作數為 null 或者 undefined 時,返回其右側操作數,否則返回左側操作數。

上面的例子可以寫成:

let a = 0;
console.log(a??'/'); // 0

typeof null——JS 犯的錯

typeof null // "object"

JavaScript 中的值是由一個表示類型的標簽和實際數據值表示的。第一版的 JavaScript 是用 32 位比特來存儲值的,且是通過值的低 1 位或 3 位來識別類型的,對象的類型標簽是 000。如下

  • 1:整型(int)
  • 000:引用類型(object)
  • 010:雙精度浮點型(double)
  • 100:字元串(string)
  • 110:布爾型(boolean)

但有兩個特殊值:

  • undefined,用整數−2^30(負2的30次方,不在整型的範圍內)
  • null,機器碼空指針(C/C++ 巨集定義),低三位也是000

由於 null 代表的是空指針(低三位也是 000 ),因此,null 的類型標簽是 000typeof null 也因此返回 "object"。

這個算是 JavaScript 設計的一個錯誤,但是也沒法修改,畢竟修改的話,會影響目前現有的代碼

Number——0.1+0.2 !== 0.3

現象

JavaScript 會存在類似如下的現象

0.1 + 0.2 
0.30000000000000004

原因

我們在對浮點數進行運算的過程中,需要將十進位轉換成二進位。十進位小數轉為二進位的規則如下:

對小數點以後的數乘以2,取結果的整數部分(不是1就是0),然後再用小數部分再乘以2,再取結果的整數部分……以此類推,直到小數部分為0或者位數已經夠了就OK了。然後把取的整數部分按先後次序排列

根據上面的規則,最後 0.1 的表示如下:

0.000110011001100110011(0011無限迴圈)……

所以說,精度丟失並不是語言的問題,而是浮點數存儲本身固有的缺陷。

JavaScript 是以 64 位雙精度浮點數存儲所有 Number 類型值,按照 IEEE754 規範,0.1 的二進位數只保留 52 位有效數字,即

1.100110011001100110011001100110011001100110011001101 * 2^(-4)

同理,0.2的二進位數為

1.100110011001100110011001100110011001100110011001101 * 2^(-3)

這樣在進位之間的轉換中精度已經損失。運算的時候如下

0.00011001100110011001100110011001100110011001100110011010
+0.00110011001100110011001100110011001100110011001100110100
------------------------------------------------------------
=0.01001100110011001100110011001100110011001100110011001110

所以導致了最後的計算結果中 0.1 + 0.2 !== 0.3

如何解決

  • 將數字轉成整數
function add(num1, num2) {
 const num1Digits = (num1.toString().split('.')[1] || '').length;
 const num2Digits = (num2.toString().split('.')[1] || '').length;
 const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
 return (num1 * baseNum + num2 * baseNum) / baseNum;
}
  • 類庫
    NPM 上有許多支持 JavaScriptNode.js 的數學庫,比如 math.jsdecimal.js,D.js 等等

  • ES6
    ES6Number 對象上新增了一個極小的常量——Number.EPSILON

Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"

引入一個這麼小的量,目的在於為浮點數計算設置一個誤差範圍,如果誤差能夠小於 Number.EPSILON,我們就可以認為結果是可靠的。

function withinErrorMargin (left, right) {
    return Math.abs(left - right) < Number.EPSILON
}
withinErrorMargin(0.1+0.2, 0.3)

未來的解決方案——TC39 Decimal proposal

目前處於 Stage 1 的提案。後文提到的 BigInt 擴展的是 JS 的正數邊界,超過 2^53 安全整數問題。Decimal 則是解決JS的小數問題-2^53。這個議案在JS中引入新的原生類型:decimal(尾碼m),聲明這個數字是十進位運算。

let zero_point_three = 0.1m + 0.2m;
assert(zero_point_three === 0.3m);
// 提案中的例子
function calculateBill(items, tax) {
  let total = 0m;
  for (let {price, count} of items) {
    total += price * BigDecimal(count);
  }
  return BigDecimal.round(total * (1m + tax), {maximumFractionDigits: 2, round: "up"});
}

let items = [{price: 1.25m, count: 5}, {price: 5m, count: 1}];
let tax = .0735m;
console.log(calculateBill(items, tax));

拓展——浮點數在記憶體中的存儲

所以最終浮點數在記憶體中的存儲是什麼樣的呢?EEE754 對於浮點數表示方式給出了一種定義

(-1)^S * M * 2^E

各符號的意思如下:S,是符號位,決定正負,0時為正數,1時為負數。M,是指有效位數,大於1小於2。E,是指數位。

Javascript 是 64 位的雙精度浮點數,最高的 1 位是符號位S,接著的 11 位是指數E,剩下的 52 位為有效數字M。

可藉助 這個可視化工具 查看浮點數在記憶體中的二進位表示)

BigInt——突破最大的限制

JavaScriptNumber 類型為 雙精度IEEE 754 64位浮點類型。
在 JavaScript 中最大的值為 2^53

BigInt 任意精度數字類型,已經進入stage3規範。BigInt 可以表示任意大的整數。要創建一個 BigInt ,我們只需要在任意整型的字面量上加上一個 n 尾碼即可。例如,把123 寫成 123n。這個全局的 BigInt(number) 可以用來將一個 Number 轉換為一個 BigInt,言外之意就是說,BigInt(123) === 123n。現在讓我來利用這兩點來解決前面我們提到問題:

Symbol——我是獨一無二最靚的仔

定義

ES6 引入了一種新的原始數據類型 Symbol,表示獨一無二的值

let s = Symbol();

typeof s
// "symbol"

應用場景

  • 定義一組常量,保證這組常量都是不相等的。消除魔法字元串

  • 對象中保證不同的屬性名

let mySymbol = Symbol();

// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';

// 第二種寫法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"
  • Vue 中的 provideinjectprovideinject 可以允許一個祖先組件向其所有子孫後代註入一個依賴,不論組件層次有多深,併在起上下游關係成立的時間里始終生效。但這個侵入性也是非常強的,使用 Symbols 作為 key 可以避免對減少對組件代碼干擾,不會有相同命名等問題

數組——對象中一個特殊的存在

請說下判斷 Array 的方法?

為什麼會問這個問題?

因為數組是一個特殊的存在,是我們平時接觸得最多的數據結構之一,它是一個特殊的對象,它的索引就是“普通對象”的 key 值。但它又擁有一些“普通對象”沒有的方法,比如 map

typeofjavascript 原生提供的判斷數據類型的運算符,它會返回一個表示參數的數據類型的字元串。但我們不能通過 typeof 判斷是否為數組。因為 typeof 數組和普通對象以及 null,都是返回 "object"

const a = null;
const b = {};
const c= [];
console.log(typeof(a)); //Object
console.log(typeof(b)); //Object
console.log(typeof(c)); //Object

判斷數組的方法

  • Object.prototype.toString.call()
    每一個繼承 Object 的對象都有 toString 方法,如果 toString 方法沒有重寫的話,會返回 [Object type],其中 type 為對象的類型
const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"
  • Array.isArray()
const a = [];
const b = {};
Array.isArray(a);//true
Array.isArray(b);//false

Array.isArray()ES5 新增的方法,當不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 實現

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}
  • instanceofinstanceof 運算符可以用來判斷某個構造函數的 prototype 屬性所指向的對象是否存在於另外一個要檢測對象的原型鏈上。因為數組的構造函數是 Array,所以可以通過以下判斷。註意:因為數組也是對象,所以 a instanceof Object 也為 true
const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true,在數組的原型鏈上也能找到Object構造函數
console.log(b instanceof Array);//false
  • constructor。通過構造函數實例化的實例,擁有一個 constructor 屬性。
function B() {};
let b = new B();
console.log(b.constructor === B) // true

而數組是由一個叫 Array 的函數實例化的。所以可以

let c = [];
console.log(c.constructor === Array) // true

註意:constructor 是會被改變的。所以不推薦這樣判斷

let c = [];
c.constructor = Object;
console.log(c.constructor === Array); // false

結論

根據上面的描述,個人推薦的判斷方法有如下的優先順序

isArray > Object.prototype.toString.call() > instanceof > constructor

總結

本文針對於 JavaScript 中部分常見的數據類型問題進行了討論和分析。希望對大家面試或者平時的工作都能有所幫助。另外可能沒有提及的比如類型轉換等有機會再討論一下

最後,歡迎大家關註我的公眾號——前端雜貨鋪,技術問題多討論~

參考


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在Winform開發中中,我們為了方便客戶選擇,往往使用系統的字典數據選擇,畢竟選擇總比輸入來的快捷、統一,一般我們都會簡單封裝一下,以便方便對控制項的字典值進行展示處理,本篇隨筆介紹DevExpress控制項的幾種常見的字典綁定展示方式,希望我們在實際WInform項目中使用到。 ...
  • 在上一個文章中,傳送門,給大家介紹了怎麼在配置文件中使用 Kestrel 部署 Https,正好今天有小伙伴穩問到:可以通過代碼的方式實現 Kestrel 的 Https 的部署嗎?答案是肯定的,我們這次一樣去不是多個功能變數名稱。 在使用代碼實現中,我是主要使用到 ListenOptions.UseHtt ...
  • 出現的原因一般是伺服器的root用戶沒有開啟訪問許可權,一般來說值允許本地的訪問。 解決方法: 一:第一種方法 1、首先打開xshell連接伺服器的終端 2、以root許可權登錄 mysql -u root -p 如果不知道伺服器的root密碼的話就去寶塔面板那裡修改 3、選擇mysql mysql> ...
  • 1.1 資料庫系統概述: 1.1.1資料庫的4個基本概念 資料庫的四個基本概念 - 數據 - 資料庫 - 資料庫管理系統 - 資料庫系統 數據:數據是資料庫中存儲的基本對象 數據是描述事物的一個符號,可以描述數字、圖形、聲音、語言等待,但都要經過數字化後存入計算器 資料庫(簡稱DB):資料庫是長期存 ...
  • 前文回顧:實現一個簡單的Database1(譯文) 譯註:cstsck在github維護了一個簡單的、類似sqlite的資料庫實現,通過這個簡單的項目,可以很好的理解資料庫是如何運行的。本文是第二篇,主要是實現資料庫的前端組件,編譯器與虛擬機部分功能 Part 2 世界上最簡單的SQL編譯器與虛擬機 ...
  • 在最新一屆國際資料庫頂級會議 ACM SIGMOD 2022 上,來自清華大學的李國良和張超兩位老師發表了一篇論文:《HTAP Database: What is New and What is Next》,並做了 《HTAP Database:A Tutorial》 的專項報告。這幾期學術分享會的 ...
  • 問題:【Chrome插件 Chrome extension 】報錯 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. 在看一個別人插件的時候發現一個如上所述的報錯,雖然 ...
  • 以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 微信公眾號「englyf」https://www.cnblogs.com/englyf/ 對於閉包的理解,其實可以歸納為,在創建函數時,同時創建了一個集合,這個集合是用來保存函數內的各個變數(無論是內部定義的,還是外部定義的),當調用函數時,變數 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...