你懂 JavaScript 嗎?#2 暖身 (๑•̀ㅂ•́)و✧ Part 1 - 運算子、運算式、值與型別、變數、條件式、迴圈

你所不知道的 JS

本文主要內容為程式設計簡介,在此可看到在初學階段所必須理解的各種專有名詞。

以下一一仔細跟大家說明 ( ゚ ∀ ゚)o 彡

讓我好好跟你說說

程式碼(Code)

程式(program)又稱原始碼(source code)、程式碼(code),用來表示一群執行特定工作的指令,也可說是述句組成的集合。

語法(Syntax)

規範有效指令與組合的規則,稱為電腦語言(computer language)或語法(syntax)。

可想成若希望能編寫電腦可懂的語言,就必須遵循一套規則來撰寫,而這個規則就是語法,就和我們平常溝通所說的語言的文法是一樣的。

Do You Know What I Mean?

圖片來源:Do You Know What I Mean?

述句(Statement)

會執行特定工作的字詞、數字或運算子(operator)組合,即是述句(statement),例如:a = b + 1 就會執行 b + 1 並將結果指定給 a。

字面值(Literal Value)

獨立存在的值,沒有存在於任何變數中,例如:a = b + 1 中的 1。

直譯器(Interpreter)與編譯器(Compiler)

直譯器與編譯器可將程式碼由上到下逐行轉為電腦可懂的命令。

其差別在於時機點

運算子(Operators)

對變數或值進行操作的字元,例如:a = b + 1 中的 =+

JavaScript 有以下幾種運算子。

5 & 1; // 1
const name = 'Summer';

// 使用字串運算子
const greetings_1 = 'Hello ' + name + '!'; // "Hello Summer!"

// 使用字串模板
const greetings_2 = `Hello ${name}!`; // "Hello Summer!"
const count = 10;
const prompt = count <= 0 ? '全部賣完了' : '還有存貨';

prompt; // "還有存貨"
for (let i = 0, j = 10; i < j; i++, j--) {
  console.log(`i: ${i}, j: ${j}`);
}

// i: 0, j: 10
// i: 1, j: 9
// i: 2, j: 8
// i: 3, j: 7
// i: 4, j: 6
const x = 1;
y = 2;
const product = {
  name: 'apple',
  count: 100,
};

delete x; // false
x; // 1

delete y; // true
y; // Uncaught ReferenceError: y is not defined

delete product.count; // true
product; // {name: "apple"}

更多如下圖,圖片來源:Summary of all unary operators

Summary of all unary operators

in 運算子的範例。

const product = {
  name: 'apple',
  count: 100,
};

'name' in product; // true
'valid' in product; // false

instanceof 運算子的範例。

product instanceof Object; // true
product instanceof Array; // false

運算式(Expression)

對「某個變數或值,或以運算子結合起來的一組變數或值」的參考(reference)。例如:a = b + 1 這個述句中有四個運算式,分別是 1、b、b + 1a = b + 1,其中 1 是字面值運算式(literal value expression)、b 是變數運算式(variable expression)用來取得變數的值、b + 1 是算術運算式(arithmetic expression)用來進行加法運算、a = b + 1 是指定運算式(assignment expression)用來將結果指定給變數存起來。這裡順道一提「呼叫運算式(call expression)」,意即函式呼叫運算式本身,例如:alert(a)


題外話,曾幾何時我以為當工程師超酷炫的,應該都像 Daisy Johnson 一樣這麼殺吧。

當年 Daisy Johnson 只是個駭客不打壞人只打電腦時就已經這麼迷人了 (๑╹◡╹๑)

Daisy Johnson

圖片來源:5 reasons why Skye from ‘Agents of S.H.I.E.L.D.’ is a Mary Sue and why it hurts the show

好了好了,夢醒了,趕快繼續幹活 (  ̄ 3  ̄)y▂ξ


值與型別(Values & Types)

型別,指的是「值的不同的表示法」,主要分為兩種「基本型別」(primitives,即 number、string、boolean、null、undefined、symbol)和「物件型別」(object)。

