《JavaScript 闖關記》之變數和資料型別

劼哥stone發表於2019-03-04

當程式需要將值儲存起來以備將來使用時,便將其賦值給一個變數,值的型別稱作資料型別。

變數

JavaScript 的變數是鬆散型別的,所謂鬆散型別就是可以用來儲存任何型別的資料。換句話說,每個變數僅僅是一個用於儲存值的佔位符而已。定義變數時要使用關鍵字 var 來宣告的,如下所示:

var message;複製程式碼

這行程式碼定義了一個名為 message 的變數,該變數可以用來儲存任何值(像這樣未經過初始化的變數,會儲存一個特殊的值 undefined)。JavaScript 也支援直接初始化變數,因此在定義變數的同時就可以設定變數的值,如下所示:

var message = "hello";複製程式碼

此時,變數 message 中儲存了一個字串值 "hello"。像這樣初始化變數並不會把它標記為字串型別,因此,可以在修改變數值的同時修改值的型別。如下所示:

var message = "hello";
message = 100;           // 有效的語句,不好的寫法複製程式碼

在這個例子中,變數 message 一開始儲存了一個字串值 "hello",然後該值又被一個數字值100取代。雖然我們不建議修改變數所儲存值的型別,但這種操作在 JavaScript 中完全有效。

有一點必須注意,即使用 var 運算子定義的變數是的該作用域中的區域性變數。也就是說,如果在函式中使用 var 定義一個變數,那麼這個變數在函式退出後就會被銷燬,例如:

function test(){
    var message = "hello";  // 區域性變數
}
test();
console.log(message);   // 產生錯誤複製程式碼

這裡,變數 message 是在函式中使用 var 定義的,是區域性變數。當函式被呼叫時,就會建立該變數併為其賦值。而在此之後,這個變數又會立即被銷燬,因此例子中的下一行程式碼就會導致錯誤。不過,可以像下面這樣省略 var 運算子,從而建立一個全域性變數:

function test(){
    message = "hello";  // 全域性變數,不好的寫法
}
test();
console.log(message);   // "hello"複製程式碼

這個例子省略了 var 運算子,因而 message 就成了全域性變數。這樣,只要呼叫一次 test() 函式,這個變數就有了定義,就可以在函式外部的任何地方被訪問到。

雖然省略 var 運算子可以定義全域性變數,但這也不是推薦的做法,因為在區域性作用域中定義全域性變數很難維護,給未經宣告的變數賦值在嚴格模式下會丟擲 ReferenceError 錯誤。

資料型別

JavaScript 中有5種簡單資料型別(也稱為「基本資料型別」或「原始資料型別」):UndefinedNullBooleanNumberString 。還有1種複雜資料型別 ObjectObject 本質上是由一組無序的名值對組成的。JavaScript 不支援任何建立自定義型別的機制,所有值最終都將是上述6種資料型別之一。

typeof 運算子

鑑於 JavaScript 是鬆散型別的,因此需要有一種手段來檢測給定變數的資料型別,typeof 就是負責提供這方面資訊的運算子。對一個值使用 typeof 運算子可能返回下列某個字串:

  • "undefined",如果這個值未宣告或已宣告但未初始化。
  • "boolean",如果這個值是布林值。
  • "string",如果這個值是字串。
  • "number",如果這個值是數值。
  • "object",如果這個值是物件或 null
  • "function",如果這個值是函式。

下面是幾個使用 typeof 運算子的例子:

var message = "some string";
console.log(typeof message);     // "string"
console.log(typeof(message));    // "string"
console.log(typeof 95);          // "number"複製程式碼

從以上例子可以看出,typeof 運算子既可以對變數使用,又可以對字面量使用。由於 typeof 是一個運算子而不是函式,因此例子中的圓括號儘管可以使用,但並不提倡。

typeof null 結果是 "object" 是歷史遺留 Bug,在 ECMAScript 6中,曾經有提案為歷史平反, 將 type null 的值糾正為 "null",但最後該提案被拒。理由是歷史遺留程式碼太多,不如繼續將錯就錯。

從技術角度講,函式在 JavaScript 中是物件,不是一種資料型別。然而,函式也確實有一些特殊的屬性,因此通過 typeof 運算子來區分函式和其他物件是有必要的。

擴充套件閱讀「為什麼 JavaScript 裡面 typeof null 的值是 "object"?」
www.zhihu.com/question/21…

擴充套件閱讀「MDN 之 typeof
developer.mozilla.org/zh-CN/docs/…

擴充套件閱讀「JavaScript 檢測原始值、引用值、屬性」
shijiajie.com/2016/06/20/…

擴充套件閱讀「JavaScript 檢測之 basevalidate.js」
shijiajie.com/2016/06/25/…

Undefined 型別

