---
# System prepended metadata

title: Javascript
tags: [js]

---

# Javascript


### module
- JS
```javascript=
// 1. 只可別稱 要{}
export const number = 123;
import { number as num } from './file.js';

// 2. 可自命名 不用{}
const count = (x) => x.length;
export default count;
import countArray from './file.js';

// 3. 一次import 全部
import * as all from './file.js';
all.count();
all.numbber;
```
- Node.js
CommonJS
```javascript=
const number = 123;
const count = (x) => x.length;
module.exports = {numbber, count};
const { log, print } = require('./file.js'); // import
```








### function
> JS30 day:
https://ithelp.ithome.com.tw/articles/10192739
book: react 學習手冊

- 宣告
因為hoisting（函式提升） 所以在宣告前或後呼叫都可以
```javascript=
function square(num) {
    return num*num;
}
```

- 匿名函式
因為是宣告function變數（變數提升） 
所以hoisting 只會提升var squa 
因此要呼叫squa(4)的話 只能在**宣告以後**
```javascript=
var squa = function (num) {   
    return num;
};

// 匿名函式也可以加上名字
// 但只有在函式內才能呼叫
var squa = function s(num) {
    console.log(typeof s);
    return num*num;
};


使用
console.log(square(4));    //16
console.log(squa(4));    //16
```

- arrow function不會遮擋this範圍

https://www.fooish.com/javascript/ES6/arrow-functions.html
**傳統只會綁定在各自function中**
所以當要取Person這個class內的age屬性時，
**要定義一個self**把this固定為Person的this，
不然直接在growUp內寫this就會取到function內的this，
而此this並沒有age，所以就會跑error。

```javascript=
function Person() {
    // 先將正確的 this reference 存到一個變數中
    var self = this;
    self.age = 0;

    setInterval(function growUp() {
        // self 變數會正確的指向 Person 物件
        self.age++;
    }, 1000);
}
```

**arrow function則會自動綁在宣告時的環境(父層)**
因為arrow function不會遮擋this範圍
所以setInterval裡的this.age就會是person的age。

且如果把function Person也改成arrow func 那this就會===window
也就出現error
```javascript=
function Person() {
    this.age = 0;

    // 定義該 Arrow Functions 時的環境，是在 Person 物件中
    setInterval(() => {
        // 所以 this 會正確指向 Person 物件
        this.age++;
    }, 1000);
}

var p = new Person();
```

- 更多範例：.map
```javascript=
var multiply = (a, b) => a * b;
multiply(2, 10)    // 20

---

let numbers = [1, 2, 3];
let doubles = numbers.map(num => {
    return num * 2;
});
console.log(doubles);    // [2, 4, 6]
```

- 可直接預設參數
```javascript=
function multiply(a, b = 1) {
  return a * b;
}
```

- 不定長度的參數
...myArgs 只能放在最後一個參數，
myArgs 的值是一個陣列 (array)。
```javascript=
function fn(a, b, ...myArgs) {
    // ...
}
```


- 能對變數做甚麼操作，就能對function做甚麼操作
https://ithelp.ithome.com.tw/articles/10192368
- 傳入多值&遞迴呼叫
取值: `arguments[i]`
遞迴呼叫: `arguments.callee(參數1, 參數2)`<br>
`arguments`雖然看起來像個「陣列」，
但實際上他只是個帶有「索引」特性的物件，然後內建個 length 屬性，
其他地方**與「陣列」完全不同**。<br>
但是!
在「**嚴格模式**」下**不**允許存取 arguments.caller 和 arguments.callee 這兩個屬性。
另外，ES6 的箭頭函式 (Arrow Function) 也沒有提供 arguments 物件。

```javascript=
var plus = function(a, b) {
  
  //就算沒有對應的變數，也可以用arguments取得值
  console.log("length: ", arguments.length);
  
  for (var i of arguments){
    console.log(i); //arguments[0], [1], ...  
  }
  
  if (arguments.length>3) {
    arguments.callee(2, 3); //同plus(1, 2)的功用(也可以直接寫這個)
  }
  
  return a+b;
}

plus(3,4,3,6);

// 會輸出：
// length: 4
// 3 4 3 6
// length: 2
// 2 3
```

- 如果參數數量容易變動，可用傳入物件

```javascript=
var people = {
  firstName: 'Kuro',
  lastName: 'Hsu',
  phone: '0987654321',
  email: 'kurotanshi@gmail.com',
  gender: 'male',
  address: 'Taipei City'
};

// 最後把 people 物件作為參數傳入 addPerson
addPerson(people);
```

- 檢查傳入的參數是否有效，若少傳一個會跑出NaN

```javascript=
var plus = function(a, b) {
    a = (typeof a === 'undefined') ? 0:a;
    b = (typeof b === 'undefined') ? 0:b;
    
    return a+b;
}

// ES6寫法

var plus = function (numA = 0, numB = 0) {
  return numA + numB;
};
```


### 物件

