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。
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 Let、JavaScript 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 Syntax、JavaScript if else and else if、JavaScript Switch Statement、JavaScript For Loop、JavaScript 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);
}
JavaScript Arrays (w3schools)
更多細節請參考array
可以跟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。
JavaScript Object Constructors
雖然是定義function,但是,就很像迷你版的java class了
Destructuring
在javascript中,提供了destructuring的語法,destructuring可以用在陣列,也可以用在物件。這個語法在react中經常被使用,這裡先介紹基本概念。
How To Use Destructuring and Arrow Functions to Improve Your JavaScript Code
JavaScript: Use Destructuring Assignment over Function Parameters
7 JavaScript Patterns Part 1: Making Variables from Properties / Destructuring assignment
What is so special about ES6 Destructuring
Object Destructuring
Array Destructuring
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
3 Practical Uses of Object Destructuring in JavaScript
Named Function Arguments
Cleanly Parse a Server Response
Setting Default Values During Assignment
Seven things you should know about Object Destructuring in ES6
Basic Assignment
Alias
Default value
Alias and Default value
function parameter destructuring
Nested Object function parameter destructuring
spread in Object Destructuring
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]
One syntax, two features
Spread
Using the spread operator with objects
Using the spread operator with arrays
Using the spread operator in function calls
Rest
Using rest in destructuring assignment
Using rest in a function signature
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裡。
Asynchronous Javascript (吳濟聰老師專題知識分享)
參考資料
ES5、ES6
Javascript: 吳濟聰老師專題知識分享
JavaScript Doodles: A quick reference to some relevant JavaScript concepts
http://openhome.cc/Gossip/CodeData/EssentialJavaScript/index.html
5 JavaScript Tips to Improve Your React Code
How to Use the Optional Chaining Operator in JavaScript
** 程式好像有錯,請參閱回應
How to Use Implicit Return with Parentheses in JavaScript
How to Use the Nullish Coalescing Operator in JavaScript
How to Use the Object Spread Operator for Updating State in JavaScript
How to Use Ternaries to Conditionally Apply Classes / Props in JavaScript