Javascript

JavaScript

2021/09/11 (更新內容)
2021/09/21 (補充內容)

基本概念

HTML構成的網頁是靜態的,要讓網頁「動」起來,比較常用的方式就是使用JavaScript,JavaScript跟HTML不太一樣,基本上JavaScript是一種程式語言,JavaScript雖然有個Java,跟Java在概念及語法雖然有些相同但是大部分還是不一樣的。比如說,Java是Strongly typed,但是,JavaScript卻是Weakly(loosely) typed,簡單的說,在JavaScript中,變數的資料型態是可以改變的,但是,在Java中,變數的資料型態是不可以改變的。

如果想利用雲端服務來執行javascript

  • 可以利用JS Fiddle,會看到HTML、CSS、Javascript,把javascript的內容放到javascript,按"Run"。

  • 也可以利用CodePen,會看到HTML、CSS、JS,把javascript的內容放到JS,完成之後,就會直接執行。

  • 也可以利用CodeSandbox ,如果只要執行javascript,請選擇「Vanilla 」,完成之後,就會直接執行。CodeSandbox比較像一般的程式編輯器,可以有多個html、css、js檔案,雖然比較複雜,但是功能也比較完整。接下來如果react的部分建議使用CodeSandbox

第一個程式

網頁裡的javascript

通常開發者會利用編輯器 (如:vs code或sublime、不能使用word或wordpad來編寫程式),來寫這個程式,因為我們是在html中寫javascript,所以,要告訴瀏覽器這是javascript,如果只是:

alert("Hi!");

會發現瀏覽器會直接出現以上內容。

一定要加上<script></script>

first.html

<script>

alert("Hi!");

</script>

利用任何瀏覽器打開,就會看到瀏覽器跳出"Hi!",因為alert方法就是告訴瀏覽器,把括號裡的內容(Hi!)以對話框的方式顯示出來,我們用雙引號或單引號告訴瀏覽器這是一個字串。

如果是以html的方式,在JS Fiddle,把html的內容放到html,按"Run"。

變數

定義

在javascript裡,變數的使用跟java非常不一樣,變數可以不用先定義:

qty = 8;

qty++;

變數的資料型態可以隨時改變:

qty = 8;

qty++;

console.log(qty);

qty = "Hello";

console.log(qty);

qty = 3.3;

console.log(qty);

//output:

//9

//"Hello"

//3.3

這樣的特性,讓程式很有彈性,但是,相對的,如果命名打錯字了,就會被當作是新變數,不容易看出錯誤,例如:

quality = 5;










qulity = 6;

console.log(quality);

//output:5

為了避免這樣的問題,在ES5以後,可以使用JavaScript Use Strict,這樣的話,就強迫變數在使用前一定要先定義。在react裡,預設use strict,所以,使用變數之前一定要定義。

"use strict";

var quality = 5;





qulity = 6;

console.log(quality);

//ReferenceError: qulity is not defined

除此之外,在javascript (ES6 /ES2015以後)裡,建議使用let及const來定義變數,let是定義一個變數,所以,變數內容可以改變,const就不可以改變了。(詳參: JavaScript LetJavaScript Const)

let qty = 30;

const size = 10;

qty ++;

size ++; //error!!!

var可以重複定義,但是,let不可以重複定義。

var num = 4;

var num = 5;


let secondNum = 6;

let secondNum = 7; //Identifier 'secondNum' has already been declared

變數範圍

使用const時,變數是有範圍的,最基礎的範圍就是在大括號內:

{

const num = 4;

}

console.log(num);

//output: num is not defined

let也是

{

let secondNum = 6;

}

console.log(secondNum);

//output: secondNum is not defined

可是var就不是

{

var num = 4;

}

console.log(num);

//output: 4

利用var雖然方便,也不用管變數的範圍,但是假如在一個程式中,如果程式碼很長,在某個部分,如果重複用了同樣名稱的變數,而且改了內容,會不清楚為何num的內容會改變:

var num = 8;

//..

{

//..

var num = 4;

//..

}

console.log(num);

//output:4

利用let可以分辨不同區域的變數:

let num = 8;

//..

{

//..

let num = 4;

}

console.log(num);

