pychiang / Learning-Notes

My notes on learning programming languages
4 stars 0 forks source link

[第一週] Lesson 1-2 之 JavaScript 基礎影片筆記 #5

Open pychiang opened 6 years ago

pychiang commented 6 years ago

剛好看這個教學影片之前在 Udacity Intro to JavaScript 上重新複習 JavaScript,又看了胡立老師的影片後,有種豁然開朗的感覺啊~不過作業依舊寫得很慢 :sob:

兩個小時的教學影片含有大量的資訊量,所以筆記很~~長,寫得頭昏 :joy:

物件 (object)

物件為一種資料型態,用大括號 {} 包起來。在物件裡面可以包含屬性 (property) 或是函式 (method),彼此之間用逗號相隔。以下為一個基本物件形式:

var brother = {
  name: "Dennis",  //key: value =>a property
  age: 28,
  parents: ["Williams", "Susan"],
  paintPicture: function() {return "Dennis paints!"},  //method
  pets: true
}

可以看見物件的值 (value) 可以是各種型態,字串、數字、陣列、函式、布林,甚至是另一個物件都可以。

想要取得物件裡面的值有兩種型式:

如果 key 有兩個單字以上組成,則不能使用空白或是橫線 - 來連接兩個單字,同樣在使用 dot notation 的時候會造成讀不到資料,如果有多個單字組成的 key,則要用 camelCase 來命名。camelCase 指的是第一個單字開頭小寫,第二個單字以後則開頭大寫,就像駱駝的駝峰一樣,例如:friendsName、homePhoneNumber,或是用底線 _ 來連接單字,例如 home_phone_number。

JSON (JavaScript Object Notation)

JSON 是一種資料格式,常見於 API(Application Programming Interface,應用程式介面)中,有利於前、後端彼此之間交換資料。其中要注意的是,JSON 檔案中的 key 必須用雙引號包起來,物件裡則不用。專案中的 package.json 就是典型的 JSON 檔案:

{
  "name": "mentor-program-hw",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "babel-core": "^6.26.0",
    "babel-jest": "^22.0.6",
    "babel-preset-es2015": "^6.24.1",
    "jest": "^22.0.6",
    "regenerator-runtime": "^0.11.1"
  }
}

兩個等號 vs 三個等號

1 == "1"  //true
1 === "1"  //false

雖然在 JavaScript 裡宣告變數的時候不用指定資料型態,但是 JavaScript 的底層還是有型態之分的。只有三個等號時才會嚴格的比較兩者間的數值與型態,所以胡立老師說,忘記兩個等號吧~

如果要轉換資料型態的話,就要用其他的方法,例如:

var num = 10;
num = num + "";
console.log(num === "10")  //prints true, 把數字變字串

或是利用函式:

var num = 10;
num = num.toString();  //use toString() method
console.log(num === "10")  //prints true, 把數字變字串

等號背後的意義

在 JavaScript 裡,有三種資料被存在記憶體位置而不能用等號來比較是否相等:

  1. 物件 (object)
  2. 陣列 (array)
  3. 函式 (function)

所以可以看到下面的例子,陣列跟物件不相等:

1 === 1  //true
"hello" === "hello"  //true
[1] === [1]  //false
{a: 1} === {a: 1}  //false

雖然我們眼睛看到的陣列跟物件長得一模一樣,但是在 JavaScript 底層,其實是把它們兩個分別存放在不同的記憶體位置,所以兩者相比時就不相等。如果要比較物件、陣列、或函式的時候,則要用手動比較。

物件的例子:

var a = {name: "yo"}
var b = a; //讓 b 跟 a 對應到同一個物件(同一個記憶體位置)
b.name = "hi";  //改 name 為 "hi"
console.log(a.name)  //prints "hi"
console.log(a === b)  //prints true

因為物件存在記憶體位置,所以當設 var b = a 時,b 跟 a 對應到同一個記憶體位置,下一行 b.name = "hi" 時,把記憶體原本儲存 {name: "yo"} 改成儲存 {name: "hi"},所以 a 的數值也跟著改變。可以看到最後 console.log(a == b) //prints true,照理來說物件無法用等號比較,但因為 a 跟 b 對應到的記憶體位置變成相同的了,所以用等號相比也能相等。

數字的例子:

var a = 10;
var b = a;  //b === a === 10
b = 30;  //b === 30
console.log(a) //prints 10

