JavaScript原始值與包裝物件的詳細介紹
前言
隨著 javascript 越來越流行,越來越多地開發者開始接觸並使用 javaScript。
同時我也發現,有不少開發者對於 JavaScript 最基本的原始值和包裝物件都沒有很清晰的理解。
那麼本篇文章,就由渣皮來給大家詳細介紹一下它們。
🧐 話不多說,Let's go!
正文
原始型別 (Primitive types)
原始型別也被稱為“基本型別”。
目前在 JavaScript 中有以下幾種原始型別:
- string(字串)
- number(數字)
- boolean(布林)
- null(空)
- undefined(未定義)
- bigint(大整數,ES6)
- symbol(標誌?ES6)
📝 如下:
typeof 'chenpipi'; // "string" typeof 12345; // "number" typeof true; // "boolean" typeof null; // "object" typeof undefined; // "undefined" typeof 12345n; // "bigint" typeof Symbol(); // "symbol"
💡 特別注意
typeof null 雖然返回 "object",但是這不代表 null 就是物件,這其實是 JavaScript 的一個 Bug,且從 JavaScript 誕生以來便如此。
在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示型別的標籤和實際資料值表示的。物件的型別標籤是 0。由於 null 代表的是空指標(大多數平臺下值為 0x00),因此,null 的型別標籤是 0,typeof null 也因此返回 "object"。
The history of “typeof null”:https://2ality.com/2013/10/typeof-null.html
原始值 (Primitive values)
原始值也就是原始型別的值(資料)。
A primitive value is data that is not an object and has no methods.
原始值是一種沒有任何方法的非物件資料。
也就是說,string、number 和 boolean 等原始型別的值本身是沒有任何屬性和方法的。
😨 這個時候嗅覺敏銳的小夥伴是不是已經察覺到有什麼不對勁了?
是孜然!我加了孜然!(手動狗頭並劃掉)
🤓 這裡有一個非常有意思的點,但是在討論這個問題之前,先讓我們認識下包裝物件。
包裝物件 (Wrapper objects)
除了 null 和 undefined 外的原始型別都有其相應的包裝物件:
- String(字串)
- Number(數字)
- Boolean(布林)
- BigInt(大整數,ES6)
- Symbol(標誌?ES6)
物件 (Object)
物件是引用型別。
首先程式設計客棧,包裝物件本身是一個物件,也是函式。
String instanceof Object; // true String instanceof Function; // true
建構函式 (Constructor)
例項 (Instance)
其中 String、Number 和 Boolean 均支援使用 new 運算子來建立對應的包裝物件例項。
📝 例如 String 的宣告(節選):
interface StringConstructor { new(value?: any): String; (value?: any): string; readonly prototype: String; } declare var String: StringConstructor;
📝 使用 new 運算子得到的資料是物件(Object):
// 字串 typeof 'pp'; // "string" typeof new String('pp'); // "object" new String() instanceof Object; // true // 數字 typeof 123; // "number" typeof new Number(123); // "object" new Number() instanceof Object; // true // 布林 typeof true; // "boolean" typeof new Boolean(true); // "object" new Boolean() instanceof Object; // true
📝 我們可以呼叫包裝物件例項的 valueOf() 函式來獲取其原始值:
// 字串 let s = new String('pp'); s.valueOf(); // "pp" typeof s.valueOf(); 程式設計客棧 // "string" // 數字 let n = new Number(123); n.valueOf(); // 123 typeof n.valueOf(); // "number" // 布林 let b = new Boolean(true); b.valueOf(); // true typeof b.valueOf(); // "boolean"
“異類” (Attention)
而 BigInt 和 Symbol 都屬於“不完整的類”,不支援 new 運算子。
📝 例如 BigInt 的宣告(節選):
interface BigIntConstructor { (value?: any): bigint; readonly prototype: BigInt; } declare var BigInt: BigIntConstructor;
可以看到 BigInt 的宣告中沒有 new 運算子相關函式。
普通函式 (程式設計客棧Function)
包裝物件也可以作為普通函式來使用。
其中 String()、Number() 和 Boolean() 函式都可以用來對任意型別的資料進行顯式型別轉換。
另外 Object() 函式也可用於顯式型別轉換,但本文不再展開。
String
📝 示例程式碼:
typeof String(); // "string" String(); // "" String('pp'); // "pp" String(123); // "123" String(true); // "true" String(false); // "false" String(null); // "null" String(undefined); // "undefined" String([]); // "" String({}); // "[object Object]"
💡 小貼士 1
當我們使用 String() 函式來轉換物件時,JavaScript 會先訪問物件上的 toString() 函式,如果沒有實現,則會順著原型鏈向上查詢。
🌰 舉個栗子:執行 String({ toString() { return 'pp'; } }) 返回的結果是 "pp",並非 "[object Object]"。
所以 String() 函式並不能夠用來判斷一個值是否為物件(會翻車)。
💡 小貼士 2
常用的判斷物件的方式為 Object.prototype.toString({}) === '[object Object]'。
🌰 舉個栗子:執行 Object.prototype.toString({ toString() { return 'pp'; } }) 返回的是 "[object Object]"。
Number
📝 示例程式碼:
typeof Number(); // "number" Number(); // 0 Number(''); // 0 Number('pp'); // NaN Number(123); // 123 Number(true); // 1 Number(false); // 0 Number(null); // 0 Number(undefined); // NaN Number([]); // 0 Number({}); // NaN
💡 小貼士
對於 Number() 函式來說,可能最實用的轉換就是將 true 和 false 轉換為 1 和 0 吧。
Boolean
📝 示例程式碼:
typeof Boolean(); // "boolean" Boolean(); // false Boolean(''); // false Boolean('pp'); // true Boolean(0); // false Boolean(1); // true Boolean(null); // false Boolean(undefined); // false Boolean([]); // true Boolean({}); // true
💡 小貼士
某些情況下,我們會在資料中使用 0 和 1 來表示真假狀態,此時就可以使用 Boolean() 進行狀態的判斷。
BigInt
BigInt() 函式用於將整數轉換為大整數。
該函式接受一個整數作為引數,傳入引數若為浮點數或任何非數字型別資料都會報錯。
📝 示例程式碼:
BigInt(123); // 123n BigInt(123n); // 123n typeof 123n; // "bigint" typeof BigInt(123); // "bigint"
BigInt & Number
需要注意的是,BigInt 和 Number 是不嚴格相等(寬鬆相等)的。
📝 示例程式碼:
123n === 123; // false 123n == 123; // true
Symbol
Symbol() 函式用於建立一個 symbol 型別的值。
該函式接受一個字串作為描述符(引數),如果傳入其他型別的值則會被轉換為字串(除了 undefined)。
注意,每一個 symbol 值都是獨一無二的,即使它們的描述符都是一樣的。
且 symbol 型別的資料只能通過 Symbol() 函式來建立。
📝 示例程式碼:
// 後面的返回值是 Devtools 模擬出來的,並非實際值 Symbol('pp'); // Symbol(pp) Symbol(123); // Symbol(123) Symbol(null); // Symbol(null) Symbol({}); // Symbol([object Object]) // 型別 typeof Symbol('pp'); // "symbol" Symbol('pp') === Symbol('pp'); // false // 描述符 Symbol('pp').description; // "pp" Symbol(123).description; // "123" Symbol({}).description; // "[object Object]" Symbol().description; // undefined Symbol(undefined).description; // undefined
原始值不是物件 (Primitive not Object)
🎃 有意思的來了~
沒有屬性和方法 (No properties,no functions)
本文前面有提到:「原始值是一種沒有任何方法的非物件資料。」
我們都知道物件(Object)上可以有屬性和方法。
但是字串不是物件,所以你不能給字串增加屬性。
📝 做個小實驗:
let a = 'chenpipi'; console.log(a.length); // 8 // 嘗試增加新的屬性 a.name = '吳彥祖'; console.log(a.name); // undefined // 嘗試修改已有的屬性 typeof a.slice; // "function" a.slice = null; typeof a.slice; // "function"
🎬 渣皮小劇場
此時一位頭鐵的小夥伴使用了反駁技能。
渣皮你別在這忽悠人了,我平時寫 Bug 哦不寫程式碼的時候明明可以呼叫到字串、數字和布林值上的方法!
📝 比如下面這段程式碼,能夠正常執行並得到符合預期的結果:
// 字串 let s = 'chenpipi'; s.toUpperCase(); // "CHENPIPI" 'ChenPiPi'.slice(4); // "PiPi" // 數字 let n = 123; n.toString(); // "123" (123.45).toFixed(2); // "123.5" // 布林值 let b = true; b.toString(); // "true" false.toString(); // "false"
💡 無用小知識
有沒有發現,數字的字面量後面不能直接呼叫函式?例如執行 123.toString() 會報 SyntaxError(語法錯誤)。
這是因為數字(浮點數)本身會用到小數點 .,而呼叫函式也需要用小數點,這時就出現了歧義(字串和布林值就沒有這種煩惱)。
對於這種情況,我們可以使用括號 () 將數字包裹起來,如 (123).toString();或者使用兩個連續的小數點 .. 來呼叫函式,如 123..toString()。
🤔 奇了怪了
那麼既然字串不是物件,那麼為什麼字串會有屬性和方法呢?
轉念一想,數字就是數字,數字身上怎麼會有方法呢?
這確實不符合邏輯,但是這又與實際相矛盾。
咋回事呢???
替身使者 (I can't translate this)
答案揭曉~
😎 暗中操作
以字串(string)為例,當我們在程式碼中讀取字串的屬性或者方法時, JavaScript 會靜默地執行下面的操作:
- 將字串通過 new String() 的方式來建立一個臨時的包裝物件例項;
- 通過建立的物件來執行我們的程式碼邏輯(讀取屬性或執行函式);
- 臨時物件不再使用,可以被銷燬。
📝 如下面的栗子:
let a = 'chenpipi'; console.log(a); // "chenpipi" // ------------------------------ let b1 = a.length; console.log(b1); // 8 // 上面的程式碼相當於: let b2 = (new String(a)).length; console.log(b2); // 8 // ------------------------------ let c1 = a.toUpperCase(); console.log(c1); // "CHENPIPI" // 上面的程式碼相當於: let c2 = (new String(a)).toUpperCase(); console.log(c2); // "CHENPIPI"
數字(number)和布林值(boolean)同理,但數字通過 new Number() 來建立臨時物件,而布林值則通過 new Boolean() 來建立。
📝 除了上面的例子,最有力的證明,就是他們的建構函式:
'chenpipi'.constructor === String; // true (12345).constructor === Number; // true true.constructor === Boolean; // true
這一切都是 JavaScript 在暗中完成的,且過程中產生的臨時物件都是一次性的(用完就丟)。
😮 原來如此
蕪湖,這麼一來就說得通了!
這也就能解釋為什麼我們能夠訪問字串上的屬性和方法,卻不能增加或修改屬性。
那是因為我們實際操作的目標其實是 JavaScript 建立的臨時物件,而並非字串本身!
所以我們的增加或修改操作實際上是生效了的,只不過是在臨時物件上生效了!
📝 就像這樣:
// 程式碼中:
let a = 'chenpipi';
a.name = '吳彥祖';
console.log(a.name); // undefined
// 相當於:
let a = 'chenpipi';
(new String(a)).name = '吳彥祖';
console.log(a.name); // undefined
// 相當於:
let a = 'chenpipi';
let temp = newhttp://www.cppcns.com String(a);
temp.name = '吳彥祖';
console.log(a.name); // undefined
總結 (Summary)
🎉 以上,就是本篇文章的全部內容了。
最後我們來總結一下:
- 多數原始型別都有相應的包裝物件;
- 有些包裝物件可以被 new,有些不行;
- 包裝物件一般被用來進行顯式的型別轉換;
- 物件上有屬性和方法;
- 原始值上沒有屬性和方法;
- 原始值上也不程式設計客棧能有屬性和方法;
- 但我們可以像操作物件一樣來操作原始值;
- 這是因為 JavaScript 在執行程式碼的時候偷偷搞小動作;
- JavaScript 會用臨時的包裝物件來替原始值執行操作。
我們平時寫程式碼的時候不太會注意到這件事,實際上這些也不會影響到我們寫程式碼。
所以,這篇文章不就白看啦?
🙉 是,也不全是~
知己知彼,百戰百勝。
學會以上這些無用小知識,也算是對 JavaScript 有了更深的理解了吧,至少還能用來吹牛皮(手動狗頭~)。
相關資料
《JavaScript 高階程式設計(第4版)》
《JavaScript 權威指南(第6版)》
Primitive - MDN:https://developer.mozilla.org/en-US/docs/Glossary/Primitive
The history of “typeof null”:https://2ality.com/2013/10/typeof-null.html
到此這篇關於JavaScript原始值與包裝物件的文章就介紹到這了,更多相關js原始值與包裝物件內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!