從 JavaScript 踏入程式語言


Posted by ai86109 on 2020-06-21

既然是踏入程式語言,所以以下的內容,或者可以說是筆記,都會圍繞著最基本且實務面上最常遇到的語法來做紀錄。

通常會選 JavaScript 作為第一個程式語言的,八九不離十是和網頁前端有關。的確,從前從前 JavaScript 也只能跑在瀏覽器的環境中,直到 Node.js 的出現。

什麼是 Node.js?

他不是程式語言,他跟瀏覽器一樣都是 JavaScript 的執行環境(runtime environment),JavaScript 不能單獨執行,他一定要有執行環境。

要怎麼在 Node.js 上執行 JavaScript 呢?

安裝很簡單。
到官網 > download > LTS > 選自己的系統下載安裝。
到 terminal > 輸入 node -v > 如有印出版本表示安裝成功。

執行的部分也不困難,在說 Node.js 前,先說一下怎麼在瀏覽器上執行 JavaScript。

  • 先創一個檔案 vim index.html ,並且寫入<script> console.log(‘123’) </script>
  • 存檔離開後,打開網頁 open index.html
  • 進到網頁後, option+command+i 就可以看到剛剛 log 的 123。

那要如何在 Node.js 上執行呢?

  • 同理,創造一個 JS 檔案。
  • 前面說的 open 改成 node 即可。
  • node index.js
  • 就會印在 terminal 上了。

在 Node.js 也可以像在瀏覽器在 console 上直接做運算嗎?
在 terminal 上輸入 node(記得 Enter),就可以直接把程式碼打上輸出了。


以上學會了如何輸出,接下來就 focus 在 JavaScript 的語法上!

加減乘除很簡單就不提了。
取餘數寫成這樣 10 % 3,就會印出 1。


邏輯運算

  • && (and)
  • || (or)
  • ! (not)

以下是範例。

true || true
true (其中一個是true就是true)
true || false
true
true && false
false(兩個都是 true才是 true)
true && true
true
!true
false
!false
true

至於如果把 true, false 換成數字也會用一樣的邏輯印出答案。

3 || 123
3(先看 3 發現是 true,直接印出來)
0 || 123
123(看 0 是 false,再往下看 123 是 true,印出)
11 && 0
0(11 是 true,往下看 0 是 false,印出)
0 && 11
0(看到 0 是 false,直接印出)
123 && 1
1(看到 123 是 true,再往下看 1 也是 true,印出)

位移運算子

這邊要先提到 2 進位。
0100 = 2^2 = 4
1000 = 2^3 = 8
而這邊的位移運算子 << 代表進一位,而 >> 則代表退一位(因為是 2 進位所以可以想成 *2 or /2)
所以 10 << 1 等於 20
10 << 3 等於 80

那如果不是 2 的倍數,看起來是會無條件捨去。
9 >> 1 等於 4
13 >> 1 等於 6
13 >> 2 等於 3(13/2 = 6.5,無條件捨去變 6,6 再 /2 = 3)

這樣的位元運算會比一般的乘二除二效率還快。


接著要來講位元運算(2 進位)

這跟之前的不一樣,會先將數字變成 2 進位,再針對每個位數做運算。

  • & (and)
  • | (or)
  • ^ (xor,兩個一樣 =0, 兩個不一樣 =1)
  • ~ (not)

5 & 9
首先會先換成 2 進位

5 => 2 的 2 次方+2 的 0次方 =>   101
9 => 2 的 3 次方+2 的 0次方 =>  1001
再來做位元運算(題目用&)        -------
                              0001

0001 = 2 的 0 次方 = 1

5 | 9
首先會先換成 2 進位

5 => 2 的 2 次方+2 的 0 次方 =>    101
9 => 2 的 3 次方+2 的 0 次方 =>   1001
再來做位元運算(題目用|)          -------
                                1101

1101 = 2 的 3 次方+2 的 2 次方+2 的 0 次方 = 13

5 ^ 9
首先會先換成 2 進位

5 => 2 的 2 次方+2 的 0 次方 =>    101
9 => 2 的 3 次方+2 的 0 次方 =>   1001
再來做位元運算(題目用^)          -------
                                1100

1101 = 2 的 3 次方+2 的 2 次方 = 12

可以利用位元運算的特性去做條件~

例如說可以用來判斷基數偶數,要先知道二進位的數,最後一位只要是 0 就是偶數,1 就是基數
1000 ⇒ 8
1001 ⇒ 9
所以任意數和 1 用 and 比較時,偶數會輸出 0,基數會輸出 1
1000 & 1 = 0
1001 & 1 = 1