基本型別(Primitive Types)

物件型別(Object Types)

除了基本型別外的資料型別都是物件,物件型別又分以下子型別

function sayHi(name) {
  console.log(`Hi, I am ${name}`);
}

sayHi('Jack'); // Hi, I am Jack

在型別之間進行轉換(Converting Between Types)

我們有時候會需要將資料在不同型別間轉換,例如,遇到在表單中輸入一連串的金額(此時是字串),接著計算總金額時就會希望將這些字串轉成數字來做加減乘除的操作,這時候就可能需要做轉型,從字串轉成數字。

例如,小明在蝦 X 買了一件商品準備在母親節送給媽媽,並選擇貨到付款。

蝦皮購物-晚輩圖的逆襲

圖片來源:母親節送地方媽媽小黃瓜「煮菜啦!」

來看看商品金額、運費和總金額,商品金額(product)是 100(以字串型態存在),運費(shipment)也同樣是 100(以數字型態存在),這時候總金額 total 是?

const product = '100';
const shipment = 100;

咦?怎麼會是 100100!

const total = product + shipment; // 100100

原來是因為若兩值資料型別不同,當其中一方是字串時,+ 所代表的就是字串運算子,而將數字強制轉型為字串,並連接兩個字串。解法就是使用 Number 強制轉型(coerce),將字串的部份轉為數字就可以做數學運算了。

const product = '100';
const shipment = 100;
const total = Number(product) + shipment; // 200

又,除了強制轉型,來看在比較兩個非相同型別的值的時候會發生隱含的(implicit)轉型。

例如,小明想要比較買這個商品付這個運費划算嗎?運費該不會比商品還貴吧?

product === shipment; // false
product == shipment; // true

咦?一個是字串、一個是數字,是怎麼能做比較?這是由於 JavaScript 偷偷做了(隱含的)轉型的原因。那…到底做了什麼呢?

小明看到商品價格與運費居然相等,還是先湊個免運再買吧!

typeof

typeof 可用於檢測值的型別是什麼。

typeof 'Hello World!'; // 'string'
typeof true; // 'boolean'
typeof 1234567; // 'number'
typeof null; // 'object'
typeof undefined; // 'undefined'
typeof { name: 'Jack' }; // 'object'
typeof Symbol(); // 'symbol'
typeof function () {}; // 'function'
typeof [1, 2, 3]; // 'object'
typeof NaN; // 'number'

這裡會看到幾個有趣的(奇怪的)地方…


看到這裡是不是覺得 JavaScript 很難懂呢? (/‵Д′)/~ ╧╧

翻桌


內建方法(Built-In Type Methods)

意即物件以屬性(或稱方法)的形式對外提供的行為,例如

const prompt = 'Hello World';
prompt.length; // 11

這背後的原因是每個型別基本上都會有其物件包裹器(object wrapper,又稱原生 natives)的型態做對應使用,例如資料型別 string 的物件包裹器型態就是 String,而就是這個包裹器型態在其原型(prototype)上定義了許多屬性和方法,因此這些資料型態就能如物件般擁有屬性和方法以供使用。

相等性與不等性

相等性(Equality)

關於相等性的運算子有四個「==」(寬鬆相等性 loose equality)、「===」(嚴格相等性 strict equality)、「!=」和「!==」。寬鬆與嚴格的差異在於檢查值相等時,是否會做強制轉型,== 會做強制轉型,而 === 不會。

const a = '100';
const b = 100;

a == b; // true,強制轉型,將字串 '100' 轉為數字 100
a === b; // false

另外,關於值的儲存方式有傳值「pass by value」和傳址/參考「pass by referece」兩種,其中 pass by value 又可再細分是否為「pass by sharing」(可參考這篇),基本型別是傳值,而物件型別是傳址。當比較兩物件時,比較的是儲存的位置,因此看起來是相同的物件,但比較結果卻是不相同的。

const a = [1, 2, 3, 4, 5];
const b = [1, 2, 3, 4, 5];