Undefined 型別只有1個值,即 undefined。使用 var 宣告變數但未對其加以初始化時,這個變數的值就是 undefined,直接使用未宣告的變數會產生錯誤。對未宣告或已宣告但未初始化的變數執行 typeof 運算子會返回 "undefined" 值,例如:

var message;    // 這個變數宣告之後預設取得了 undefined 值
// var age      // 這個變數並沒有宣告

console.log(message);           // "undefined"
console.log(age);               // 產生錯誤
console.log(typeof message);    // "undefined"
console.log(typeof age);        // "undefined"複製程式碼

Null 型別

Null 型別也只有1個值,即 null。它用來表示值的空缺。你可以認為 undefined 是表示系統級的、出乎意料的或類似錯誤的值的空缺,而 null 是表示程式級的、正常的或在意料之中的值的空缺。在下列場景中應當使用 null

  • 用來初始化一個變數,這個變數可能賦值為一個物件。
  • 用來和一個已經初始化的變數比較,這個變數可以是也可以不是一個物件。
  • 當函式的引數期望是物件時,作用引數傳入。
  • 當函式的返回值期望是物件時,作用返回值傳出。

在下列場景中不應當使用 null

  • 不要使用 null 來檢測是否傳入了某個引數。
  • 不要使用 null 來檢測一個未初始化的變數。

Boolean 型別

Boolean 型別是 JavaScript 中使用得最多的一種型別,該型別只有兩個字面值:truefalse。需要注意的是,他們是區分大小寫的,也就是說 TrueFalse(以及其他的混合大小寫形式)都不是 Boolean 值,只是識別符號。

雖然 Boolean 型別的字面值只有兩個,但 JavaScript 中所有型別的值都能使用 if 語句或 Boolean() 函式轉換為對應的 Boolean 值,例如:

var message = "Hello world!";
if (message){
    console.log("Value is true.");  // 被執行
}
var messageAsBoolean = Boolean(message);
console.log(messageAsBoolean);  // true複製程式碼

下表給出了各種資料型別及其對應的轉換規則。

資料型別 轉換為true的值 轉換為false的值
Undefined undefined
Null null
Boolean true false
String 任何非空字串 “”(空字串)
Number 任何非零數字值(包括無窮大) 0和NaN
Object 任何物件

Number 型別

Number 型別是 JavaScript 中最令人關注的資料型別,這種型別使用 IEEE 754 格式來表示整數和浮點數值(浮點數值在某些語言中也被稱為雙精度數值)。和其他程式語言不同,JavaScript 中的所有數字均用浮點數值表示。

擴充套件閱讀「IEEE 754-1985」
en.wikipedia.org/wiki/IEEE_7…

整數

在 JavaScript 中進行算術計算時,所有以八進位制和十六進位制表示的數值最終都將被轉換成十進位制數值。例如:

var a = 10;         // 十進位制
var b = 023;        // 八進位制
var c = 0x12ac;     // 十六進位制
console.log(b);     // 19
console.log(c);     // 4780複製程式碼

八進位制第一位必須是0,後面跟八進位制序列0到7,如果超出了範圍,則忽略前導0,後面的數值當做十進位制解析,例如:089會被解析為89。(八進位制字面量在嚴格模式下是無效的,會丟擲錯誤。)

十六進位制前兩位必須是 0x 或 0X,後跟十六進位制序列0~9、a~f(不區分大小寫),如果超出了範圍,則會報語法錯誤。

浮點數

所謂浮點數值,就是該數值中必須包含一個小數點,並且小數點後面必須至少有一位數字。雖然小數點前面可以沒有整數,但我們不推薦這種寫法。例如:

var a = 1.1;
var b = 0.1;
var c = .1;     // 有效,但不推薦複製程式碼

JavaScript 會不失時機的將浮點數轉換成整數。例如:

var a = 5.;      // 解析成整數5
var b = 5.0;     // 解析成整數5複製程式碼

對於極大或者極小的數值,可採用科學技術法(也稱e表示法)。JavaScript 會將那些小數點後面帶有6個零以上的小於1的浮點數值轉換為以e表示法表示的數值。例如:

var a = 3.14e7;             // 等於31400000
var b = 3.14E-7;            // 等於0.000000314
console.log(0.0000003);     // 3e-7複製程式碼

浮點數值的最高精度是17位小數,但在進行算術計算時其精確度遠遠不如整數,例如:

console.log(0.1 + 0.2);     // 0.30000000000000004複製程式碼

這個舍入誤差會導致無法測試特定的浮點數值,因此,永遠不要測試某個特定的浮點數值

正無窮、負無窮