因為數字不是存在記憶體位置,所以當設 var b = a 時,b 對應到 a 原本的數字 10,下一行 b = 30 只有改變 b,而 a 並未改變。

模組 (module)

當專案越來越大時,就會重複用到同樣的物件或函式,與其浪費時間在每個檔案內寫一模一樣的東西,倒不如把重複的東西另外寫在一個檔案,然後讓其他檔案共享資源,這個時候就需要用到模組。

模組基本上就是輸出輸入的概念,利用 Node.js 內建的 module.exports 輸出,然後 require 輸入,下面是一個簡單的範例。

utils.js 檔案內:

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

module.exports = add;  //輸出函式 add

index.js 檔案內:

var add = require('./utils.js')

其中 ./ 表示 utils.jsindex.js 在同一個資料夾底下。

除了可以輸出一個函式,也可以把多個函式用物件包起來一起輸出。 在 utils.js 檔案內:

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

function log(str) {
    console.log(str)
}

module.exports = {  //輸出物件
    add: add,
    log: log
}

index.js 檔案內:

var obj = require('./utils.js')

console.log(obj.add(20, 30))  //prints 50
obj.log(obj.add(20, 30))  //prints 50

因為在 utils.js 檔案內有寫 log 的函式,所以可以取代 console.log

課程裡的測試檔案則是用 exportsimport,這兩個是 ES6 的語法,在下面會有解釋。 其中胡立老師寫 test case 的時候用到兩種不同的等於函式 toBe()toEqual(),這兩者的差別在於 toBe() 就等於是兩個等號 ==,而 toEqual() 則可以檢查陣列或物件是否相等。

作用域 (scope)

作用域 (scope) 就是變數的生存範圍,分為全域 (global scope) 跟函式域 (function scope)。

下面的例子可以看到在 function scope 的變數無法在 global scope 中呼叫到:

function test() {
    var a = 1;
}

console.log(a)  //undefined,因為 a 只能生存在 funciton test() 裡

另外一個例子可以更清楚看出在不同 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 = 10;  //全域變數
function test() {
    a = 1;  //自動變成全域變數,重新把 a 設成 1
    console.log(a)  //prints 1
}
test();
console.log(a)  //prints 1

變數也可以拿來存函式:

var add = function(a, b) {
    return a + b;
}

所以函式可以被當成參數傳入另外一個函式裡。 被當成參數的函式有時會以匿名函式 (anonymous function) 的型式出現,之所以稱作匿名函式是因為它沒有名稱。

function exec(fn) {
    fn(); 
}

exec(function() {  //匿名函式 function()
    console.log(123)
})

其實上面例子的後半部函式跟下面的例子是表示一樣的東西:

var log123 = function() {
    console.log(123)
}
exec(log123);

ES6 語法

上次上新手到中手前端加強班的時候,那時候很怕 ES6 語法,每次看胡立老師教學的時候就好像在看天書一樣,看了幾個解說 ES6 的文章也是看不太懂,這次胡立老師講解之後卻變得容易許多,可能之前有看過比較不陌生了,不過在實際運用上還要多練習。

1. 宣告變數的方式 constlet 在 ES6 裡多加了 constlet 這兩個宣告變數的方法。其中的差別在:

這裡沒有任何 function,所以 var test 不受 if 的大括號範圍侷限:

var score = 60;
if (score >= 60 ) {
    var test = "Pass";
}
console.log(test)  //prints "Pass"

如果改成 let 就只生存在 if 的區塊內:

var score = 60;
if (score >= 60 ) {
    let test = "Pass";
}
console.log(test)  //ReferenceError: test is not defined

所以 let 的生存範圍可以是 function、if 判斷、或 for 迴圈。

2. Arrow function Arrow function 跟原本的寫法有細微的差距,但是現在會把事情變得太復雜,所以之後胡立老師才講解。下面四種寫法都是一樣的:

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

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

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

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

3. importexport Node.js (8.9.4) 還沒支援 importexport,所以要安裝 Babel。關於 Node.js 跟 Babel 是什麼可以參考我以前寫的這一篇筆記,雖然裡面是在講 Webpack,但是有不少東西都是一樣的。

所以先安裝 Babel CLI 跟 preset:

npm install --save-dev babel-cli babel-preset-env

然後創一個 .babelrc 的檔案,然後在裡面放進:

{
  "presets": ["env"]
}

