zuppachu / Joanne-s-Learning-Blog

程式導師實驗計畫第二期 之 Coding 學習紀錄。
2 stars 0 forks source link

[ JS102 ] - ES6 + npm + jest 筆記 #3

Open zuppachu opened 6 years ago

zuppachu commented 6 years ago

ES6 語法

呼呼~ 好不容易進入到第二階段的 javaScript,看的速度跟不上筆記的速度!安捏姆後!趁記憶力還在時,趕快做筆記才是王道!

什麼是 作用域(scope)?

[MTR01] Lesson1-2 JS基礎 (52:30處)

作用域代表一個變數的生存範圍 = 超過這個生存範圍,此變數就不存在了。

作用域分為:

  1. 全域 (global scope)
  2. 函式域(function scope)
  3. 區塊域 (block scope): let and const 都是屬於此。

以下例子為 function scope 無法再 global scope 中顯示:

function test() {
    var a = 1;
}

console.log(a)  //undefined

//undefined 是因為 a 只能生存在 funciton test() 裡,而且此 log(a) 超出 function 的範圍,表示不存在。

變數在不同 scope 裡的生存範圍:

var a = 10;  //全域變數
function test() {
    var a = 1;  //函式域變數
    console.log(a)  //prints 1
}
test();
console.log(a)  //prints 10

宣告變數時一定要加 var,否則 JavaScript 會把此變數自動改成全域變數,容易產生 bug :

var a = 1;  //全域變數

function test() {
    a = 10;  //自動變成全域變數,重新把 a 設成 10
    console.log(a)  //prints 1
}
test();
console.log(a)  //prints 10

const、let 與 var

colors.push('red'); colors.push('blue');

colors = 'Green'; //typeError: Assignment to constant variable.

console.log(colors);

物件和陣列是存在記憶體位置,所以內容可變:
```js
const a = {
    number: 10
}
a.number = 20
console.log(a)
//得 {number:20}

letconst:區塊作用域 (block scope = 大括號 { }的區塊範圍內) var 是函式作用域 (function scope = 生存於一個 function 範圍內)

來看一些程式碼,比較容易理解~

例一:

var a = 'Test1'
let b = 'Test2'

console.log(a) // Test1
console.log(b) // Test2

例二:

function testVar(){
    var a = 30; //global scope level

    if(true){
        var a = 50;
        console.log(a); //50    
    }

    console.log(a); //50; 因為第一個50取代了30
}

testVar();

例三:

function testLet() {
    let a = 30;

    if(true) {
        let a = 50;
        console.log(a); //50
    }
    console.log(a); //30; 因為中間的 let變數 不影響前面的變數
}

testLet();

箭頭函式 Arrow function

以下四種寫法皆相同:

//ES5 語法
var add = function(a, b) {
    return a + b;
}

//ES6 語法,使用 arrow 取代 function
var add = (a, b) => {
    return a + b;
}

簡化版本:
var add = (a,b) => a + b

//ES6 語法,用 const 宣告
const add = (a, b) => {
    return a + b;
}

//ES6 語法,用 let 宣告
let add = (a, b) => {
    return a + b;
}
function test(n){
    return n
}

等同於
const test = function(n){ 
    return  n
}

等同於
const test = (n =>{   //單一參數時,箭頭函式可以省略一邊刮號
    return  n
}
ES5 寫法:
var arr = [1, 2, 3, 4, 5]

console.log(
    arr
        .filter(function(value) {
            return value > 1
        }
        .map(function(value) {
            return value * 2
        }
)

等同於

var arr = [1, 2, 3, 4, 5]

console.log(
    arr
        .filter(value => value > 1)   //箭頭函式
        .map(value => value * 2)
)

// 如果多行,箭頭函式大括號不能省略
console.log(
    arr
        .filter(value => {
            ...
            ...
            ...
            return ...
        })

Template string

解決字串相加太多時,造成可讀性不高的問題。

MTR01 Lesson 1-2 於 79:40 分處

//ES5 語法

function sayHi(name) {
console.log('hello, ' + name + 'now is' + new Date()) //太多行,容易有失誤 
}
//ES6 語法

function sayHi(name) {
console.log(`hello, ${name} now is ${newDate}`)
}

簡而言之,省去 +。讓字串用 (backtick) 包起來後,把變數放入$ {} (大括號) 裡面。

然後另外個好處是:可以換行 !不像 ES5 一換行等於失誤。

var str = `hello,
${name} now is ${myName}`

console.log(str)
// 得 
hello,

nick
I am Joanne

解構 Destructuring

直播 week2-2 ES6 於 30:14

將用陣列與物件展示如何解構:

var first = arr[0] var second = arr[1] var third = arr[2] var fourth = arr[3]

console.log(second, third) //得2,3

//ES6 語法 const arr = [1,2,3,4] var [first, second, third, fourth] = arr //對應的方式,配對! console.log(second, third) //得2,3


另外,其實程式不是很聰明,如果要取第三個值的話:
```js
var [, , third] = arr