變數

變數的命名,基本上有兩種流派。

  1. var this_is_a_book
  2. var thisIsABook

變數命名有限制,不能以數字開頭,不能使用保留字。

這邊既然提到變數,就要提到賦值,簡單的賦值就不說了。

var a = 0
a = a + 5
console.log(a)
=> 5

這邊的等於是賦值的意思。

a = a + 5 可以簡化為 a += 5
a += 1 有其他寫法嗎?
可以寫成 a++
a++ 就有 a--
那可以寫成 ++a 嗎?他們是不同意思

var a = 0
console.log(a++ && 20)
=> 0
var a = 0
console.log(++a && 20)
=> 20

前者可以想成

console.log(a && 20)
a+=1

後者可以想成

a+=1
console.log(a && 20)

那變數有什麼型態呢?

可以利用 typeof 來查
typeof true
可以得到 boolean number string object function undefined(前三種是 primitive)
其中比較要注意的是陣列會是 object
null 也會印出 object(曾經有討論要修掉,但被 reject 了)

接下來講 array
形式是[a, b, c],裡面的 a, b, c 稱為索引(index)
可以直接定義 var array = [x, y, z]
也可以用加入的

var array = []
array[0] = xxx
array[1] = yyy

或是可以用 push 的,用 push 會接續往後加
所以如果是以下

var array = []
array[100] = 12
array.push(13)

這個 array 0~99 會自動補上空值,接著才是依序的 12, 13。

除了 array 還有 object
形式是

{
    key: value,
}

要如何取值呢?
先做一個 object

var peter = {
    name: ‘Peter’,
    score: 98,
    address: “Taipei”,
    father: {
        name: Tom,
        phone: ’12345’
    }
}

利用.來取值
console.log(peter.name)
或是利用索引
console.log(peter[‘father’][‘name’])

為什麼要用索引呢?因為如果你 peter 後面是要接變數才有辦法接。

var key = 'name'
console.log(peter[key])

否則 peter.key 會是 undefined。


變數運算

其實沒那麼簡單

var a = ’10’
var b = 20
console.log(a + b)

想當然耳答案是 30,但等等...答案怎麼是 1020
當數字與字串做相加時,會將數字也理解成字串,變成字串相加
這時要解決有兩種方法:
(1)用 Number
console.log(Number(a) + b)
注意:N 一定要大寫
(2)用 parseInt
console.log(parseInt(a, 10) + b)
注意:parseInt 裡面第二格填的是進位,這邊填 10 代表示 10 進位,如果不填預設為 10 進位,但還是建議填;如果填 2,答案會變成 22

好吧既然字串+數字這麼麻煩,我都用數字總沒錯了吧!

var a = 0.1 + 0.2
console.log(a == 0.3)

結果答案竟然是 false,難道我數學有問題嗎?
用 console 來看一下 a

> console.log(a)
0.30000000000000004

這就是浮點數誤差(所以盡量不要用到小數)
參考:使用浮點數最最基本的觀念


===== 在比較上有何差別?
差別在 === 會多比較型態
所以建議都用 ===

Object 指向的是記憶體位置
所以 console.log({a: 1} === {a:1})
會是 false


條件判斷

有輕鬆寫意的 if, else if, else

也有 switch, case

var month = 1

switch(month) {
    case 1:
        console.log('一月')
        break
    case 2:
        console.log('二月')
        break
    default:
        console.log('haha')
}

沒加 break 就會把後面都輸出
都不符合會印出預設值
另外也可以這樣用

var month = 1

switch(month) {
    case 1:
    case 2:
        console.log('一月')
        break
    default:
        console.log('haha')
}

這樣 month 是 1 or 2 都會輸出一月
不過這種題目以下解法會更好

var month = 1

var monthToChinese = ['一月', '二月', '三月']
console.log(monthToChinese[month - 1])

三元運算子(ternary operator)

條件 ? true的答案 : false的答案
這個很常使用!


迴圈(loop)

接下來要講一個很重要的觀念,迴圈loop。
首先要先知道 do...while...

var i = 1

do{
    console.log(i * i)
    i++
}while(i <= 5)

console.log('i=', i)

//印出
1
4
9
16
25
i= 6

從上面可以看出來 while 後面是接條件,如果是 true 則回到 do 繼續執行,直到 while 條件不符合時,才會往下執行,這就是 do...while 迴圈最常見的寫法。