- 真 Class
https://www.fooish.com/javascript/ES6/class.html
原JS沒有正式的類別
後來ES6引進了類別宣告
```javascript=
class Lion extends Cat {
    constructor(name, attack) {
        super(name);
        this.attack = attack;
    }
    speak() {
        super.speak();
        console.log(this.name + ' roars.');
    }
}
```

- 繼承

`Object.setPrototypeOf(objB,objA);`
第一個參數子物件，第二個參數為父物件(原型物件)

`super.xxx`
可呼叫父層
```javascript=
// 也可寫
// function Parent(a, b) {
//     this.a = a;
//     this.b = b;
// }
var Parent = {
    // print方法的兩種寫法
    // (下面這個要在 ‘物件外’寫)
    // Parent.prototype.print = funcion() {
    // console.log('Hello from the Parent');
    // }
    print() {
        console.log('Hello from the Parent');
    },
    a: 'a of parent'
}

var Child = {
    print() {
        console.log('Hello from the child');
        super.print();
        
        console.log(this.a);
        console.log(super.a)
    },
	a: 'a of child'
}

Object.setPrototypeOf(child, parent);


child.print();
```

- 標籤內可自訂屬性
`const element = <div myattribute="somevalue" />;`

- 物件中**變數**名稱 = **屬性**名稱的話 可省略

```javascript=
var foo = 0;
var bar = 1;

var obj = {
    foo: foo,
    bar: bar
};

// 等同於

var obj = {
    foo,
    bar
};
```
方法名 = 屬性名，亦同
```javascript=
var obj = {
    // 可以省略 function 關鍵字和冒號 (colon)
    doSomething: function() {
        // ...
    }
};

// 等同於

var obj = {
    // 可以省略 function 關鍵字和冒號 (colon)
    doSomething() {
        // ...
    }
};
```


- 物件內 屬性名/方法名 可**計算而成**

```javascript=
var prefix = 'es6';

var obj = {
    // 計算屬性
    [prefix + ' is']: 'cool',
    
    // 計算方法
    [prefix + ' score']() {
        console.log(100);
    }
};
 
// 顯示 cool
console.log(obj['es6 is']);

// 顯示 100
obj['es6 score']();
```


### Promise

非同步 (asynchronous) 編程的處理方案

簡單來說它是一個 等待非同步操作完成 的物件，
當事件**完成**時，Promise 根據操作結果是成功、或者失敗，**做相對應的處理**動作。


一個 Promise 物件 (只) 會處於下面三種**狀態**之一：
* pending - 初始狀態 (進行中)
* fulfilled - 事件已完成
* rejected - 事件已失敗

狀態的改變只有兩種可能：
1. 從 pending 變成 fulfilled
1. 從 pending 變成 rejected



```javascript
//宣告時可傳入一個function當作constructor
//如果constructor發生錯誤 throw error，Promise 物件的狀態會自動變成 rejected
var promise = new Promise(function(resolve, reject) {

  // ...
  // resolve/reject 這兩個函數會由 JavaScript interpreter 自動傳入
  if (異步操作成功) {
    resolve(value);
  } else {
    reject(error);
  }

});


//這邊是寫剛剛resolve/reject內部的操作
//語法 Promise.prototype.then(onFulfilled, onRejected)
promise.then(function(value) {
  // 當狀態是 fulfilled (成功) 時，執行這個函數
  // value 是透過 resolve() 傳進來的參數

}, function(error) {
  // 當狀態是 rejected (失敗) 時，執行這個函數
  // error 是透過 reject() 傳進來的參數

});


//捕捉丟出來的error 很像 then(不定義, onRejected)
//語法 Promise.prototype.catch(onRejected)
var promise = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

promise.catch(function(e) {
  console.log(e);
});

```
* resolve(value)
函數的用途是用來將 Promise 物件的**狀態變為 fulfilled** (已完成)，
在非同步操作**成功時**調用，你可以將非同步操作的**結果當作參數**一起傳入
* reject(error) -> 不一定需要寫
函數的用途是用來將 Promise 物件的**狀態變為 rejected** (已失敗)，
在非同步操作**失敗時**調用，你可以將非同步操作的**錯誤當作參數**一起傳入

#### Promise.resolve(value)
把傳入值轉成Promise，並直接並直接resolve他

#### Promise.reject(error)
把傳入值轉成Promise，並直接並直接reject他

```javascript
Promise.reject(new Error('Fail')).then(function(error) {
  console.log('Success');
}, function(error) {
  console.log('Fail');
});

// 輸出 "Fail"
```


#### chaining
then() 和 catch() 方法執行後都會返回一個新的 Promise 物件，
讓你可以使用 chaining 的語法。
後面的then()會接收 前一個then()的return value 當作參數。
**(如果 return value 的型態不是 Promise，會先執行 Promise.resolve())**
```javascript
var hello = new Promise(function(resolve, reject) {
  resolve('Hello');
});

hello.then(function(str) {
  return str + ' World';

}).then(function(str) {
  return str;

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

// 最後輸出 "Hello World"
```


#### Promise.all(iterable)
裡面可裝多個Promise組成的array