就還是得用對應的方式(按照位置)讓程式理解你想要取的值。

array 參照 index

//ES5 語法 var name = obj.name var age = obj.age

console.log(age)

ES5 的解法是:
1. 宣告 name、age 為變數。
2. 把 name、age的初始值設為 `.obj key` ( `.obj.name` )裡面的初始值

而 ES6 的語法就快多惹~
```javescript
//ES6 語法 - 解構
var {name, age} = obj

console.log(age)

上面寫法也等同做了兩件事:

  1. 宣吿 name
  2. 宣告 name = obj.name

object 參考 key

//對應版 var { family: { father } console.log(father)


## Spread Operator 
展開運算值 `...XXX`

一、陣列

- 請注意上下範例的差別: 有 [ ] 無 [ ]
```js
var arr = [1, 2, 3]
var arr2 = [4, 5, 6, arr]

console.log(arr2)
//得 [ 4, 5, 6, [ 1, 2, 3 ] ]
var arr = [1, 2, 3]
var arr2 = [4, 5, 6, ...arr]

console.log(arr2)
//得 [ 4, 5, 6, 1, 2, 3 ]

console.log(arr2) //得 [ 4, 1, 2, 3, 5, 6 ]

- 以前兩個 array 相加時,用內建函式,有展開運算式後,可這樣用
```js
const arr = [1, 2, 3]
const arr2 = [4, 5, 6]

const arrConcat = [...arr, ...arr2]
console.log(arrConcat)
//得 [1, 2, 3, 4, 5, 6]

console.log(arr === arr2) // true 因為記憶體位置一樣

```js
var arr = [1,2,3]
var arr2 = [...arr]

console.log(arr === arr2) 
// false 因為記憶體位置不一樣

const arr = [1, 2, 3]

const arr2 = arr arr2[0] = 10

console.log(arr) //得 [10, 2, 3],因為他們是指到同個記憶體位置。

```js
//想要他的值,但不想要他的記憶體位置
const arr = [1, 2, 3]

const arr2 = [...arr]
arr2[0] = 10
console.log(arr)
//得  [1, 2, 3],因為只複製了值而已。

補充處

var arr = [1, 2, 3, 4, [6, 7], [8, 9]]
var arr2 = arr   // 變數宣告賦值整個陣列時,當變數變動時,會改到原陣列值
var arr3 = [...arr]   //第一層是複製一份值,且不會改到原陣列; 第二層是相連的陣列所以會改到

arr2[0] = 5
arr2[4] = 6
arr3[1] = 7    
arr3[5][0] = 0  

console.log(arr)
console.log(arr2)
console.log(arr3)
// [ 5, 2, 3, 4, 6, [ 0, 9 ] ]
// [ 5, 2, 3, 4, 6, [ 0, 9 ] ]
// [ 1, 7, 3, 4, [ 6, 7 ], [ 0, 9 ] ]

二、 物件

var arr = [1, 2, 3] console.log(add(...arr)) //得 6

- 深拷貝 vs 淺拷貝
```js
var obj1 = {
    a: 1,
    b: 2,
    c: {
        d:3,
        e:4
    }
}

var obj2 = obj1 //變數宣告賦值整個物件時,當變數變動時,會改到原物件值
var obj3 ={
    ...obj1  //第一層是複製一份值,所以不會改到原物件。第二層是相連的物件所以會改到
}

obj2.a = 0
obj2.c.d = 0
obj3.b = 0
obj3.c.e = 0