a == b; // false

不等性(Inequality)

關於不等性的比較運算子有 ><>=<= 共四種,在這裡有幾種狀況需要注意

'ab' < 'cd'; // true
'99' > 98; // true,字串 '99' 被強制轉型為數字 99

'Hello World' > 1; // false,字串 'Hello World' 無法轉為數字,變成 NaN
'Hello World' < 1; // false
'Hello World' = 1; // false
NaN > NaN; // false
NaN < NaN; // false
NaN === NaN; // false
NaN == NaN; // false

看到這裡如果覺得 JavaScript 很難學,莫慌莫急莫害怕,推薦一本好書給你。

從入門到放棄

轉行賣雞排吧你!


程式碼註解(Code Comments)

///* ... */

這陣子我們都看到一則新聞「碼農槍擊了 4 名同事導致一人情況危急」,程式碼註解有多重要就不用再提了吧!

程式碼註解(Code Comments)

變數(Variables)

變數是儲存值的地方,又稱為符號佔位器(symbolic placeholder),例如:a = b + 1 中的 a 和 b。變數的作用是「管理程式的狀態」,讓我們能將程式中各種會變動的狀態(也就是值)存起來並搭配運算子組成運算式做一些運算。注意,JavaScript 是弱型別的語言,意即宣告變數、賦值後仍可改變值的資料型別。

// 用 var 宣告一個物件
var product = {
  name: 'apple',
  count: 100,
};

// 用 let 宣告一個有作用域限制的變數,範圍限於大括號內
let name = 'Nina';

常數(Constants)

ES6 使用 const 來宣告常數,代表這變數的值不會改變,在嚴格模式下還會報錯。

const PI = 3.14;

命名規則

變數的命名必須要為有效的識別字,何謂有效?就是必須以 a-z、A-Z、$(錢字號)或 _(底線)開頭,之後可加上 a-z、A-Z、$(錢字號)、_(底線) 和數字 0-9,並且不可以是關鍵字或保留字。變數的命名規則同樣也適用於物件特性的命名,只是物件特性的名稱可為關鍵字或保留字。

var happy = 'happy'; // 這是合法的

var @@ = 'sad'; // 這是不合法的,報錯「Uncaught SyntaxError: Invalid or unexpected token」

var obj = {
  // 這是合法的,物件的特性可使用關鍵字或保留字命名
  this: 'this is an object',
};

什麼是關鍵字?又什麼是保留字?

關鍵字」(keyword)是指在目前 ECMAScript 中有特定用途的英文字詞,而「保留字」(reserved word)則是系統留用,雖然目前尚未用到但未來可能有其他用途的字彙。

再次強調,不管是關鍵字或保留字都不能做變數名稱使用。

區塊(Blocks)

區塊是由一對大括號(curly-brace pair,{ ... })所規範出來的範圍。

if (count > 10) {
  // 區塊範圍在此...
}

條件式(Conditionals)

表達條件式的方法有 if 述句、switch 述句、條件(三元)運算子和迴圈,以下分別述之。

const product = '100';
const shipment = 100;
const total = Number(product) + shipment; // 200

if (product > shipment) {
  console.log('Buy it!');
} else {
  console.log('Do not buy it!');
}

結果得到「Do not buy it!」。

const count = 50;

switch (count) {
  case 0:
  case 1:
  case 2:
    console.log('快賣完了!趕快進貨!');
  case 50:
    console.log('庫存充裕');
  case 100:
    console.log('是不是賣不掉了!?');
  default:
    console.log('運作正常');
}

但出乎意料的是,結果印出「庫存充裕、是不是賣不掉了!?、運作正常」。

庫存充裕
是不是賣不掉了!?
運作正常

這是因為如果沒有加入 break,一旦某個符合條件了,接下來的 case 無論符合與否都會被執行,也就是剛才所提到的「落穿而過」。

加入 break 修正一下。

const count = 50;