* 狀態變為 fulfilled: 所有都是fullfilled才符合，會回傳陣列內所有Promise的返回值(陣列型態)。
* 狀態變為 rejected: 只要有一個Promise是rejected，那就會回傳第一個rejected的值

```javascript
var p1 = Promise.resolve(3);

var p2 = 1337;

var p3 = new Promise(function(resolve, reject) {
  setTimeout(resolve, 100, 'foo');
}); 

Promise.all([p1, p2, p3]).then(function(values) { 
  console.log(values);
});

// 會顯示 [3, 1337, "foo"]
```

#### Promise.race(iterable)
概念跟.all一樣，
只是狀態的改變會依照陣列中某個Promise先變，
.race就會變成那種狀態。

https://www.fooish.com/javascript/ES6/Promise.html







<style>
h4 {
    color: #B38939;
}
li:first-line {
    color: #638939;
}
li li:first-line {
    color: #736912;
}
.l {
    color: #736912;
}
h3 {
    color: #B3A939;
}
</style>


## 概念

### JS函式性程式設計
#### 函式性 Functional Programming
變數能做到的事 **函式都能做到**
例如：
- 放func進 obj, array
- 把func當做另一func的 參數或回傳值
e.g.
```javascript=
var createStream = function(log) { // 為高階函式 表把func當作參數 或回傳值
    return function(message) {
        log(message.toUpperCase(), '!!!');
    }
}
// 呼叫
const stream = createStream(message => console.log(message));
stream('gogo'); // GOGO !!!

// 等同於

var createStream = log = message => {
    log(message.toUpperCase(), '!!!');
}
// 呼叫方式相同
```

#### 宣告式Declarative v.s. 命令式Imperative 程式設計
函式性就是**宣告式**程式設計的一部分

- 命令式
重於 定義**如何達成**
code內需要很多註解才會讓人明白要達成什麼
```javascript=
var str1 = 'I am Dena'
var urlFriendly = ''

for (var i=0; i<str1.length; i++) {
    if (str1[i] === ' ') {
        urlFriendly += '-'
    } else {
        urlFriendly += str1[i]
    }
}
console.log(urlFriendly)
```

- 宣告式
重於 定義**要達成什麼**
很容易理解
因為code本身（變數、函式的命名）就會告訴你要做什麼事
所以也就不用很多註解
而如何發生的細節會被分離至別的檔案

```javascript=
var str1 = 'I am Dena'
var urlFriendly = str1.replace(\ \g, '-')
console.log(urlFriendly)

// 重點不是用了 內建函式
// 而是把詳細做法用.replace拆了出去
// 讓別人一看到replace就知道 此code在做什麼
```

- 將此概念套用在React上
> React使用手冊 P33

![](https://i.imgur.com/PhbZpjW.jpg)
![](https://i.imgur.com/k0LML5R.jpg)

#### 函式性概念：不變性、純度、資料轉換、高階函式、遞迴
- 不變性Immutability
資料不可修改
若要修改
需複製一副本後 修改副本

```javascript=
const lawnColor = {
    title: 'lawn',
    color: 'green',
    rating: 0
}
var colorRating = function(color, rating) {
    return Object.assign({}, color, { rating });
}
// 等同於
// var colorRating = (color, rating) =>
// ({
//     ...color,
//     rating
// })
console.log(colorRating(lawnColor, 5).rating); // 5
console.log(lawnColor.rating); // 0
```
總結：必須使用`Object.assign()`複制lawnColor的副本，再去修改rating
補充：若要複製array，除了用ES6展開運算子(`...`)外，可使用`arrary1.concat({ color })`


- 純函式Pure funciton
至少有一個傳入參數
與回傳 值/函數
且不能影響到 外界的資料(全域變數 或 應用程式狀態)
```javascript=
const dena = {
    name: 'Dena',
    canRead: true,
    canWrite: false
}
const selfEducate = person => // 有一參數
({ // 有回傳值
   ...person,
   canRead: true,
   canWrite: true
});

console.log(selfEducate(dena).canWrite); // true
console.log(dena.canWrite); // false 不會影響到全域變數
```

- Data Transformations
因為Pure function的機制
所以我們不能更動原始資料
而要改變資料時我們就用**複製一份**的方式 去達成
此時 會需要用到Array.map和Array.reduce

`Array.filter`
```javascript=
// 示範 用JS built-in的函式 複製出一個改動過的資料wschool
let schools = [
  "abc",
  "wdd",
  "wba"
];

// s[0] 表字串的第一個字
// Array.filter(述詞) 
// 述詞就類似判斷式 所以.filter回傳Boolean
let wschools = schools.filter(school => s[0] === "w");
console.log(wschools);

// 結論就是
// 如果我們在array要移除一些東西
// 那我們用.filter會比.pop或.splice好
// 因為.filter是immutable
```

`Array.map`
```javascript=
// Array.map(函式)

const highSchools = schools.map(school => `${school} High School`);

console.log(highSchools.join("\n"));
// abc High School
// wdd High School ...

// 而原school不變
```






###### tags: `js`