//output:8

跟其他的語言一樣,在區塊裡是可以更動num的內容:

let num = 8;

//..

{

//..

num = 4;

}

console.log(num);

//output:4

基本語法

javascript的一些基本語法,如條件式、迴圈都跟c、c++、java類似 (詳參: JavaScript SyntaxJavaScript if else and else ifJavaScript Switch StatementJavaScript For LoopJavaScript While Loop)

比較不同的是,javascript有 for in 迴圈,可以用在陣列及物件。for of 迴圈,可以用在物件。

陣列的語法跟php有點類似。定義一個陣列

let persons = ["Ben", "Mary", "Tom"];

列出所有陣列的元素,有幾種做法:

//基本寫法

for (let i = 0; i < 3; i ++) {

console.log(persons[i];

}


for (let i in persons) {

console.log(persons[i]);

}


//類似php及java的foreach

for (let person of persons) {

console.log(person);

}

可以跟php一樣,利用關鍵字function定義一個函數,也可以預設參數值:

function times(a=1, b=1) {

return a * b;

}


console.log(times(1,2));

console.log(times(3));

console.log(times());

//output:

//2

//3

//1

更多細節,請參考function

object

javascript的object,跟php與java很不一樣,php與java裡的object要透過類別(class)來產生,但是,javascript的object則不一定要。例如:

let person = {name:"Ben", age:6};

console.log(person);

console.log(person.name);

console.log(person.age);

這樣就定義了一個物件person,可以利用person.name,取得name的內容。

輸出:

{ name: 'Ben', age: 6 }

Ben

6

其實,這個使用方法比較像php的associative array,所以,也可以這樣取得資料:

let person = {name:"Ben", age:6};

console.log(person);

console.log(person["name"]);

console.log(person["age"]);

如果物件的值來自於變數:

let weight = 60;

let height = 170;

let person = {weight:weight, height:height};

console.log(person);

//output:

//{weight:60, height:170}

也可以這樣寫:

let weight = 60;

let height = 170;

let person = {weight, height};

console.log(person);

//output:

//{weight: 60, height: 170}

object也可以套用for in迴圈,不過,不能使用for of。

let weight = 20;

let height = 30;

let person = {weight, height};

for (let i in person){

console.log(i);

}


for (let i in person){

console.log(person[i]);

}

//output:

//weight

//height

//20

//30

跟associative array不一樣的是,javascript的物件是可以有function,在function裡要取得變數,跟java一樣,要利用this:

let person = {

name:"Ben",

age:6,

getName: function(){

return this.name;

},

setName: function(name){

this.name = name;

}

};

person.setName("Mary");

console.log(person.getName());

console.log(person["age"]);

當然,在javascript裡是可以透過類別產生物件,請詳參class

Destructuring

在javascript中,提供了destructuring的語法,destructuring可以用在陣列,也可以用在物件。這個語法在react中經常被使用,這裡先介紹基本概念。

Array destructuring

javascript的destructuring(解構賦值)使用在陣列時,可以簡化語法,例如:

const [first, second] = numbers;

就是將numbers裡的前兩個元素依序放到first跟second兩個變數。

let numbers = [2, 4, 6, 8, 10];
const
[first, second] = numbers;
console.log(first);
console.log(second);
//output:
//2
//4

其實就等於:

let numbers = [2, 4, 6, 8, 10];
const first = numbers[0];
const second = numbers[1];
console.log(first);
console.log(second);
//output:
//2
//4

Object destructuring

object destructuring跟array不一樣的是,array是依順序對應變數,object則是依名稱對應。

let people = {name:"Ben", age:6, weight:60, height:170};


let {age, name} = people;

console.log(name);

console.log(age);

//output:

//Ben

//6

Spread/Rest

ES6有個「...」的語法,這個語法有兩個功能:spread、rest,Spread就是展開內容的意思,Rest就是取剩餘的內容的意思。

Spread (展開)

這個語法可以展開物件、陣列以及function的參數。在以陣列為例,當我們要產生一個新的陣列,需要原本陣列的內容,就可以利用:

let numbers = [2, 4, 6, 8, 10];
let second = [
...numbers];
second[1] = 100;
console.log(numbers);
console.log(second);
//output:
//[2,4,6,8,10]
//[2,100,6,8,10]

要記得,如果只是

let second = numbers;

second及numbers是指向同一個陣列

let numbers = [2, 4, 6, 8, 10];
let second =
numbers;
second[1] = 100;
console.log(numbers);
console.log(second);
//output:
//[2,
100,6,8,10]
//[2,100,6,8,10]