由於記憶體限制,JavaScript 能表示的數值範圍從 Number.MIN_VALUENumber.MAX_VALUE,並將超出範圍的數轉換成 Number.POSITIVE_INFINITYNumber.NEGATIVE_INFINITY。0作為除數是不會報錯的,正數除以0返回正無窮,負數除以0返回負無窮,0除以0返回NaN。例如:

console.log(Number.MAX_VALUE);       // 最大數 1.7976931348623157e+308
console.log(Number.MIN_VALUE);       // 最小數 5e-324

console.log(Number.POSITIVE_INFINITY);    // 正無窮  Infinity
console.log(Number.NEGATIVE_INFINITY);    // 負無窮 -Infinity

console.log( 1 / 0);     //  Infinity
console.log(-1 / 0);     // -Infinity複製程式碼

JavaScript 提供了 isFinite() 函式,來確定一個數是不是有窮的。例如:

console.log(isFinite(100));         // true
console.log(isFinite(Infinity));    // false複製程式碼

NaN

NaN(not a number),是一個特殊的數值。之所以稱它為「非數值」,是因為它不能參與算數運算,任何涉及 NaN 的操作都返回 NaN。並且 NaN 與任何值都不相等(包括自身)。例如:

console.log(typeof NaN);      // "number"

console.log(0 / 0);                 // NaN
console.log(NaN - NaN);             // NaN
console.log(Infinity - Infinity);   // NaN

var a = NaN;
console.log(a === a);   // false複製程式碼

JavaScript 提供了 isNaN() 函式,來確定一個數是不是 NaN。例如:

console.log(isNaN(100));        //  false
console.log(isNaN("100"));      //  false
console.log(isNaN(true));       //  false
console.log(isNaN("sss"));      //  true
console.log(isNaN(NaN));        //  true複製程式碼

Number()parseInt()parseFloat() 轉型函式

isNaN() 函式在接收到一個值之後,會嘗試使用轉型函式 Number() 將這個值轉換為數值,轉換規則如下:

  • undefined 轉換為 NaN
  • null 轉換為 0;
  • true 轉換為 1false 轉換為 0
  • number 整數轉換為十進位制,小數不變;
  • string 如果只包含十進位制數和小數,則返回對應的數值,如果只包含八進位制數,則忽略前導0返回剩餘部分,如果只包含十六進位制,則返回十進位制數,空字串轉換為0,其它字串轉換為 NaN
  • object 先則呼叫物件的 valueOf() 方法,然後依照前面的規則轉換返回的值。如果轉換的結果是 NaN,則呼叫物件的 toString() 方法,然後再次依照前面的規則轉換返回的字串值。

由於 Number() 轉型函式在轉換字串時不夠理想,因此還有兩個專門用來轉換字串的函式 parseInt()parseFloat() 函式。

parseInt() 函式會忽略字串前面的空格,直至找到第一個非空格字元,只要第一個非空格字元不是數字或者正負號,一律返回 NaN, 如果第一個非空格字元是數字字元,parseInt() 會繼續解析第二個字元,直到解析完所有後續字元或者遇到了一個非數字字元。例如:

console.log(parseInt(""));          // NaN(Number("")返回 0)
console.log(parseInt("123S"));      // 123
console.log(parseInt("12.4"));      // 12複製程式碼

parseFloat() 函式也會忽略字串前面的空格,直至找到第一個非空格字元,只要第一個非空格字元不是數字或者正負號或者小數點,一律返回 NaN, 如果第一個非空格字元是上述字元之一,parseFloat() 會繼續解析第二個字元,直到解析完所有後續字元或者遇到了一個非浮點數值。例如:

console.log(parseFloat("098.2"));       // 98.2
console.log(parseFloat("123.23.23"));   // 123.23複製程式碼

String 型別

