# 二十、 常出錯的地方
###### tags: `JavaScript` `JS101` `2020七月第三週` `進度筆記` `Lidemy心得` `7/13`
---
## 「回傳」與「印出」的差異
### 有甚麼東西在角落出現了...
function add(a, b) { // 回傳參數 ;
console.log(a, b) // 到這裡印出 1, 2 ;
return undefined // 預設回傳;
}
console.log(add(1, 2)) // 這行先執行; 等程式全跑完,到最後回傳值 undefined ;
會印出 1, 2 和 undefined 。
---
### 原來是眼殘,沒有出現任何東西
function add(a, b) {
return a + b
}
add(1, 2)
沒有 `console.log` 就不會印出任何東西。
很多線上解題都是用 ''回傳'' 沒有用 `console.log` 。
幾乎都是對函式的回傳值做測試,而不是對 "印出(log)" 。
---
### 如何召喚角落生物:
比較好的寫法:
function add(a, b) {
return a + b
}
console.log(add(1, 2))
會出現 3 。
---
不常用的方法,將結果回傳:
function add(a, b) {
// return a + b 這裡沒有回傳的話,最後一行沒有意義;
console.log(a+b)
}
console.log(add(1, 2))
出現 3 跟 undefined , 因為沒有 `return a + b` 。
---
### 瀏覽器比較特別
在用瀏覽器時,每執行一行指令,就會自動 印出(log) 出來一個值:

---
如果輸入 `console.log(a+3)` 會出現:

4 的前面沒有 "<·" , 表示 `console.log` 印出來的東西; 而語句前面有 "<·" 表示命令、函式...等,其回傳值。
用 `console.log` 在 CLI 上的好處是一定會印出你需求的值。
---
# 二十一、 重要的賦值後不變 (immutable) 觀念
## 幾乎除了物件、陣列以外的都是原始型態,且賦值後不變
範例:
var a = 'hello'
// a 這個值是可以改變的,但字串 'hello' 賦值後是不會變的;
a = "yo"
---
a: 'hello' 0x01
a: "yo" 0x02
以上面的值來看,一開始 a 是給 'hello' , 其 'hello' 假設在記憶體內有個存取空間 0x01 。
而 'yo' 是被創造一個新的記憶體空間 0x02 。
而如果第二行改成 a = a +'yo' 。
其會再創造一個新的記憶體空間,假設 a: 'helloyo' 0x03 , 而其第一行的值和記憶體空間依然不變。
---
## 不可變的特性常出現在內建函式:
var a = "Hello"
a = a.toUpperCase()
console.log(a)
因為第一行的 "Hello" 字串是賦值後不可變的,連帶影響呼叫內建函式時,不能只用 `a.toUpperCase()` 去改變第一行 a 的值,例如:
var a = "Hello"
// a = a.toUpperCase()
// console.log(a)
function change(str) {
str = "123"
}
change(a)
console.log(a)
會印出 Hello , 賦值後不可變的,無法改變原來的值,只能回傳一個新的值(新創造記憶體位置) 。
1. 因為 a = "Hello" 後賦值後不可變,不會改變 a 的值。
2. 不能改變新的值,因此會回傳一個新的值(新記憶體位置) 。
因此分解其程式過程大概如下:
var a = "Hello"
// 第一行大約是 a: "hello" 存到一個 stack 0x01 ;
a = a.toUpperCase()
// 呼叫 a.toUpperCase() 時,會先把 a: "hello" 回去的位置存到 stack ;
// 然後執行 a.toUpperCase() 時,等執行完會讀取回去的位置並存到 stack ;
// 大約是這樣指定: a: "HELLO" , 回傳一個新的字串;
console.log(a)
程式呼叫函式時,需要存放函式的數據時會用 Stack Memory Space , 完成後,會將其執行環境從呼叫過的 stack 移除,並再次回到一開始執行環境。
---
## 習慣是可以被改變的 ?
### 有種型態是可以被改變的,如 array 、 object... 等,例如:
var arr = [1, 2, 3]
arr.indexOf(2)
console.log(arr)
值不會改變,會印出: [ 1, 2, 3 ] 。
var arr = [1, 2, 3]
arr = arr.indexOf(2)
// 會回傳一個新的數字,不會動到原本的 array ;
console.log(arr)
值不會改變,會印出 1 。
---
### 值被改變的情況:
`.push()` :
var arr = [1, 2, 3]
arr.push(4)
console.log(arr)
值會改變,印出 [1, 2, 3, 4] , 分解狀況如下:
arr: [1, 2, 3, 4] 在原本 array 中裡面多了個 4 , 因為直接在記憶體內更動,所以會改變到原本的 array 。
而如果用 `arr = arr.push(4)` 會變成印出 4 ,表示 `.push` 多推了一個,長度為 4 。
`.reverse()` :
var arr = [1, 2, 3]
arr.reverse()
console.log(arr)
會排成 [3, 2, 1] , 會改變原本的 array , 並回傳一個新的 array 。
除了 `.reverse()` 、 `.push()` 會改動原本的 array 外,另外還有 `.shift() 、 .sort() 、 .splice() 、 .unshift()`... 等,會改動原本的,通常內建函式只有一行,不太會用變數去接收他,參考 [MDN: Array](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array) 。
### 而不太會改動到的情況,為通常回傳的東西不是一個 array :
`.join()` :
var arr = [1, 2, 3]
arr = arr.join()
console.log(arr)
改動的東西為字串,所以會選擇回傳新的字串; 如果操作上想改動 array 內的值,就不會特意去回傳值,而是直接更動。
### `.splice()` 和 `.slice()` 差異
如 `.splice()` 可以在 array 內改動 。
`.slice()` 而不會改動到原本的 array 會直接回傳新的 array 。
### 結論
1. 在對 array 或 object 用一些內建函式的時候,他可能會改動到原本的值或是 array 。
2. 如果呼叫的東西本來就不是 array 就需要宣告變數並賦予。
3. 而對字串或是數字,不會改變到原本的值,所以需要宣告並賦予,回傳新的值。
4. 如果不知道內建函式用法可以多查找文件,否則執行容易遇到一堆 bug 。
---
參考資料:
[Array 陣列](https://ithelp.ithome.com.tw/articles/10204695)
[JavaScript陣列與物件元素順序重新排序sort()、array 排序、object排序](https://www.ucamc.com/e-learning/javascript/271-javascript-sort-array-object)
[Immutability 為何重要?淺談 immutable.js 和 seamless-immutable](https://cythilya.github.io/2017/02/12/immutability-immutablejs-seamless-immutable/)
[Call Stack 呼叫堆疊](https://ithelp.ithome.com.tw/articles/10206190)
[stack vs heap:執行時期儲存兩大要角](http://antrash.pixnet.net/blog/post/70456505-stack-vs-heap)
[Stack 與 Heap 有何差別](https://medium.com/@yauhsienhuang/stack-acdcc11263a0)
---
# 二十二、 `console.log` 蠻重要
debug 蠻常用到 `console.log` 可以根據印出的東西來看有沒有問題。
例如質數,無法整除因數有 1 ,範例如下:
function isPrime(num) {
if (num === 1) return false // 如果傳下來的數字是 1 就不是質數;
if (num === 2) return true // 如果傳下來的數字是 2 就是質數;
for(var i = 2; i<num; i++) { // 從 2 跑到 num-1 ;
if (num % i === 0) { // 如果能被整除嚴格相等為 0 就不是質數;
return false
} else {
return true // 否則就是質數;
}
}
}
console.log(isPrime(15))
會印出 true , 但 15 不是質數,因此開始 debug :
function isPrime(num) {
console.log('num:', num)
if (num === 1) return false
if (num === 2) return true
for(var i = 2; i<num; i++) {
console.log('i:', i)
if (num % i === 0) {
console.log('num % i === 0', num, i)
// 記得在 return 前加 console.log 才會先執行,否則會優先執行 return 跳到最後一行 console.log ;
return false
} else {
console.log('else', num, i)
return true
}
}
}
console.log(isPrime(15))
會印出:

在 else 15 2 的地方會發現,應該還有其他整除方法來判斷 15 不是質數,因此:
function isPrime(num) {
console.log('num:', num)
if (num === 1) return false
if (num === 2) return true
for(var i = 2; i<num; i++) {
console.log('i:', i)
if (num % i === 0) {
console.log('num % i === 0', num, i)
// 記得在 return 前加 console.log 才會先執行,否則會優先執行 return 跳到最後一行 console.log ;
return false
}
}
return true
}
// ↑ return 放這裡代表說,迴圈跑完了,如果還沒 return 代表迴圈內沒有數字能整除他,迴圈內沒有因數;
console.log(isPrime(15))
最後 debug 完會印出:

碰到問題時可以在每一行用 `console.log` 覺得有問題的地方印出來,找出不一樣的地方。