console.log(obj1)
console.log(obj2)
console.log(obj3)
// { a: 0, b: 2, c: { d: 0, e: 0 } }
// { a: 0, b: 2, c: { d: 0, e: 0 } }
// { a: 1, b: 0, c: { d: 0, e: 0 } }
const obj = {
    a: 1,
    b: 2,
    student: {
          name: 'hello'
    }
}

const obj2 = {
    ... obj 
}

// 上面複製的情況意思如下例:
const obj2 = {
   a: 1,  // 等於 a: obj.a
   b: 2, // 等於 b: obj.b
   student: obj.student  //值的記憶體位置
}

obj2.student.name = 'Ciao'
console.log(obj.student.name)
//得 Ciao,還是被改到了
var obj = {
    a: 1,
    b: 2
}

var obj2 = {
    ... obj
}

console.log(obj, obj2, obj === obj2)
//{ a: 1, b: 2 } { a: 1, b: 2 } false
var obj = {
    a: 1,
    b: 2
}

var obj2 = obj

console.log(obj, obj2, obj === obj2)
//{ a: 1, b: 2 } { a: 1, b: 2 } true 因為記憶體位置一樣

淺拷貝與深拷貝在直播 week2-2 ES6 48:11處 51:00 處解釋如何不要被改到值的方式 JSON (有聽沒懂?) 先做筆記再說好了

const obj_json = JSON.stringify(obj)
const obj2 = JSON.parse(obj_json)
obj2.student.name = 'ciao'

console.log(obj.student.name)
//得 hello
因為利用 JSON 方式不會改到原本的值。
原因是因為 obj2 已經變成一個全新的物件了。(而 function 無法這樣用)
這種叫做 深拷貝 (deep clone, deep copy)

反向展開:Rest Parameters

給我剩下的東西~ (...XXX)

console.log(add(1, 2)) // [ 1, 2 ] // 3