要執行的時候輸入下面指令,就可以執行較新的語法:

.\node_modules\.bin\babel-node

環境設定好之後就可以來講 importexport 了。前面有提到 module.importsrequire,其實兩個的概念是一樣的。 在 utils.js 檔案裡,如果只輸出一個函式:

const add = (a, b) => {
    return a + b;
}

//ES5 的語法
module.exports add;

//ES6 的語法
export default = add;

index.js 檔案裡可以輸入剛才的函式:

//ES5 的語法
var add = require('./utils.js')

//ES6 的語法
import add from './utils.js'

utils.js 檔案裡,如果輸出多個函式:

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

var log = function(str) {
    console.log(str)
}

module.exports = {  //輸出物件
    add: add,
    log: log
}

//ES6 的語法
export const add = (a, b) => {
    return a + b;
}

export const log = (str) => {
    console.log(str)
}

index.js 檔案裡可以輸入剛才的兩個函式:

//ES5 的語法
var utils = require('./utils.js')
utils.add
utils.log

//ES6 的語法
import {add, log} from './utils.js'

4. Template string 有時候需要用到字串相加,但當字串很長又有變數跟其他符號在裡面的時候,可讀性就會降低,而 template string 就是用來改善這個問題的。

var name = "Nike";
var myName = "Huli";

//ES5 的語法
var str = "Hello, " + name  + "!" + "I am " + myName;

//ES6 的語法
var str = `Hello, ${name}"!" I am ${myName}`

所以把要相加的字串跟變數放在 ` 符號 (backtick) 裡面(鍵盤左上角跟波浪號 ~ 一起的那個符號),然後把變數用 ${} 包起來就可以了,省去加號 +。最厲害的是還可以直接換行,省去使用 \n,真是太方便的方法了啊!

5. Destructing 跟 spread opeartor 在一個物件裡,要把數值宣告成額外的變數的話可以用 destructing 寫法:

const obj = {
    score: 60,
    name: "Peggy"
}

//ES5 的語法
var score = obj.score
var name = obj.name

//ES6 的語法,destructing 的用法
const {score, name} = obj

console.log(score) //prints 60

而 spread operator 就是用 ...rest 去省略一個物件或陣列裡面的元素寫法,例如在剛才的物件裡多加兩個元素:

const obj = {
    score: 60,
    name: "Peggy",
    location: "USA",
    city: "Taichung"
}

const {score, name, ...rest} = obj

console.log(rest)  //prints {location: "USA", city: "Taichung"}

在陣列中也可以用 ...rest 還可以搭配 first

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

var arr = [first, ...rest]  //用 first 取代 1,...rest 取代 2, 3, 4, 5

console.log(first)  //prints 1
console.log(rest)  //prints [2, 3, 4, 5]

數學類常用函式

console.log(parseInt(a) === 50) //prints true

可以傳入第二個參數,指定要以哪一個進位為底
```javascript
var a = "50";

console.log(parseInt(a, 10))  //prints 50,以 10 進位為底

字串類常用函式

console.log(arr.map(twice)) //prints [2, 4, 6, 8, 10]

可以用匿名函式的寫法簡化:
```javascript
console.log(arr.map(function (n) {
    return n * 2;
}))

可以用 ES6 語法的 arrow function 簡化:

console.log(arr.map((n) => {
    return n * 2;
}))

在 ES6 語法裡,函式只有一行的話不用使用大括號 {},而且 return 也可以省略:

console.log(arr.map((n) => n * 2))

在 ES6 語法裡,函式只有一個變數也可以省略旁邊的小括號 (),所以最後可以省略成這樣:

console.log(arr.map(n => n * 2))

而且可以一直連續回傳,簡化後的寫法可讀性變得高許多:

console.log(
    arr.map(n => n * 2)
        .map(n => n +1)
        .map(n => n * n)
)

痛苦會過去,筆記會留下,終於寫完了 :joy: :tada: :tada: :tada:

aszx87410 commented 6 years ago

其中要注意在幫 key 命名的時候,不能使用數字做為第一個字母,雖然使用 bracket notation 沒有差別,但是在使用 dot notation 的時候會造成讀不到資料。 不能使用 => 盡量不要使用,畢竟你後面都說可以用 bracket notationa 了,哪來的「不能」XD

如果有多個單字組成的 key,則要用 camelCase 來命名 也可以用 under score 喔,例如說 home_phone_number