switch (count) {
  case 0:
  case 1:
  case 2:
    console.log('快賣完了!趕快進貨!');
    break;
  case 50:
    console.log('庫存充裕');
    break;
  case 100:
    console.log('是不是賣不掉了!?');
    break;
  default:
    console.log('運作正常');
}

結果印出

庫存充裕

Truthy & Falsy

在 JavaScript 中會被轉為 false 的值有

而除了以上之外,都會被轉為 true,舉例如下

如果真的很不確定到底會轉成什麼,可以使用 !! 做測試

!![]; // true
!!{}; // true
!!NaN; // false

迴圈(Loops)

重複一組動作,直到檢測條件不成立為止。迴圈的形式有很多種,最常用的就是 while 迴圈(while 或 do…while)和 for 迴圈兩種。

while 迴圈

while 迴圈的構成有以下要素:測試條件和區塊,而每次執行區塊時就稱為一次迭代(iteration)。

while vs do...while

兩者的差異在於 while 是先測後跑,而 do...while 是先跑後測。

來看第一個簡單的例子,。假設商品數量目前有五個,每賣掉一個就將庫存減一,當全賣完(及庫存為零)的時候就跳出迴圈,並印出「全部賣完了」的訊息。

let product = 5;

while (product > 0) {
  console.log('買一個!');
  product--;
  console.log(`現在還剩 ${product} 個。`);
}

console.log('全部賣完了');

while

但下面這個例子就超有不同了,此時更改商品數量為零,剛剛提到 while 是「先測後跑」,因此當檢驗測試條件時,發現 product > 0 得到 false,也就不會進入區塊了,直接印出「全部賣完了」的訊息。

let product = 0;

while (product > 0) {
  console.log('買一個!');
  product--;
  console.log(`現在還剩 ${product} 個。`);
}

console.log('全部賣完了');

再來看 while...loop,這個例子並無異狀,跟第一個例子所得到的結果完全相同。

let product = 5;

do {
  console.log('買一個!');
  product--;
  console.log(`現在還剩 ${product} 個。`);
} while (product > 0);

console.log('全部賣完了');

do...while

但是…剛剛提到 while...loop 是「先跑後測」,我們又將商品商品數量改為零, 此時會先進入區塊,依序印出「買一個!」、商品數量減一、顯示「現在還剩 -1 個。」,最後才檢驗測試條件,終止迴圈的執行,印出「全部賣完了」的訊息。

let product = 0;

do {
  console.log('買一個!');
  product--;
  console.log(`現在還剩 ${product} 個。`);
} while (product > 0);

console.log('全部賣完了');

do...while

break

使用 break 跳出迴圈。

範例如下,在 product 為 2 的時候跳出迴圈。

let product = 5;

while (product > 0) {
  console.log('買一個!');
  product--;
  console.log(`現在還剩 ${product} 個。`);

  if (product === 2) {
    console.log(`停停停,不要賣了!快進貨啊。`);
    break;
  }
}

console.log(`停賣後還剩 ${product} 個。`);

break

continue

使用 continue 跳過本次迭代,迴圈依舊持續進行。

範例如下,在 product 為 2 的時候忽略之後要執行的 console.log(現在還剩 ${product} 個。);,直接進入下一次迭代。

let product = 5;

while (product > 0) {
  console.log('買一個!');
  product--;

  if (product === 2) {
    console.log(`第二個我要暗摃起來`);
    continue;
  }

  console.log(`現在還剩 ${product} 個。`);
}

console.log('全部賣完了');

continue

for 迴圈

for 迴圈有三個子句-初始化子句、條件測試子句、更新子句。

for (let product = 5; product > 0; product--) {
  console.log('買一個!');
  console.log(`現在還剩 ${product} 個。`);
}

console.log('全部賣完了');

for

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

References


同步發表於2019 鐵人賽


You-Dont-Know-JS javascript 你所不知道的JS 2019鐵人賽 你懂JavaScript嗎? 鐵人賽 You-Dont-Know-JS-Up-and-Going ReferenceError undefined operator 運算子 NaN 你懂 JavaScript 嗎?2019 iT 邦幫忙鐵人賽 系列文