(現代JavaScript 之JavaScript 基礎知識 第一部分 第二章 易錯總結)
2.1 Hello, world!
1. 現代 JavaScript 中已經不這樣使用了。這些註釋是用於不支持 <script>
標籤的古老的瀏覽器隱藏 JavaScript 代碼的。由於最近 15 年內發佈的瀏覽器都沒有這樣的問題,因此這種註釋能幫你辨認出一些老掉牙的代碼。
<script type=…> <script language=…>
2. 一個單獨的 <script>
標籤不能同時有 src 特性和內部包裹的代碼。
<script src="file.js">
alert(1); // 此內容會被忽略,因爲設定了 src
</script>
2.2 代碼結構
1. 在大多數的編輯器中,一行代碼可以使用<kbd>Ctrl+/</kbd> 熱鍵進行單行註釋,<kbd> Ctrl+Shift+/ </kbd>的熱鍵可以進行多行註釋
2. 不要在 /*...*/
內嵌套另一個 /*...*/
。
2.3 現代模式,"use strict"
1. 沒有辦法取消 "use strict"
沒有類似於 "no use strict"
這樣的指令可以使程序返回默認模式。一旦進入了嚴格模式,就沒有回頭路了。
2. 當你使用 開發者控制檯 運行代碼時,請注意它默認是不啓動 "use strict"
的。你可以嘗試搭配使用 <kbd>Shift+Enter</kbd> 按鍵去輸入多行代碼,然後將 "use strict"
放在代碼最頂部。
3. 目前我們歡迎將 "use strict"; 寫在腳本的頂部。稍後,當你的代碼全都寫在了 class 和 module 中時,你則可以將 "use strict"
; 這行代碼省略掉。
2.4 變量
1. var 關鍵字與 let 大體 相同,也用來聲明變量,但稍微有些不同,也有點“老派”。
2. 額外聲明一個變量絕對是利大於弊的。現代的 JavaScript 壓縮器和瀏覽器都能夠很好地對代碼進行優化,所以不會產生性能問題。爲不同的值使用不同的變量可以幫助引擎對代碼進行優化。
2.5 數據類型
8 種基本的數據類型
- Number 類型
特殊數值:Infinity、-Infinity 和 NaN,數學運算是安全的 - BigInt 類型
“number” 類型無法表示大於 (253-1)(即 9007199254740991),或小於 -(253-1) 的整數。這是其內部表示形式導致的技術限制。可以通過將 n 附加到整數字段的末尾來創建 BigInt 值。 - String 類型
反引號:let phrase = ``can embed another ${str}``;
- Boolean 類型(邏輯類型)
用於 true 和 false。 - “null” 值
JavaScript 中的 null 僅僅是一個代表“無”、“空”或“值未知”的特殊值。 - “undefined” 值
undefined 的含義是 未被賦值。如果一個變量已被聲明,但未被賦值,那麼它的值就是 undefined - object 類型
用於更復雜的數據結構。 - symbol 類型
用於唯一的標識符。
typeof 運算符
- 兩種形式:typeof x 或者 typeof(x)。
- typeof null 會返回 "object" —— 這是 JavaScript 編程語言的一個錯誤,實際上它並不是一個 object。
2.6 交互:alert、prompt 和 confirm
alert
alert("Hello");
彈出的帶有信息的小窗口被稱爲 模態窗。
prompt
result = prompt(title, [default]);
title 顯示給用戶的文本
default 可選的第二個參數,指定 input 框的初始值。(不是必須的)
confirm
result = confirm(question);
點擊確定返回 true,點擊取消返回 false
2.7 類型轉換
字符串轉換
字符串轉換最明顯。false 變成 "false",null 變成 "null" 等。
數字型轉換
值 | 變成…… |
---|---|
undefined | NaN |
null | 0 |
true 和 false | 1 and 0 |
string | 去掉首尾空格後的純數字字符串中含有的數字。如果剩餘字符串爲空,則轉換結果爲 0。否則,將會從剩餘字符串中“讀取”數字。當類型轉換出現 error 時返回 NaN。 |
請注意 null 和 undefined 在這有點不同:null 變成數字 0,undefined 變成 NaN。
布爾型轉換
轉換規則如下:
- 直觀上爲“空”的值(如 0、空字符串、null、undefined 和 NaN)將變爲 false。
- 其他值變成 true。
- 對 "0" 和只有空格的字符串(比如:" ")進行布爾型轉換時,輸出結果爲 true。
請注意:包含 0 的字符串 "0" 是 true
2.8 基礎運算符,數學
數字轉化,一元運算符 +
但是如果運算元不是數字,加號 + 則會將其轉化爲數字。
// 轉化非數字
alert( +true ); // 1
alert( +"" ); // 0
2.9 值的比較
一個有趣的現象
let a = 0;
alert( Boolean(a) ); // false
let b = "0";
alert( Boolean(b) ); // true
alert(a == b); // true!
對 null 和 undefined 進行比較
當使用嚴格相等 === 比較二者時
它們不相等,因爲它們屬於不同的類型。
alert( null === undefined ); // false
當使用非嚴格相等 == 比較二者時
JavaScript 存在一個特殊的規則,會判定它們相等。它們倆就像“一對戀人”,僅僅等於對方而不等於其他任何的值(只在非嚴格相等下成立)。
alert( null == undefined ); // true
當使用數學式或其他比較方法 < > <= >= 時:
null/undefined 會被轉化爲數字:null 被轉化爲 0,undefined 被轉化爲 NaN。
alert( null > 0 ); // (1) false
alert( null == 0 ); // (2) false
alert( null >= 0 ); // (3) true
特立獨行的 undefined
alert( undefined > 0 ); // false (1)
alert( undefined < 0 ); // false (2)
alert( undefined == 0 ); // false (3)
值的比較練習題
5 > 4 → true
"apple" > "pineapple" → false
"2" > "12" → true
undefined == null → true
undefined === null → false
null == "\n0\n" → false
null === +"\n0\n" → false
2.11 邏輯運算符
||(或)
result = value1 || value2 || value3;
一個或運算 || 的鏈,將返回第一個真值,如果不存在真值,就返回該鏈的最後一個值。
&&(與)
result = value1 && value2 && value3;
與運算返回第一個假值,如果沒有假值就返回最後一個值。
與運算 && 的優先級比或運算 || 要高。
!(非)
兩個非運算 !! 有時候用來將某個值轉化爲布爾類型:
alert( !!"non-empty string" ); // true
alert( !!null ); // false
下面的代碼將會輸出什麼?
alert( alert(1) || 2 || alert(3) );
答案:首先是 1,然後是 2.
與運算連接的 alerts 的結果是什麼?
alert( alert(1) && alert(2) );
答案:1,然後 undefined。
或運算、與運算、或運算串聯的結果
alert( null || 2 && 3 || 4 );
答案:3。
2.12 空值合併運算符 '??'
a ?? b 的結果是:
- 如果 a 是已定義的,則結果爲 a,
- 如果 a 不是已定義的,則結果爲 b。
重寫 result = a ?? b
result = (a !== null && a !== undefined) ? a : b;
舉例
let firstName = null;
let lastName = null;
let nickName = "Supercoder";
// 顯示第一個已定義的值:
alert(firstName ?? lastName ?? nickName ?? "Anonymous"); // Supercoder
與 || 比較
它們之間重要的區別是:
- || 返回第一個 真 值。
- ?? 返回第一個 已定義的 值。
如果沒有明確添加括號,不能將其與 || 或 && 一起使用。
2.13 循環:while 和 for
普通 break 只會打破內部循環
標籤 是在循環之前帶有冒號的標識符:
outer: for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
let input = prompt(`Value at coords (${i},${j})`, '');
// 如果是空字符串或被取消,則中斷並跳出這兩個循環。
if (!input) break outer; // (*)
// 用得到的值做些事……
}
}
我們還可以將標籤移至單獨一行:
outer:
for (let i = 0; i < 3; i++) { ... }
continue
指令也可以與標籤一起使用。在這種情況下,執行跳轉到標記循環的下一次迭代。
只有在循環內部才能調用 break/continue,並且標籤必須位於指令上方的某個位置。
break label; // 無法跳轉到這個標籤
label: for (...)
易錯題
以下兩個循環的 alert 值是否相同?
前綴形式 ++i: 從 1 到 4
let i = 0;
while (++i < 5) alert( i );
後綴形式 i++:從 1 到 5
let i = 0;
while (i++ < 5) alert( i );
2.14 "switch" 語句
共享同一段代碼的幾個 case 分支可以被分爲一組:
let a = 3;
switch (a) {
case 4:
alert('Right!');
break;
case 3: // (*) 下面這兩個 case 被分在一組
case 5:
alert('Wrong!');
alert("Why don't you take a math class?");
break;
default:
alert('The result is strange. Really.');
}
2.15 函數
默認值
如果未提供參數,那麼其默認值則是 undefined。
如果我們想在本示例中設定“默認”的 text,那麼我們可以在 = 之後指定它:
function showMessage(from, text = "no text given") {
alert( from + ": " + text );
}
showMessage("Ann"); // Ann: no text given
這裏 "no text given" 是一個字符串,但它可以是更復雜的表達式,並且只會在缺少參數時纔會被計算和分配。所以,這也是可能的:
function showMessage(from, text = anotherFunction()) {
// anotherFunction() 僅在沒有給定 text 時執行
// 其運行結果將成爲 text 的值
}
後備的默認參數
我們可以拿它跟 undefined 做比較:
function showMessage(text) {
if (text === undefined) {
text = 'empty message';
}
alert(text);
}
showMessage(); // empty message
我們可以使用 || 運算符:
// 如果 "text" 參數被省略或者被傳入空字符串,則賦值爲 'empty'
function showMessage(text) {
text = text || 'empty';
...
}
現代 JavaScript 引擎支持 空值合併運算符 ??,當可能遇到其他假值時它更有優勢,如 0 會被視爲正常值不被合併:
// 如果沒有傳入 "count" 參數,則顯示 "unknown"
function showCount(count) {
alert(count ?? "unknown");
}
showCount(0); // 0
showCount(null); // unknown
showCount(); // unknown
空值的 return 或沒有 return 的函數返回值爲 undefined
函數命名
函數以 XX 開始……
- "get…" —— 返回一個值,
- "calc…" —— 計算某些內容,
- "create…" —— 創建某些內容,
- "check…" —— 檢查某些內容並返回 boolean 值,等。
2.16 函數表達式
在 JavaScript 中,函數不是“神奇的語言結構”,而是一種特殊的值。
函數聲明:
function sayHi() {
alert( "Hello" );
}
函數表達式:
let sayHi = function() {
alert( "Hello" );
};
我們可以複製函數到其他變量:
function sayHi() { // (1) 創建
alert( "Hello" );
}
let func = sayHi; // (2) 複製
func(); // Hello // (3) 運行復制的值(正常運行)!
sayHi(); // Hello // 這裏也能運行(爲什麼不行呢)
爲什麼這裏末尾會有個分號?
function sayHi() {
// ...
}
let sayHi = function() {
// ...
};
- 在代碼塊的結尾不需要加分號 ;,像 if { ... },for { },function f { } 等語法結構後面都不用加。
- 函數表達式是在語句內部的:let sayHi = ...;,作爲一個值。它不是代碼塊而是一個賦值語句。不管值是什麼,都建議在語句末尾添加分號 ;。所以這裏的分號與函數表達式本身沒有任何關係,它只是用於終止語句。
回調函數
function ask(question, yes, no) {
if (confirm(question)) yes()
else no();
}
function showOk() {
alert( "You agreed." );
}
function showCancel() {
alert( "You canceled the execution." );
}
// 用法:函數 showOk 和 showCancel 被作爲參數傳入到 ask
ask("Do you agree?", showOk, showCancel);
ask 的兩個參數值 showOk 和 showCancel 可以被稱爲 回調函數 或簡稱 回調。
函數表達式 vs 函數聲明
函數表達式是在代碼執行到達時被創建,並且僅從那一刻起可用。
一旦代碼執行到賦值表達式 let sum = function… 的右側,此時就會開始創建該函數,並且可以從現在開始使用(分配,調用等)。
函數聲明則不同。
在函數聲明被定義之前,它就可以被調用。
一個全局函數聲明對整個腳本來說都是可見的,無論它被寫在這個腳本的哪個位置。
函數聲明的另外一個特殊的功能是它們的塊級作用域。
嚴格模式下,當一個函數聲明在一個代碼塊內時,它在該代碼塊內的任何位置都是可見的。但在代碼塊外不可見。
如果我們使用函數聲明,則以下代碼無法像預期那樣工作:
let age = prompt("What is your age?", 18);
// 有條件地聲明一個函數
if (age < 18) {
function welcome() {
alert("Hello!");
}
} else {
function welcome() {
alert("Greetings!");
}
}
// ……稍後使用
welcome(); // Error: welcome is not defined
正確的做法是使用函數表達式,並將 welcome 賦值給在 if 外聲明的變量,並具有正確的可見性。
下面的代碼可以如願運行:
let age = prompt("What is your age?", 18);
let welcome;
if (age < 18) {
welcome = function() {
alert("Hello!");
};
} else {
welcome = function() {
alert("Greetings!");
};
}
welcome(); // 現在可以了
2.17 箭頭函數,基礎知識
單行箭頭函數
let func = (arg1, arg2, ...argN) => expression
多行的箭頭函數
let sum = (a, b) => { // 花括號表示開始一個多行函數
let result = a + b;
return result; // 如果我們使用了花括號,那麼我們需要一個顯式的 “return”
};
alert( sum(1, 2) ); // 3
2.18 JavaScript 特性
typeof 運算符返回值的類型,但有兩個例外:
typeof null == "object" // JavaScript 編程語言的設計錯誤
typeof function(){} == "function" // 函數被特殊對待
值 null 和 undefined 是特殊的:它們只在 == 下相等,且不相等於其他任何值。