# 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`