接著用其他寫法來認識 JS

do{
    console.log(i * i)
    i++
    if (i >10){
    break
    }
}while(true)

當執行 break 時,跳出 do...while 迴圈。

break,當然也有 continue

var i = 1

do{
    console.log(i * i)
    i++
    if(i % 2 === 1){
        continue
    }
    console.log('keep going')
}while(i <= 5)

console.log('i=', i)

//印出
1
keep going
4
9
keep going
16
25
keep going
i= 6
以上面的條件看來,當 i 為奇數時會啟動 continue,接著會驗證完 while 後直接跳回執行 do,所以 console.log('keep going’) 就不會被執行到。

以上的執行剛開始可能會不熟悉或不知道為何會這樣印出,可以一步一步去推敲,除此之外也可以用瀏覽器來做一步一步的推敲,只要在最前面加上 debugger

<script>
    debugger
    do{
        console.log(i * i)
        i++
        if(i % 2 === 1){
            continue
        }
        console.log('keep going')
    }while(i <= 5)

    console.log('i=', i)
</script>

到瀏覽器 > source > 會停在 debugger 處,不會向下執行。
這時候就可以按箭頭一步一步看程式是怎麼樣執行的。
滑鼠移到變數上,也可以看到目前是多少,例如 i=2
如果不想這麼麻煩,可以在 watch 那邊加上 i,這樣他就會即時顯示變數的值了。

另外,可以在程式的行數那邊點一下,可以製造一個 breakpoint 中斷點,按一下 resume script execution,會從 debugger 處執行到中斷點,再按一下則會再到下一個,也是一個很實用的功能。

補充一下,如果寫出無窮迴圈,該怎麼終止呢?
在 Mac 是 ctrl+c

回到剛剛的 do...while 迴圈是先執行一次再做判斷,那有先判斷在執行的嗎?
有,就是 while 迴圈

var i = 1
while(i <= 5){
    console.log(i * i)
    i++
}

觀看剛剛寫的迴圈,可以發現都會有幾個常見的值。

var i = 1                      //初始值
while(i <= 5){                 //終止條件
    console.log(i * i)
    i++                        // i每一圈要做的事
}

For 迴圈可以把這些東西寫在同一行
for(初始值; 終止條件; i每一圈要做的事){ }

for(var i =1; i<=5; i++){            
    console.log(i * i)
}

Function

基礎的最後一塊拼圖就是 function
除了

function name(){

}

這種宣告函式的方式外,也可以用

var hello = function name(){

}

function 的參數也可以傳參數進去,例如:
我想要將一組陣列,裡面的數字都變成兩倍

function transform(arr, transformFunction){
    var result = []
    for(i=0; i<arr.length; i++){
        result.push(transformFunction(arr[i]))
    }
    return result
}

function double(x){
    return x*2
}

console.log(transform([1, 2, 3], double))

//結果便是[ 2, 4, 6 ],當然我們也可以將 double 這個 function 縮進來改寫成

function transform(arr, transformFunction){
    var result = []
    for(i=0; i<arr.length; i++){
        result.push(transformFunction(arr[i]))
    }
    return result
}

console.log(transform([1, 2, 3], function double(x){
    return x*2
}))

而這個沒有名字的函式,就稱為匿名函式(anonymous function)。

這邊要來名詞解釋一下,什麼是參數?什麼是引數?

function add(a, b){
    return a+b
}

console.log(1, 2)

參數(parameter)就是剛剛 function() 裡的東西,也就是 a 和 b
引數(argument)就是你真正傳進去的東西,也就是 1 和 2
而在 JS 有一個特別的方法可以使用 argument

function add(){
    return arguments[0] + arguments[1]
}

console.log(1, 2)

所以即使參數不填依然可以讀到,但幾乎不會用到就是了。
再來就是 arguments 既然可以以[0]這樣形式去取值,但他其實不是陣列,而是物件。
以上面的例子,將 argument 印出來會是一個長這樣的 object
{'0': 1, '1': 2}


關於 JavaScript 的參數傳遞可以看這篇:深入探討 JavaScript 中的參數傳遞:call by value 還是 reference?
但這裡其實沒有要講這麼細,有興趣再看。


再來簡單說一下 return

當你執行一個 function,而沒有寫 return 時,其實 JS 預設會直接幫你加上 return undefined 這個值。
而當你有寫 return 的狀況,跑到 return 時會直接回傳,所以 return 之後的程式碼並不會被讀到。


接下來了解一些內建函式,如果知道這些內建函式,除了可以更有效率也可以使程式碼更簡潔。

(1) Number類型的內建函式

前面有說過 Number, parseInt
但沒有提到,parseInt 其實只會取整數部分,如果後有小數會忽略,因為 Int 就是 Integral 整數的意思。

如果想要取小數呢?
parseFloat
那如果我只想印到小數後兩位怎麼辦

var a = 36.123456
console.log(parseFloat(a).toFixed(2))

但如果 toFixed 後面括號沒填,則會將小數全去掉;另外也要注意它會四捨五入。

無條件進位(ceiling 天花板的意思)
Math.ceil()

無條件捨去(floor地板的意思)
Math.floor()

四捨五入
Math.round()

開根號(square root)
Math.sqrt()

取最大值
Math.max()
括號內可以直接填入數字 Math.max(1, 3, 2)
也可以填入參考的地方,記得加入三個點。

var arr = [1, 3, 2]
Math.max(…arr)

反之取最小值
Math.min()

次方
Math.pow(數字, 次方)

產生 0~小於 1 的隨機數
Math.random()
這就可以應用,例如 console.log(Math.floor(Math.random()*10 + 1)),就可以印出 1~10 隨機整數。

數字變字串有兩種方法:

var a = 2
a.toString()

或是

var a = 2
(a + ‘’)

第二種說明:數字加上空字串也會是字串。


(2) String類型的內建函式

變大寫
console.log('abc'.toUpperCase())

反之
toLowerCase()

在 JS 每個字母都有個代表的數字(ASCII code),當儲存字串時其實實際上存取這個代號,那要如何取得這個值呢?

var a = ‘BACFG’ //有一個字串
var aCode = a.charCodeAt(1) //我想取 A 這個字的 character code,括號數字就跟 array 的數法一樣
console.log(aCode) //就可以得到 A 是 65

反之用數字找字母

var str = String.fromCharCode(65)
console.log(str) //可以得到 A

字母是按照順序的,A是65, B是66...a是97

根據以上,字串是可以比大小的

> console.log('A' <= 'a')
true

那有辦法尋找某字是否存在字串中嗎?

var str = ‘hey hello are you ok’
var index = str.indexOf(‘hello’)
console.log(index)

答案會回傳 4,表示 hello 的第一個字出現在字串的 4 號位。

但如果字串沒有這個字,則會回傳負數
console.log(index < 0)
可以利用這個特性

可以找字那可以改字串內容嗎?
var str = ‘hey hello are you ok hey’.replace(‘hey’, ‘!!!!’)
就可以把字串改成 ‘!!!! hello are you ok hey’,但只會改到第一個
如果都要換呢?
var str = ‘hey hello are you ok hey’.replace(/hey/g, ‘!!!!’)
只要將引號換成反斜線,後面加個 g(g 是 global 的意思)

將字串切割輸出成陣列

var str = 'data1,data2,data3,data4'
console.log(str.split(','))

就會輸出[ 'data1', 'data2', 'data3', 'data4' ]
split 的地方會被切掉,下面出現的逗號是因為 array
這可以用在 csv 格式

切掉字串前後空格

var str = ‘  data1,data2,data3,data4   '
console.log(str.trim())

(3) array類型的內建函式

將陣列 modified 後轉成字串

var arr = [1, 2, 3]
console.log(arr.join(‘,’))

便會印出 1,2,3
所以如果 join 後面括號不填值,也會印出 1,2,3(預設是加上逗號分隔)
若要印出 123,則輸入 console.log(arr.join(‘’))

將陣列的每個值經 function 作用後,輸出一個新的 array

var arr = [1, 2, 3]
console.log(arr.map(function(x){
    return x*2
}))

function 直接寫在裡面,或是寫外面再帶入都可以,因為 map 出來的也是 array,所以 .map() 後面還可以再接 .map

有一個用法跟 map 很像的是 filter,他會回傳答案是 true 的值

var arr = [1, 2, -5, 3, -4]
console.log(arr.filter(function(x){
    return x > 0
}))

便會印出[1, 2, 3]

那有辦法剪出我要的陣列區段嗎

var arr = [1, 2, -5, 3, -4]
console.log(arr.slice(2, 4)) //slice(切的起始點, 切的終點),第二個數字沒有指定的話會切到完
會印出[-5, 3]

有一個跟 slice 長很像的是 splice
他可以做新增&刪除的動作

var month = [‘Jan’, ‘March’, ‘April’, ‘June’]
month.splice(1, 0, ‘Feb’)                            // (插入點, 刪除幾個值, 插入的值)
console.log(month)

所以會印出[‘Jan’, ‘Feb’, ‘March’, ‘April’, ‘June’]

month.splice(4, 1, ‘May’)
console.log(month)

會得到[‘Jan’, ‘Feb’, ‘March’, ‘April’, ‘May’]
這邊要特別注意的是 slice 會回傳一個新的陣列,原本的陣列不變;但 splice 會改到原本的陣列。

接下來要講到用來排序陣列的sort

var month = [‘March’, ‘April’, ‘Nov’]
month.sort()
console.log(month)

會印出[‘April’, ‘March’, ‘Nov’] //照第一個字母排列

var arr = [1, 30, 22, 9]
arr.sort()
console.log(arr)

會印出[1, 22, 30, 9] //會將他當成字串,按照第一個數字排列

那如果想要讓數字照大小排列呢?
需要在 sort 後面加參數

arr.sort(function(a, b){
    return 
})

這邊會將 array 隨意兩個數傳入,分別為 a 和 b,這邊的規則是

當 return < 0 a 前 b 後
當 return = 0 a, b 不動
當 return > 0 b 前 a 後

以從小到大排列為例
我們假設 a 是 1, b 是 30,所以由小到大 a 和 b 不用換位子,不用換位子代表 return 回傳是負數
什麼公式可以讓 a, b 是負數,a-b = 1-30 = 負數
那如果 a 是 30, b 是 1 呢,這個情況 a-b = 正數,也符合我們的從小排到大

var arr = [1, 30, 22, 9]
arr.sort(function(a, b){
    return a - b
})
console.log(arr)

印出[ 1, 9, 22, 30 ]
所以反過來要從大排到小,function 就換成
return b - a

最後我們要將array的值反轉

var arr = [1, 2, 3]
arr.reverse()
console.log(arr)

就會印出[3, 2, 1]

還有一個很常用到的是 arr.forEach
我們有時候會用到

var total = 0
    for(i=0; i<arr.length; i++){
        total+=arr[i]
    }

但其實可以寫成

var total = 0
arr.forEach(function(value){
    total+=value
})

他會將 arr 的值依序帶入執行裡面的動作

但如果像上面的程式碼,就會需要在外面 var total = 0
有沒有辦法可以將累加的東西放在內部呢
有,就是利用 reduce

[1, 2, 3].reduce(function(accumulator, currentValue){
    return currentValue + accumulator
}, 10)

reduce 後面有兩個數,一個是 function,一個是 accumulator 起始值(沒有填預設就是 null)
reduce 會依序取出 array 的數,放入 currentValue
所以第一圈 currentValue = 1, accumulator = 10,1+10=11,11 會放入 accumulator,再進行第二圈
最後跑完後得到的值就是 accumulator
答案=16
這邊注意 reduce 不只可以拿來累加,而是 accumulator 這格可以拿來累加,function 內部要做什麼條件,或加減多少到 accumulator 都可以自行調整。


Immutable 不可變

有一個基礎觀念是非常重要的,就是 immutable 不可變
除了物件以外,其他的直創造出來之後就是不可變得,舉凡字串、數字等等
最經典的例子就是

var a = ‘hello’
a.toUpperCase()
console.log(a)

你預期的結果應該是 HELLO 吧,但印出來的還是 hello,這是為什麼
原因在儲存的記憶體

var a = ‘hello’ //此時 a 的值會存在 0x01
a.toUpperCase() //因為 a 的值是字串不可變,所以不會發生任何事
console.log(a) //所以 log 出來的還是 hello

那要怎麼辦呢

var a = ‘hello’ //此時 a 的值會存在 0x01
a = a.toUpperCase() //重新將 a 賦值,此時新的 a 的值會存在 0x02
console.log(a) //所以 log 出來才會是 HELLO

反之,對 object array 操作有可能會改動到原本的值,如果這個動作會產生一個新的值
就需要創造一個東西去接他的值。


最後的最後,請愛用 console.log 去 debug。


#javascript #node.js







Related Posts

學習 Git (7) - 分支

學習 Git (7) - 分支

如何用 Markov Decision Process 描述 Human-Robot Interaction 問題

如何用 Markov Decision Process 描述 Human-Robot Interaction 問題

methods 和 computed 比較

methods 和 computed 比較


Comments