String 型別用於表示由零或多個16位 Unicode 字元組成的字元序列,即字串。字串可以由雙引號(”)或單引號(`)表示,因此下面兩種字串的寫法都是有效的:

var firstName = "Nicholas";
var lastName = `Zakas`;複製程式碼

JavaScript 中的這兩種語法形式沒有什麼區別。用雙引號表示的字串和用單引號表示的字串完全相同。不過,以雙引號開頭的字串也必須以雙引號結尾,而以單引號開頭的字串必須以單引號結尾。

String 資料型別包含一些特殊的字元字面量,也叫轉義序列,用於表示非列印字元,或者具有其他用途的字元。例如:
換行、 製表、 空格、
回車、f 進紙、\ 斜槓、` 單引號,在用單引號表示的字串中使用、" 雙引號,在用雙引號表示的字串中使用。

轉義字元可出現在字串中的任意位置,且長度為1。如要在字串中顯示 ,則必須使用 進行轉義。例如:

console.log("
\".length);    // 2
console.log("\hello");        // "hello"(長度為6)複製程式碼

大部分值都可以使用繼承而來的 toString()方法轉換為字串,但 undefinednull 值沒有這個方法。對數值使用 toString() 方法時,可以傳入一個數字基數,以此輸出對應進位制的字串值。例如:

console.log(true.toString());   // "true"

var num = 10;
console.log(num.toString());    // "10"
console.log(num.toString(2));   // "1010"
console.log(num.toString(8));   // "12"
console.log(num.toString(16));  // "a"複製程式碼

在不知道要轉換的值是不是 undefinednull 的情況下,還可以使用轉型函式 String(),這個函式能夠將任何型別的值轉換為字串。String() 函式遵循下列轉換規則:

  • 如果值有 toString() 方法,則呼叫該方法(沒有引數)並返回相應的結果;
  • 如果值是 undefined,則返回 "undefined"
  • 如果值是 null,則返回 "null"
var value;
console.log(String(10));        // "10"
console.log(String(true));      // "true"
console.log(String(null));      // "null"
console.log(String(value));     // "undefined"複製程式碼

Object 型別

JavaScript 中所有物件都繼承自 Object 型別,每個物件都具有下列基本的屬性和方法:

  • constructor:儲存著用於建立當前物件的函式(建構函式)。
  • hasOwnProperty():用於檢查給定的屬性在當前物件例項中是否存在。
  • propertyIsEnumerable():用於檢查給定的屬性是否能夠使用for-in語句來列舉。
  • isPrototypeOf():用於檢查物件是否是傳入物件的原型。
  • toString() 方法:返回物件的字串表示。
  • toLocaleString():返回物件的本地字串表示。
  • valueOf():返回物件的字串、數值或布林值表示(通常與toString()方法的返回值相同)。

Object 本質上是由一組無序的名值對組成,「名稱」部分是一個 JavaScript 字串,「值」部分可以是任何 JavaScript 的資料型別(包括物件和方法)。這使使用者可以根據具體需求,建立出相當複雜的資料結構。

以下兩種方法都可以建立一個空物件,這兩種方法在語義上是相同的。第二種更方便的方法叫作「物件字面量」法。這也是 JSON 格式的核心語法,一般我們優先選擇第二種方法。例如:

var obj = new Object();
var obj = {};   // 好的寫法複製程式碼

「物件字面量」也可以用來在物件例項中定義一個物件:

var obj = {
    name: "Carrot",
    "for": "Max",
    details: {
        color: "orange",
        size: 12
    }
}複製程式碼

物件的屬性可以通過鏈式(chain)表示方法進行訪問:

obj.details.color;       // orange
obj["details"]["size"];  // 12複製程式碼

完成建立後,物件屬性可以通過如下兩種方式進行賦值和訪問:

obj.name = "Simon"      // 賦值
var name = obj.name;    // 訪問

obj["name"] = "Simon";  // 賦值
var name = obj["name"]; // 訪問複製程式碼

關卡

// 挑戰一
console.log(typeof "undefined");  // ???
console.log(typeof null);         // ???複製程式碼
// 挑戰二
var message = "some string";
console.log(typeof massage);    // ???
message = 10000;
console.log(typeof message);    // ???複製程式碼
// 挑戰三
var a;
var b = null;
var c = {};
if(a && b && c){
    console.log("true.");       // ???
}else{
    console.log("false.");      // ???
}複製程式碼
// 挑戰四
console.log(typeof (0 / 0));    // ???
console.log(023 + 123);         // ???複製程式碼
// 挑戰五
console.log(Number("1234S"));   // ???
console.log(parseInt("1234S")); // ???複製程式碼
// 挑戰六
console.log(3.14E-7 === 0.000000314);   // ???
console.log(0.1 + 0.6 === 0.7); // ???
console.log(0.1 + 0.7 === 0.8); // ???
console.log(NaN === NaN);       // ???複製程式碼
// 挑戰七
console.log("
ight
ow");          // ???
console.log("
ight
ow".length);   // ???
console.log(010.toString(2));       // ???複製程式碼
// 挑戰八
// 1、為 person、wife、child 物件新增 weight 屬性,數值分別為 62、36、15。
// 2、為 person 物件新增二胎 child2 子物件,name 為 emma,其他屬性自行發揮。
var person = {
    name: "stone",
    age: 30,
    wife: {
        name: "sohpie",
        age: 30
    },
    child:{
        name: "tommy",
        age: 3
    }
}複製程式碼

挑戰九,深度閱讀下面兩篇文章,提出你的疑問。

「JavaScript 檢測原始值、引用值、屬性」
shijiajie.com/2016/06/20/…

「JavaScript 檢測之 basevalidate.js」
shijiajie.com/2016/06/25/…

更多

關注微信公眾號「劼哥舍」回覆「答案」,獲取關卡詳解。
關注 github.com/stone0090/j…,獲取最新動態。

相關文章