以下文章轉載自前端自習課,作者王平安

前言

最近與部門老大一起面試了許多前端求職者,其中「想換個學習氛圍較好的人佔多數」,但良好的學習氛圍也是需要一點點營造出來的🌺。

為此我們組建了我們團隊內部的 “「現代 JavaScript 突擊隊」”,第一期學習內容為《現代 JavaScript 教程》系列,幫助小組成員系統地進行學習鞏固,並「讓大家養成系統性學習和輸出學習總結的學習方式」

本文作為我輸出的第一部分學習總結,希望作為一份自測清單,幫助大家鞏固知識,温故知新。

接下來開始分享自測清單的內容。

Hello World!

腳本引入方式

JavaScript 腳本引入方式有兩種:

  • <script>  標籤插入腳本;

  • <script> 標籤 src 設置腳本地址。

script 標籤屬性

<script>  標籤有以下常用屬性:

src

src :指定外部腳本的 URI, 如果設置了 src 特性,script 標籤內容將會被忽略;

1
<script src="example-url.js"></script>

type

type :指定引用腳本的語言,屬性值為 MIME 類型,包括text/javascript, text/ecmascript, application/javascript, 和application/ecmascript。如果沒有定義這個屬性,腳本會被視作 JavaScript。

ES6 新增了屬性值 module ,代碼會被當做 JavaScript 模塊。

1
<script type="text/javascript"></script>

async

async 規定一旦腳本可用,則會異步執行。注意:async 屬性僅適用於外部腳本(「只有在使用 src 屬性時」)。有多種執行外部腳本的方法:如果 async="async" :腳本相對於頁面的其餘部分異步地執行(當頁面繼續進行解析時,腳本將被執行);如果不使用 async  且 defer="defer" :腳本將在頁面完成解析時執行;如果既不使用 async 也不使用 defer :在瀏覽器繼續解析頁面之前,立即讀取並執行腳本;

1
<script async="async"></script>

defer

defer  屬性規定是否對腳本執行進行延遲,直到頁面加載為止。

如果您的腳本不會改變文檔的內容,可將 defer 屬性加入到 <script> 標籤中,以便加快處理文檔的速度。因為瀏覽器知道它將能夠安全地讀取文檔的剩餘部分而不用執行腳本,它將推遲對腳本的解釋,直到文檔已經顯示給用户為止。

1
<script defer="defer"></script>

詳細介紹可以閲讀《 MDN <script>章節 》。

代碼結構

語句

語句是執行行為(action)的語法結構和命令。如:alert('Hello, world!') 這樣可以用來顯示消息的語句。

分號

存在分行符時,多數情況下可以省略分號。但不全是,比如:

1
2
3
alert(3 +
1
2);

建議新人最好不要省略分號。

註釋

「單行註釋以兩個正斜槓字符 // 開始。」

1
2
// 註釋文本
console.log("leo");

「多行註釋以一個正斜槓和星號開始 “/*” 並以一個星號和正斜杆結束 “*/”。」

1
2
3
4
5
/*
這是多行註釋。
第二行註釋。
*/
console.log("leo");

現代模式,”use strict”

作用

JavaScript 的嚴格模式是使用受限制的 JavaScript 的一種方式,從而隱式地退出 “草率模式”。

"use strict" 指令將瀏覽器引擎轉換為 “現代” 模式,改變一些內建特性的行為。

使用

通過在腳本文件 / 函數開頭添加 "use strict"; 聲明,即可啟用嚴格模式。全局開啟嚴格模式:

1
2
3
// index.js
"use strict";
const v = "Hi!  I'm a strict mode script!";

函數內開啟嚴格模式:

1
2
3
4
5
6
7
8
// index.js
function strict({
  'use strict';
  function nested(
    return "And so am I!"
  }
  return "Hi!  I'm a strict mode function!  " + nested();
}

注意點

  1. "use strict" 需要定義在腳本最頂部(函數內除外),否則嚴格模式可能無法啟用。

  2. 一旦進入了嚴格模式,就無法關閉嚴格模式。

體驗

啟用 "use strict" 後,為未定義元素賦值將拋出異常:

1
2
"use strict";
leo = 17// Uncaught ReferenceError: leo is not defined

啟用 "use strict" 後,試圖刪除不可刪除的屬性時會拋出異常:

1
2
"use strict";
delete Object.prototype; // Uncaught TypeError: Cannot delete property 'prototype' of function Object() { [native code] }

詳細介紹可以閲讀《MDN 嚴格模式章節 》。

變量

介紹

變量是數據的 “命名存儲”。

使用

目前定義變量可以使用三種關鍵字:var / let / const。三者區別可以閲讀《let 和 const 命令》 。

1
2
3
let name = "leo";
let name = "leo", age, addr;
let name = "leo", age = 27, addr = "fujian";

命名建議

變量命名有 2 個限制:

  1. 變量名稱必須僅包含「字母,數字,符號」 $_

  2. 首字符必須「非數字」。變量命名還有一些建議:

  • 常量一般用全大寫,如 const PI = 3.141592

  • 使用易讀的命名,比如 userName 或者 shoppingCart

注意點

  • JavaScript 變量名稱區分大小寫,如變量 leoLeo 是不同的;

  • JavaScript 變量名稱允許非英文字母,但不推薦,如 let 平安 = "leo"

  • 避免使用  abc 這種縮寫。

數據類型

JavaScript 是一種「弱類型」或者説「動態語言」。這意味着你不用提前聲明變量的類型,在程序運行過程中,類型會被自動確定。這也意味着你可以使用同一個變量保存不同類型的數據:

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

詳細介紹可以閲讀《MDN JavaScript 數據類型和數據結構 》。

八大數據類型

前七種為基本數據類型,也稱為原始類型(值本身無法被改變),而 object 為複雜數據類型。八大數據類型分別是:

  • number 用於任何類型的數字:整數或浮點數,在 ±2 範圍內的整數。

  • bigint 用於任意長度的整數。

  • string 用於字符串:一個字符串可以包含一個或多個字符,所以沒有單獨的單字符類型。

  • boolean 用於 truefalse

  • null 用於未知的值 —— 只有一個 null 值的獨立類型。

  • undefined 用於未定義的值 —— 只有一個 undefined 值的獨立類型。

  • symbol 用於唯一的標識符。

  • object 用於更復雜的數據結構。「每個類型後面會詳細介紹。」

檢測數據類型

通過 typeof 運算符檢查:

  • 兩種形式:typeof x 或者 typeof(x)

  • 以字符串的形式返回類型名稱,例如 "string"

  • typeof null 會返回 "object" —— 這是 JavaScript 編程語言的一個錯誤,實際上它並不是一個 object

1
2
3
4
5
6
7
8
9
10
11
typeof "leo" // "string"
typeof undefined    // "undefined"
typeof 0     // "number"
typeof NaN   // "number"
typeof 10n   // "bigint"
typeof true  // "boolean"
typeof Symbol("id"// "symbol"
typeof [1,2,3,4]    // "object"
typeof Math  // "object"  (1) Math 是一個提供數學運算的內建 object。
typeof null  // "object"  (2) JavaScript 語言的一個錯誤,null 不是一個 object。null 有自己的類型,它是一個特殊值。
typeof alert // "function"  (3) alert 在 JavaScript 語言中是一個函數。

類型轉換

JavaScript 變量可以轉換為新變量或其他數據類型:

  • 通過使用 JavaScript 函數

  • 通過 JavaScript 自身自動轉換

字符串轉換

通過全局方法 String()  將 「其他類型數據(任何類型的數字,字母,布爾值,對象)」 轉換為 String 類型:

1
2
3
4
5
6
7
String(123);   // "123"
// Number方法 toString()/toExponential()/toFixed()/toPrecision() 也有同樣效果。
String(false); // "false"
// Boolean方法 toString() 也有同樣效果。
String(new Date()); // "Sun Jun 07 2020 21:44:20 GMT+0800 (中國標準時間)"
// Date方法 toString() 也有同樣效果。
String(leo);

數值轉換

通過以下幾種方式能將其他類型數據轉換為 Number 類型:

  • 一元運算符 +
1
const age = +"22"// 22
  • Number 方法
1
2
3
const age = Number("22"); // 22
Number.parseFloat("22");  // 22
Number.parseInt("22");  // 22
  • 其他方式轉 Number 類型
1
2
3
4
5
6
7
8
9
10
11
12
// 布爾值
Number(false)     // 返回 0
Number(true)      // 返回 1
// 日期
const date = new Date();
Number(date);     // 返回 1591537858154
date.getTime();   // 返回 1591537858154,效果一致。
// 自動轉換
5 + null    // 返回 5         null 轉換為 0
"5" + null  // 返回"5null"   null 轉換為 "null"
"5" + 1     // 返回 "51"      1 轉換為 "1" 
"5" - 1     // 返回 4         "5" 轉換為 5

布爾值轉換

轉換規則如下:

  • 直觀上為 “空” 的值(如 0、空字符串、nullundefinedNaN)將變為 false

  • 其他值變成 true

1
2
3
4
5
6
Boolean(1); // true
Boolean(0); // false
Boolean("hello"); // true
Boolean(""); // false
Boolean("0"); // true
Boolean(" "); // 空白, 也是 true (任何非空字符串是 true)

小結

運算符

運算符概念

常見運算符如加法 + 、減法 - 、乘法 * 和除法 / ,舉一個例子,來介紹一些概念:

1
2
let sum = 1 + 2;
let age = +18;

其中:

  • 加法運算 1 + 2 中, 12 為 2 個運算元,左運算元 1 和右運算元 2 ,即「運算元就是運算符作用的對象。」

  • 1 + 2 運算式中包含 2 個運算元,因此也稱該運算式中的加號  +「二元運算符。」

  • +18 中的加號 + 對應只有一個運算元,則它是 「一元運算符」

+ 號運算符

1
2
3
4
5
6
7
let msg = "hello " + "leo"// "hello leo"
let total = 10 + 20;  // 30
let text1 = "1" + "2"// "12"
let text2 = "1" + 2;   // "12"
let text3 = 1 + "2";   // "12"
let text4 = 1 + 2 + "3";  // "33"
let num = +text1; //  12 轉換為 Number 類型

運算符優先級

運算符的優先級決定了表達式中運算執行的先後順序,優先級高的運算符最先被執行。下面的表將所有運算符按照優先級的不同從高(20)到低(1)排列。

優先級運算類型關聯性運算符
20圓括號n/a(不相關)( … )
19成員訪問從左到右… . …
需計算的成員訪問從左到右… [ … ]
new (帶參數列表)n/anew … ( … )
函數調用從左到右… ( … )
可選鏈(Optional chaining)從左到右?.
18new (無參數列表)從右到左new …
17後置遞增 (運算符在後)n/a
… ++
後置遞減 (運算符在後)… –
16邏輯非從右到左! …
按位非~ …
一元加法+ …
一元減法- …
前置遞增++ …
前置遞減– …
typeoftypeof …
voidvoid …
deletedelete …
awaitawait …
15從右到左… ** …
14乘法從左到右
… * …
除法… / …
取模… % …
13加法從左到右
… + …
減法… - …
12按位左移從左到右… << …
按位右移… >> …
無符號右移… >>> …
11小於從左到右… < …
小於等於… <= …
大於… > …
大於等於… >= …
in… in …
instanceof… instanceof …
10等號從左到右
… == …
非等號… != …
全等號… === …
非全等號… !== …
9按位與從左到右… & …
8按位異或從左到右… ^ …
7按位或從左到右
6邏輯與從左到右… && …
5邏輯或從左到右
4條件運算符從右到左… ? … : …
3賦值從右到左… = …
… += …
… -= …
… *= …
… /= …
… %= …
… <<= …
… >>= …
… >>>= …
… &= …
… ^= …
2yield從右到左yield …
yield*yield* …
1展開運算符n/a… …
0逗號從左到右… , …
1
2
3
4
5
3 > 2 && 2 > 1
// return true
3 > 2 > 1
// 返回 false,因為 3 > 2 是 true,並且 true > 1 is false
// 加括號可以更清楚:(3 > 2) > 1

值的比較

常見比較

在 JS 中的值的比較與數學很類型:

  • 大於 / 小於 / 大於等於 / 小於等於:a>b / a<b / a>=b / a<=b

  • 判斷相等:

1
2
3
4
5
6
// 使用 ==,非嚴格等於,不關心值類型
// == 運算符會對比較的操作數做隱式類型轉換,再比較
'1' == 1// true
// 使用 ===,嚴格相等,關心值類型
// 將數字值 -0 和 +0 視為相等,並認為 Number.NaN 不等於 NaN。
'1' === 1// false

(圖片來自:《MDN JavaScript 中的相等性判斷》)

  • 判斷不相等:和判斷相等一樣,也有兩種:!= / !==

相等性判斷(Object.is())

另外 ES6 新增 Object.is 方法判斷兩個值是否相同,語法如下:

1
Object.is(value1, value2);

以下任意項成立則兩個值相同:

  • 兩個值都是 undefined

  • 兩個值都是 null

  • 兩個值都是 true 或者都是 false

  • 兩個值是由相同個數的字符按照相同的順序組成的字符串

  • 兩個值指向同一個對象

  • 兩個值都是數字並且

    • 都是正零 +0

    • 都是負零 -0

    • 都是 NaN

    • 都是除零和 NaN 外的其它同一個數字 使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Object.is('foo''foo');     // true
Object.is(windowwindow);   // true
Object.is('foo''bar');     // false
Object.is([], []);           // false
var foo = { a1 };
var bar = { a1 };
Object.is(foo, foo);         // true
Object.is(foo, bar);         // false
Object.is(nullnull);       // true
// 特例
Object.is(0, -0);            // false
Object.is(0, +0);            // true
Object.is(-0, -0);           // true
Object.is(NaN0/0);         // true

兼容性 Polyfill 處理:

1
2
3
4
5
6
7
8
9
10
11
12
if (!Object.is) {
  Object.is = function(x, y{
    // SameValue algorithm
    if (x === y) { // Steps 1-5, 7-10
      // Steps 6.b-6.e: +0 != -0
      return x !== 0 || 1 / x === 1 / y;
    } else {
      // Step 6.a: NaN == NaN
      return x !== x && y !== y;
    }
  };
}

null 與 undefined 比較

對於相等性判斷比較簡單:

1
2
null == undefined;  // true
null === undefined// false

對於其他比較,它們會先轉換位數字:null 轉換為 0undefied 轉換為 NaN

1
2
3
4
null > 0;  // false 1
null >= 0// true  2
null == 0// false 3
null < 1;  // true  4

需要注意:null == 0; // false 這裏是因為:undefinednull 在相等性檢查 ==「不會進行任何的類型轉換」,它們有自己獨立的比較規則,所以除了它們之間互等外,不會等於任何其他的值。

1
2
3
undefined > 0;  // false  1
undefined > 1;  // false  2
undefined == 0// false  3

第 1、2 行都返回 false 是因為 undefined 在比較中被轉換為了 NaN,而 NaN 是一個特殊的數值型值,它與任何值進行比較都會返回 false。第 3 行返回 false 是因為這是一個相等性檢查,而 undefined 只與 null 相等,不會與其他值相等。

alert / prompt / confirm

alert

顯示一個警告對話框,上面顯示有指定的文本內容以及一個 “確定” 按鈕。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」

1
2
3
4
5
// 語法
window.alert(message);
alert(message);
// 示例
alert('hello leo!');

message是要顯示在對話框中的文本字符串,如果傳入其他類型的值, 會轉換成字符串。

prompt

顯示一個對話框,對話框中包含一條文字信息,用來提示用户輸入文字。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」當點擊確定返回文本,點擊取消或按下 Esc 鍵返回 null。語法如下:

1
let result = window.prompt(text, value);
  • result 用來存儲用户輸入文字的字符串,或者是 null。

  • text 用來提示用户輸入文字的字符串,如果沒有任何提示內容,該參數可以省略不寫。

  • value 文本輸入框中的默認值,該參數也可以省略不寫。不過在 Internet Explorer 7 和 8 中,省略該參數會導致輸入框中顯示默認值 “undefined”。

confirm

Window.confirm() 方法顯示一個具有一個可選消息和兩個按鈕 (確定和取消) 的模態對話框。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」語法如下:

1
let result = window.confirm(message);
  • message 是要在對話框中顯示的可選字符串。

  • result 是一個布爾值,表示是選擇確定還是取消 (true 表示 OK)。

條件運算符:if 和 ‘?’

if 語句

當 if 語句當條件表達式,會將表達式轉換為布爾值,當為 truthy 時執行裏面代碼。轉換規則如:

  • 數字 0、空字符串 ""nullundefinedNaN 都會被轉換成 false。因為他們被稱為 “falsy” 值。

  • 其他值被轉換為 true,所以它們被稱為 “truthy”。

三元運算符

「條件(三元)運算符」是 JavaScript 僅有的使用三個操作數的運算符。一個條件後面會跟一個問號(?),如果條件為 truthy ,則問號後面的表達式 A 將會執行;表達式 A 後面跟着一個冒號(:),如果條件為 falsy ,則冒號後面的表達式 B 將會執行。本運算符經常作為 [if](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else) 語句的簡捷形式來使用。語法:

1
condition ? exprIfTrue : exprIfFalse
  • condition 計算結果用作條件的表達式。

  • exprIfTrue 如果表達式 condition 的計算結果是 truthy(它和 true 相等或者可以轉換成 true ),那麼表達式 exprIfTrue 將會被求值。

  • exprIfFalse 如果表達式 condition 的計算結果是 falsy(它可以轉換成 false ),那麼表達式 exprIfFalse 將會被執行。示例:

1
2
3
4
5
6
7
let getUser = function(name){
 return name === 'leo' ? 'hello leo!' : 'unknow user';
}
// 可以簡寫如下:
let getUser = name => name === 'leo' ? 'hello leo!' : 'unknow user';
getUser('leo'); // "hello leo!"
getUser('pingan'); // "unknow user"

邏輯運算符

詳細可以閲讀《MDN 邏輯運算符》 。

運算符介紹

邏輯運算符如下表所示 (其中_expr_可能是任何一種類型, 不一定是布爾值):

運算符語法説明
邏輯與,AND(&&expr1 && expr2expr**1** 可轉換為 true,則返回 expr**2**;否則,返回 expr**1**
邏輯或,OR(``)
邏輯非,NOT(!)!exprexpr 可轉換為 true,則返回 false;否則,返回 true

如果一個值可以被轉換為 true,那麼這個值就是所謂的 truthy,如果可以被轉換為 false,那麼這個值就是所謂的 falsy。會被轉換為 false 的表達式有:

  • null

  • NaN

  • 0

  • 空字符串("" or '' or ````);

  • undefined。儘管 &&|| 運算符能夠使用非布爾值的操作數, 但它們依然可以被看作是布爾操作符,因為它們的返回值總是能夠被轉換為布爾值。如果要顯式地將它們的返回值(或者表達式)轉換為布爾值,請使用雙重非運算符(即!!)或者 Boolean 構造函數。JavaScript 裏有三個邏輯運算符:||(或),&&(與),!(非)。

運算符示例

  • 邏輯與(&&) 所有條件都為 true 才返回 true,否則為 false。
1
2
3
4
5
6
7
8
9
a1 = true  && true      // t && t 返回 true
a2 = true  && false     // t && f 返回 false
a3 = false && true      // f && t 返回 false
a4 = false && (3 == 4)  // f && f 返回 false
a5 = "Cat" && "Dog"     // t && t 返回 "Dog"
a6 = false && "Cat"     // f && t 返回 false
a7 = "Cat" && false     // t && f 返回 false
a8 = ''    && false     // f && f 返回 ""
a9 = false && ''        // f && f 返回 false
  • 邏輯或( || ) 所有條件有一個為 true 則返回 true,否則為 false。
1
2
3
4
5
6
7
8
9
o1 = true  || true      // t || t 返回 true
o2 = false || true      // f || t 返回 true
o3 = true  || false     // t || f 返回 true
o4 = false || (3 == 4)  // f || f 返回 false
o5 = "Cat" || "Dog"     // t || t 返回 "Cat"
o6 = false || "Cat"     // f || t 返回 "Cat"
o7 = "Cat" || false     // t || f 返回 "Cat"
o8 = ''    || false     // f || f 返回 false
o9 = false || ''        // f || f 返回 ""
  • 邏輯非( ! )
1
2
3
4
n1 = !true              // !t 返回 false
n2 = !false             // !f 返回 true
n3 = !''                // !f 返回 true
n4 = !'Cat'             // !t 返回 false
  • 雙重非運( !! )
1
2
3
4
5
6
n1 = !!true                   // !!truthy 返回 true
n2 = !!{}                     // !!truthy 返回 true: 任何 對象都是 truthy 的…
n3 = !!(new Boolean(false))   // …甚至 .valueOf() 返回 false 的布爾值對象也是!
n4 = !!false                  // !!falsy 返回 false
n5 = !!""                     // !!falsy 返回 false
n6 = !!Boolean(false)         // !!falsy 返回 false

布爾值轉換規則

  • 將 && 轉換為 ||
1
2
3
condi1 && confi2
// 轉換為
!(!condi1 || !condi2)
  • 將 || 轉換為 &&
1
2
3
condi1 || condi2
// 轉換為
!(!condi1 && !condi2)

短路取值

由於邏輯表達式的運算順序是從左到右,也可以用以下規則進行 “短路” 計算:

  • (some falsy expression) && (_expr)_ 短路計算的結果為假。

  • (some truthy expression) || _(expr)_ 短路計算的結果為真。短路意味着上述表達式中的 expr 部分「不會被執行」,因此 expr 的任何副作用都不會生效(舉個例子,如果 expr 是一次函數調用,這次調用就不會發生)。造成這種現象的原因是,整個表達式的值在第一個操作數被計算後已經確定了。看一個例子:

1
2
3
4
5
6
7
8
function A()console.log('called A'); return false; }
function B()console.log('called B'); return true; }
console.log( A() && B() );
// logs "called A" due to the function call,
// then logs false (which is the resulting value of the operator)
console.log( B() || A() );
// logs "called B" due to the function call,
// then logs true (which is the resulting value of the operator)

注意

與運算 && 的優先級比或運算 || 要高。所以代碼 a && b || c && d 完全跟 && 表達式加了括號一樣:(a && b) || (c && d)

循環:while 和 for

while 循環

詳細可以閲讀《MDN  while》 。「while 語句」可以在某個條件表達式為真的前提下,循環執行指定的一段代碼,直到那個表達式不為真時結束循環。如:

1
2
3
4
5
6
var n = 0;
var x = 0;
while (n < 3) {
  n++;
  x += n;
}

當循環體為單行時,可以不寫大括號:

1
2
let i = 3;
while(i) console.log(i --);

do…while 循環

詳細可以閲讀《MDN  do…while》 。do...while 語句創建一個執行指定語句的循環,直到condition值為 false。在執行statement 後檢測condition,所以指定的statement至少執行一次。如:

1
2
3
4
5
6
var result = '';
var i = 0;
do {
   i += 1;
   result += i + ' ';
while (i < 5);

for 循環

詳細可以閲讀《MDN  for》 。for 語句用於創建一個循環,它包含了三個可選的表達式,這三個表達式被包圍在圓括號之中,使用分號分隔,後跟一個用於在循環中執行的語句(通常是一個塊語句)。語法如:

1
2
3
for (begin; condition; step) {
  // ……循環體……
}

示例:

1
2
3
for (let i = 0; i < 3; i++) {
  console.log(i);
}

描述:

begini = 0進入循環時執行一次。
conditioni < 3在每次循環迭代之前檢查,如果為 false,停止循環。
body(循環體)alert(i)條件為真時,重複運行。
stepi++在每次循環體迭代後執行。

可選的 for 表達式

for 語句頭部圓括號中的所有三個表達式都是可選的。

  • 不指定表達式中初始化塊
1
2
3
4
var i = 0;
for (; i < 3; i++) {
    console.log(i);
}
  • 不指定表達式中條件塊,這就必須要求在循環體中結束循環,否則會出現死循環
1
2
3
4
for (var i = 0;; i++) {
   console.log(i);
   if (i > 3break;
}
  • 不指定所有表達式,也需要在循環體中指定結束循環的條件
1
2
3
4
5
6
var i = 0;
for (;;) {
  if (i > 3break;
  console.log(i);
  i++;
}

break 語句

詳細可以閲讀《MDN  break》 。break 語句中止當前循環,switch語句或label 語句,並把程序控制流轉到緊接着被中止語句後面的語句。在 while 語句中:

1
2
3
4
5
6
7
8
9
10
function testBreak(x{
  var i = 0;
  while (i < 6) {
    if (i == 3) {
      break;
    }
    i += 1;
  }
  return i * x;
}

另外,也可以為代碼塊做標記,並在 break 中指定要跳過的代碼塊語句的 label:

1
2
3
4
5
6
7
8
outer_block:{
  inner_block:{
    console.log ('1');
    break outer_block;      // breaks out of both inner_block and outer_block
    console.log (':-(');    // skipped
  }
  console.log ('2');        // skipped
}

需要注意的是:break 語句需要內嵌在它所應用的標籤或代碼塊中,否則報錯:

1
2
3
4
5
6
7
block_1:{
  console.log ('1');
  break block_2;            // SyntaxError: label not found
}
block_2:{
  console.log ('2');
}

continue 語句

continue 聲明終止當前循環或標記循環的當前迭代中的語句執行,並在下一次迭代時繼續執行循環。與 break 語句的區別在於, continue 並不會終止循環的迭代,而是:

  • while 循環中,控制流跳轉回條件判斷;

  • for 循環中,控制流跳轉到更新語句。注意:continue 也必須在對應循環內部,否則報錯。

1
2
3
4
5
6
7
8
9
i = 0;
n = 0;
while (i < 5) {
   i++;
   if (i === 3) {
      continue;
   }
   n += i;
}

帶 label:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var i = 0
    j = 8;
checkiandj: while (i < 4) {
   console.log("i: " + i);
   i += 1;
   checkj: while (j > 4) {
      console.log("j: "+ j);
      j -= 1;
      if ((j % 2) == 0)
         continue checkj;
      console.log(j + " is odd.");
   }
   console.log("i = " + i);
   console.log("j = " + j);
}

注意

「禁止 break/continue 在 ‘?’ 的右邊:」

1
(i > 5) ? console.log(i) : continue// continue 不允許在這個位置

這樣會提示語法錯誤。請注意非表達式的語法結構不能與三元運算符 ? 一起使用。特別是 break/continue 這樣的指令是不允許這樣使用的。

總結

三種循環:

  • while —— 每次迭代之前都要檢查條件。

  • do..while —— 每次迭代後都要檢查條件。

  • for (;;) —— 每次迭代之前都要檢查條件,可以使用其他設置。通常使用 while(true) 來構造 “無限” 循環。這樣的循環和其他循環一樣,都可以通過 break 指令來終止。如果我們不想在當前迭代中做任何事,並且想要轉移至下一次迭代,那麼可以使用 continue 指令。break/continue 支持循環前的標籤。標籤是 break/continue 跳出嵌套循環以轉到外部的唯一方法。

“switch” 語句

switch 語句用來將表達式的值與 case 語句匹配,並執行與情況對應的語句。switch 語句可以替代多個 if 判斷,為多個分支選擇的情況提供一個更具描述性的方式。

語法

switch 語句至少包含一個 case 代碼塊和一個可選的 default 代碼塊:

1
2
3
4
5
6
7
8
9
switch(expression) {
  case 'value1':
    // do something ...
    [break]
   
  default:
    // ...
    [break]
}

expression 表達式的值與 value1 匹配時,則執行其中代碼塊。如果沒有 case  子句匹配,則會選擇 default 子句執行,若連 default 子句都沒有,則直接執行到 switch 結束。

使用 case 分組

所謂 case 分組,就是與多個 case 分支共享同一段代碼,如下面例子中 case 1  和 case 2

1
2
3
4
5
6
7
8
9
10
11
12
13
let a = 2;
switch (a) {
  case 1// (*) 下面這兩個 case 被分在一組
  case 2:
    console.log('case is 1 or 2!');
    break;
  case 3:
    console.log('case is 3!');
    break;
  default:
    console.log('The result is default.');
}
// 'case is 1 or 2!'

注意點

  1. expression 表達式的值與 case 值的比較是嚴格相等:」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function f(n){
    let a ;
    switch(n){
        case 1:
            a = 'number';
            break;
        case '1':
            a = 'string';
            break;
        default:
            a = 'default';
            break;
    }
    console.log(a)
}
f(1);   // number
f('1'); // string
  1. 「如果沒有 break,程序將不經過任何檢查就會繼續執行下一個 case:」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = 2 + 2;
switch (a) {
 case 3:
    console.log( 'Too small' );
  case 4:
    console.log( 'Exactly!' );
  case 5:
    console.log( 'Too big' );
  default:
    console.log( "I don't know such values" );
}
// Exactly!
// Too big
// I don't know such values
  1. default 放在 case 之上不影響匹配」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function f(n){
  switch (n) {
    case 2:
      console.log(2);
      break;
    default:
      console.log('default')
      break;
    case 1:  
      console.log('1');
      break;
  }
}
f(1); // 1
f(2); // 2
f(3); // default
  • switch 語句中存在 let / const重複聲明問題:」
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 以下定義會報錯
function f(n){
    switch(n){
        case 1:
            let msg = 'hello';
            console.log(1);
            break;
        case 2
            let msg = 'leo';
            break;
        default
            console.log('default');
            break;
    }
}
// Error: Uncaught SyntaxError: Identifier 'msg' has already been declared

這是由於兩個 let 處於同一個塊級作用域,所以它們被認為是同一變量名的重複聲明。解決方式,只需要將 case 語句包裝在括號內即可解決:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function f(n){
    switch(n){
        case 1:{ // added brackets
            let msg = 'hello';
            console.log(msg);
            break;
        }
        case 2: {
            let msg = 'leo';
            console.log(msg);
            break;
        }
        default
            console.log('default');
            break;
    }
}

函數

函數可以讓一段代碼被多次調用,避免重複代碼。如之前學習到的一些內置函數:alert(msg) / prompt(msg, default) / confirm(quesyion) 等。

函數定義

定義函數有兩種方式:「函數聲明」「函數表達式」

函數聲明

如定義一個簡單 getUser 函數:

1
2
3
4
function getUser(name){
 return 'hello ' + name;
}
getUser('leo"); // 函數調用

通過函數聲明來定義函數時,需要由以下幾部分組成:

  • 函數名稱 - getUser

  • 函數參數列表 - name

  • 函數的 JS 執行語句 - return 'hello ' + name

函數表達式

類似聲明變量,還是以 getUser 為例:

1
2
3
let getUser = function(name){
 return 'hello ' + name;
}

另外,函數表達式也可以提供函數名,並用於函數內部指代函數本身:

1
2
3
4
5
let fun = function f(n){
    return n < 3 ? 1 : n * f(n - 1);
}
fun(3);  // 3
fun(5);  // 60

函數調用

當定義一個函數後,它並不會自動執行,而是需要使用函數名稱進行調用,如上面例子:

1
fun(3);  // 3

「只要注意:」使用 「函數表達式」 定義函數時,調用函數的方法必須寫在定義之後,否則報錯:

1
2
console.log(fun());  // Uncaught ReferenceError: fun is not defined
let fun = function(){return 1};

而使用 「函數聲明」 則不會出現該問題:

1
2
console.log(fun());  // 1
function fun(){return 1};

原因就是:函數提升僅適用於函數聲明,而不適用於函數表達式。

函數中的變量

在函數中,可以使用局部變量和外部變量。

局部變量

函數中聲明的變量只能在該函數內可見。

1
2
3
4
5
let fun = function(){
 let name = 'leo';
}
fun();
console.log(name); // Uncaught ReferenceError: name is not defined

全局變量

函數內可以使用外部變量,並且可以修改外部變量的值。

1
2
3
4
5
6
let name = 'leo';
let fun = function(){
 let text = 'Hello, ' + name;
  console.log(text);
}
fun(); // Hello, leo

當函數內也有與外部變量名稱相同的變量,會忽略外部變量:

1
2
3
4
5
6
7
let name = 'leo';
let fun = function(){
  let name = 'pingan8787';
 let text = 'Hello, ' + name;
  console.log(text);
}
fun(); // Hello, pingan8787

函數參數

從 ECMAScript 6 開始,有兩個新的類型的參數:默認參數,剩餘參數。

默認參數

若函數沒有傳入參數,則參數默認值為undefined,通常設置參數默認值是這樣做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ES6 之前,沒有設置默認值
function f(a, b){
    b = b ? b : 1;
    return a * b;
}
f(2,3);  // 6
f(2);    // 2
// ES6,設置默認值
function f(a, b = 1){
    return a * b;
}
f(2,3);  // 6
f(2);    // 2

剩餘參數

可以將參數中不確定數量的參數表示成數組,如下:

1
2
3
4
function f (a, ...b){
    console.log(a, b);
}
f(1,2,3,4); // a => 1 b => [2, 3, 4]

既然講到參數,那就不能少了 arguments 對象。

arguments 對象

函數的實際參數會被保存在一個「類似數組的 arguments 對象」中。在函數內,我們可以使用 arguments 對象獲取函數的所有參數:

1
2
3
4
5
6
7
let fun = function(){
    console.log(arguments);
    console.log(arguments.length);
}
fun('leo'); 
// Arguments ["leo", callee: ƒ, Symbol(Symbol.iterator): ƒ] 
// 1

以一個實際示例介紹,實現將任意數量參數連接成一個字符串,並輸出的函數:

1
2
3
4
5
6
7
8
let argumentConcat = function(separator){
 let result = '', i;
  for(i = 1; i < arguments.length; i ++){
   result += arguments[i] + separator;
  }
  return result;
}
argumentConcat(',''leo''pingan'); //"leo,pingan,"

函數返回值

在函數任意位置,指定 return 指令來停止函數的執行,並返回函數指定的返回值。

1
2
3
4
5
let sum = function(a, b){
 return a + b;
};
let res = sum(12);
console.log(res); // 3

默認空值的 return 或沒有 return 的函數返回值為 undefined

函數表達式

函數表達式是一種函數定義方式,在前面章節中已經介紹到了,這個部分將着重介紹 「函數表達式」「函數聲明」 的區別:

語法差異

1
2
3
4
// 函數表達式
let fun = function(){};
// 函數聲明
function fun(){}

創建時機差異

函數表達式會在代碼執行到達時被創建,並且僅從那一刻可用。而函數聲明被定義之前,它就可以被調用。

1
2
3
4
5
6
// 函數表達式
fun();  // Uncaught ReferenceError: fun is not defined
let fun = function(){console.log('leo')};
// 函數聲明
fun();  // "leo"
function fun(){console.log('leo')};

使用建議

建議優先考慮函數聲明語法,它能夠為組織代碼提供更多靈活性,因為我們可以在聲明函數前調用該函數。

箭頭函數

「本章節簡單介紹箭頭函數基礎知識,後面章節會完整介紹。」
「函數箭頭表達式」是 ES6 新增的函數表達式的語法,也叫「胖箭頭函數」,變化:更簡潔的函數和this

代碼更簡潔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 有1個參數
let f = v => v;
// 等同於
let f = function (v){return v};
// 有多個參數
let f = (v, i) => {return v + i};
// 等同於
let f = function (v, i){return v + i};
// 沒參數
let f = () => 1;
// 等同於
let f = function (){return 1};
let arr = [1,2,3,4];
arr.map(ele => ele + 1);  // [2, 3, 4, 5]

注意點

  1. 箭頭函數不存在this

  2. 箭頭函數不能當做「構造函數」,即不能用new實例化;

  3. 箭頭函數不存在arguments對象,即不能使用,可以使用rest參數代替;

  4. 箭頭函數不能使用yield命令,即不能用作 Generator 函數。一個簡單的例子:

1
2
3
4
5
6
7
function Person(){
  this.age = 0;
  setInterval(() => {
    this.age++;
  }, 1000);
}
var p = new Person(); // 定時器一直在執行 p的值一直變化

總結

本文作為《初中級前端 JavaScript 自測清單》第一部分,介紹的內容以常用基礎知識為主,並在學習資料中,將知識點結合實際開發中遇到的場景進行展開介紹。希望能幫助大家自測自己的 JavaScript 水平並查缺補漏,温故知新。

如果你喜歡這篇文章,可以關注原作者的公眾號 前端自習課