```js
function add(a, ...args){
    console.log(args)
    return a + args[0]
}

console.log(add(1, 2))
// [ 2 ]
// 3

參數預設值:Default Parameters

function repeat(str = "hello", times = 5) {
    return str.repeat(times)
}
console.log(repeat())
console.log(repeat("YO"))
console.log(repeat(,8))
console.log(repeat("YO", 3))
// hellohellohellohellohello
// YOYOYOYOYO
// 錯誤
// YOYOYO
const obj = {
    a: 1
}

const {a = 123, b = "hello"} = obj

console.log(a, b)
// 1 'hello'

Import 與 Export

ES5 寫法:

//在 utils.js

function add(a, b){
    return a + b
}
module.exports = add
//在 index.js

var ans = require('./utils')
console.log(add(3, 5))
//在 iTerm
node index.js
//得 8

ES6寫法:

//在 utils.js

export function add(a, b){   //在前面加上 export 就可以輸出了
    return a + b
}

export const PI = 3.14  //一個檔案可以 export 多個東西
//在 index.js

import {add, PI} from './utils'   
console.log(add(3, 5), PI)
//在 iTerm
npx babel-node index.js  
//目前 Node.js 版本還未支援 ES6 export import 寫法
//得8 3.14

或可以這樣寫

//在 utils.js

function add(a, b){ 
    return a + b
}
const PI = 3.14
export {    //不是物件
   add as addFunction,   //可以換名稱 => export 出去的名稱就會變成 add2
   PI
}
//在 index.js

import {addFunction, PI} from './utils' //換名稱這邊也要一起換
console.log(addFunction(3, 5), PI)
import {add as a, PI} from './utils'  //也可以在這改名。
console.log(a(3, 5), PI)

import * from './utils'   //用 * 可以將全部 export 的給 import 進來
console.log(add(3, 5), PI)
import * as a from './utils'   //也可以改名
console.log(a.add(3, 5), a.PI)  //這邊也要一起改變
export default
//在 utils.js

export default function add(a, b){  
    return a + b
}
export const PI = 3.14 
//在 index.js
import add, {PI} from './utils'   //加了預設就不用大括號
console.log(add(3, 5), PI)

Babel

A JavaScript package that transpiles JavaScript ES6+ code to ES5.

zuppachu commented 6 years ago

模組化與 Library

可以看 Peggy 的筆記 非常詳細 :)

Node.js 內建很多模組(Module),而模組化就是把功能分堆,以便之後好維護。 例如:把主程式裡面分為:登入功能 + 金流功能 + 會員功能 + 權限功能。 之後可能哪一部分壞了就修那一塊,不會干擾到其他功能。

模組化好處小結:

  1. 更好找尋、處理、debug 程式
  2. 可以將應用程式分為好幾個小塊,分別處理
  3. 可以將隱藏和保護資訊,與其他模組區隔
  4. 最重要的是,prevent pollution of the global namespace and potential naming collisions, by cautiously selecting variables and behavior we load into a program.
  5. 自己和別人都可以使用

借別人的東西來用:require

把別人寫好的功能,用 require 引入進來。

var os = require("os")
// "os" 這個被字串包起來的 os 是我要引入的名稱
// 引入進來後,變數(os)名稱可以改

console.log(os.platform())
// darwin => MAC OS 的核心名稱

把東西借給別人:export

(這單元講:如何自己做出一個 module 讓自己和別人使用?)

例一:

//在 myModule.js

function double(n) {
    return n * 2
}

module.exports = double 
//要輸出一個 module 給別人使用,用 module.exports = 接你想要輸出的東西
//module.exports 輸出什麼,之後再 index.js require 就會是接收什麼
//在index.js

var ABC = require("./myModule.js")  
// "檔案路徑" ,("./myModule.js") 可以不寫 .js,寫("./myModule")也可以
console.log(ABC(3))
//得6

例二

//在 myModule.js

module.exports = 123 
//用這個方式,想要另一邊輸出數字,就會輸出數字
//在index.js

var test = require("./myModule.js")
console.log(test)
//得123

例三

//在 myModule.js

module.exports = [1, 2, 3] //用這個方式,想要另一邊輸出陣列,就會輸出陣列
//在index.js

var KKK = require("./myModule.js")
console.log(KKK)
//[1, 2, 3]
//在 myModule.js

function double(n) {
    return n * 2
}
var obj = {
    double: double,
    triple: function(n) {
        return n * 3
    }
}
module.exports = obj  //變成物件的方式

或是這種寫法,但意思與上面一樣。

function double(n) {
    return n * 2
}
module.exports =  {
    double: double,
    triple: function(n) {
        return n * 3
    }
}
module.exports = obj 

或另一種方法:

function double(n) {
    return n * 2
}

//用此寫法 exports .XXX,XXX必得是物件(object)
exports.double = double   
exports.triple = function(n) {
    return n * 3
}
//在index.js

var ABC = require("./myModule.js") 
console.log(ABC.double(2), ABC.triple(10))
//得 4 30
zuppachu commented 6 years ago

Node Package Manager (NPM) :把你們的力量借給我吧!

NPM 是什麼?

當你寫好一個功能時,可以把它包裝成 package/ library/ module,上傳給全世界的人使用。 當然,我們也可以透過 NPM 幫忙管理這些套件們。可以想像成一個大型倉庫專門存套件的地方。

npm 環境設置 (npm install -- 以 left-pad 為例)

在 iTerm 寫下:

  1. npm init : 初始化。處理完會多一個 package.json 的檔案 (= 一個描述資料 json格式的檔案/A file that contains information about a JavaScript project.)

  2. npm install left-pad: 安裝 left-pad => 打 ls => 會發現在 mentor-program 裡面多一個 node _modules 資料夾。 (但這種方式不太好,因為當你下載太多個套件,上傳給別人時,會造成別人檔案太大的困擾,倒不如跟別人說:喔,我有用 left-pad,你可上自己抓!所以用下面的方法比較好。)

  3. npm install left-pad --save : 安裝 left pad 套件至 node_modules 資料夾中,並將資訊存在 package.json 裡的 dependencies 中。(從 npm 5 以後,--save 就已經變成預設的選項了,因此 npm install 不加 --save 也是可以的喔,一樣會把資訊寫到 package.json 裡面去!)

  4. npm install left-pad --save-dev: devDependencies 指開發時才會用到的 library,正式環境上不會用到的 library (與 --save 差別微乎其微。)

  5. npm -v :檢視有無裝成功,會顯示一串數字代表版本。

PS:切記要在上傳專案時,要把 node-module 用 .gitignore 排出在外,因為真的太大了,會造成對方困擾,對方需要時可以再用 npm install 這個指令會直接幫忙把需要的套件抓下來。其背後的原理是因為 npm 會自動檢查 package.json 檔案內的 Dependencies欄位。

在你要使用此 module 的 .js 檔案中

var abc = require("left-pad")    //使用left pad 這個 module 
console.log(abc(123,10,"0")
//得 0000000123

npm Scripts (npm 腳本)

當專案變大,有太多檔案,每個都看起來很像入口點/該執行的檔案時,有兩種方法:

  1. 在 package.json 檔內的 "main"區塊,先寫好該開啟的檔案。
  2. 在 package.json 檔案中 “scripts” 區塊
"scripts": {
     "start": "node index.js",  //在 iTerm 中用 nmp 執行 start 時,它就會幫你執行後面你寫好的檔案(npm run star)
     "yoyoyo": "echo 123"
}
//在 iTerm 中
打 npm run start          //就會執行 node index.js
打 npm run yoyoyo     // 結果就會是 123

yarn:npm 以外的另一種選擇

FB 開發的,大致相同於 npm

zuppachu commented 6 years ago

來幫你的程式寫測試吧!Unit Test 單元測試!

What's unit test?

單元測試,就是利用自動化方式測試程式內的小單元(如: function 、method、class 等)

Why?

就是為了減少 debug 時間呀~

怎麼測試正確與否?How?

A:

  1. console.log() + 自己想的範例(最好想些詭異的邊界條件(h case),例如空字串)
  2. console.log((a,3) === 'aaa')自己想可能的結果,如果出現 true 即代表正確!

由於點一與點二的缺點是很難規模化!故用別人已經寫好的測試框架,例如: Jest 框架。

Jest :利用 jest 來寫第一個測試!

首先,先裝 jest,有兩種方式:

  1. yarn add --dev jest
  2. npm install --save-dev jest

再來將 package.jason 裡的 "scripts": { "test": "..." } ,改為 { "test": "jest" }

寫測試檔 xxx.test.js(通常會將測試檔案命名為 xxx.test.js),如下: 在有 xxx.test.js 檔案的資料夾中,跑 npm run test。(nmp 不是 globel 的,他只會跑在某個專案底下的 test 檔)

npm run test :會跑全部命名為 test 的檔案 我用 npx jest hw1.test.js 跑個個小測驗 或是 npx jest test 也是跑全部有命名為 test 的檔案。

npx 是新一點的版本

例一:

// 在 hw1.js中

function stars(n) {
    var result = ["*"]
    for (var i=2; i<=n; i++){
        result.push("*".repeat(i))
    }
    return result
}

module.exports = stars;
在 hw1.test.js中

var stars = require('./hw1')

describe("hw1", function() {
  it("should return correct answer when n = 1", function() {
    expect(stars(1)).toEqual(['*']) 
    // .toEqual 也可改寫成 .toBe
  })
})

例二:

//在 index.js 中

function repeat(str, times) {
    var result = ""
    for (var i=0; i<times; i++){
        result += str
    }
    return result
}

module.exports = repeat;
//在 index.test.js中

var repeat = require('./index.js')

//可以寫成一個比較模組的感覺,如下:
describe("測試 repeat", function() {
    test("a 重複 5 次應該要等於 aaaaa", function() {
        expect(repeat("a", 5)).toBe('aaaaa')
    })

    test("abc 重複 1 次應該要等於 abc", function() {
        expect(repeat("abc", 1)).toBe('abc')
    })

    test(""" 重複 10 次應該要等於 """, function() {
        expect(repeat("", 10)).toBe("")
    })
})

先寫測試再寫程式: TDD (Test-driven Development) 測試驅動開發

先寫測試再寫程式方式:

  1. 把條件列得詳細點,盡可能包含普通、空字串、邊際條件等~
  2. 修改版本直到測試跑到對的為止。
zuppachu commented 5 years ago

淺深拷貝

參考: [Javascript] 關於 JS 中的淺拷貝和深拷貝 [筆記] 談談JavaScript中by reference和by value的重要觀念


深拷貝 = 全部東西都複製「值」而已 淺拷貝 = 第一層複製值,第二三四五六七八之後的層都是複製「記憶體位置」 展開運算子是淺拷貝,故只對第一層有用。