# 二十、 常出錯的地方 ###### 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) 出來一個值: ![](https://i.imgur.com/EGfBJgo.png) --- 如果輸入 `console.log(a+3)` 會出現: ![](https://i.imgur.com/iZGJhmP.png) 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)) 會印出: ![](https://i.imgur.com/TNYo98y.png) 在 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 完會印出: ![](https://i.imgur.com/IWhpcYf.png) 碰到問題時可以在每一行用 `console.log` 覺得有問題的地方印出來,找出不一樣的地方。