# 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


#### 函式性概念:不變性、純度、資料轉換、高階函式、遞迴
- 不變性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`