如果在陣列前後增加元素:

let numbers = [2, 4, 6, 8, 10];
let second = [
0,...numbers,150];
second[1] = 100;
console.log(numbers);
console.log(second);
//output:
//[2,4,6,8,10]
//[0,100,4,6,8,10,150]

其餘 Rest

這個語法可以取得物件、陣列以及function的參數剩下的。如果結合前面destructuring,可以把陣列的第一個元素給first,第二的元素依序給second,其他的給others。

const [first, second, ...others] = [2, 4, 6, 8, 10];


console.log(first);

console.log(second);

console.log(others);

//output:

//2

//4

//[6,8,10]

Asynchronous

有時候Javascript會產生非同步的動作,我們比較常見的是呼叫外部的API,這時候,我們不希望javascript會停下來等,這樣會讓使用者覺得是當機了,所以,我們會以非同步的方式呼叫,也就是讓javascript的其他部分繼續執行,等非同步的部分(如:呼叫API)有了回應,再去處理回應。

簡單的說,一開始是利用callback function,也就是提供被呼叫者一個callback function,當被呼叫者完成動作時,透過呼叫callback function來處理回應。可是,當呼叫比較複雜的時候,就會產生callback hell。為了避免callback hell,javascript (ES6)提供了Promise。後來,javascript (ES8)基於Promise,提供了async/await的語法,提高程式碼的可讀性。

先寫一個回傳Promise的function,利用resolve回傳資料。

let getData = ()=> {

return new Promise((resolve)=> {

//模擬要花時間取得資料

setTimeout(

()=>resolve([{name:'Ben', age:30},{name:'Mary', age:20}])

, 3000);

});

}

呼叫getData,因為回傳的是Promise,利用Promise的then方法,取得回傳內容。

getData().then(data=>{console.log(data)});

完整的程式:

let getData = ()=> {

return new Promise((resolve)=> {

//模擬要花時間取得資料

setTimeout(

()=>resolve([{name:'Ben', age:30},{name:'Mary', age:20}])

, 3000);

});

}


console.log("start");

getData().then(data=>{console.log(data)});

console.log("end");

執行的時候,會發現,程式並不會等候getData回傳資料,會往下執行,等到取得資料時,才會去執行.then裡的arrow function。

start

end

[{name:'Ben', age:30},{name:'Mary', age:20}]

失敗的話,利用reject回傳資料。

let getData = ()=> {

return new Promise((resolve,reject)=> {

//模擬要花時間取得資料

setTimeout(

()=>reject('Error')

//()=>resolve([{name:'Ben', age:30},{name:'Mary', age:20}])

, 3000);

});

}


console.log("start");

getData()

.then(data=>{console.log(data)})

.catch(data=>{console.log(data)});

console.log("end");

執行的時候,會去執行.catch裡的arrow function。

start

end

Error

改用async/await語法,看到await,javascript就會停下來,等待getData執行完,再往下執行:

const getRemoteData = async function() {

result = await getData();

console.log(result);

}

改用arrow function:

const getRemoteData = async()=> {

result = await getData();

console.log(result);

}

完整的程式:

let getData = ()=> {

return new Promise((resolve,reject)=> {

//模擬要花時間取得資料

setTimeout(

//()=>reject('Error')

()=>resolve([{name:'Ben', age:30},{name:'Mary', age:20}])

, 3000);

});

}


const getRemoteData = async()=> {

result = await getData();

console.log(result);

}


console.log("start");

result = getRemoteData();

console.log(result);

console.log("end");

結果是

start

Promise(pending)

end
[{name:'Ben', age:30},{name:'Mary', age:20}]

一樣,當呼叫getRemoteData時,不會等候回傳資料,會直接往下執行,所以,要處理回傳值,就要寫在async裡。

參考資料