Hero Image
[Code] 格式化民國年

這個方法用 proxy 擴充 dayjs,讓 dayjs 支援格式化民國年。 JS 程式碼 // dayx.js import day from "dayjs"; const prototype = Object.getPrototypeOf(day()); const yyy = (dt) => (dt.getFullYear() - 1911).toString().padStart(3, "0"); const handler = { get: function (target, prop, receiver) { // age, dte, tme if (prop === "age") return day().diff(receiver, "years", false); if (prop === "dte") return receiver.format("YYYMMDD"); if (prop === "tme") return receiver.format("HHmmss"); // format if (prop === "format") return (format) => { const formattingTokens = /Y{4,}|Y{3}|[^Y{3}]+/g; const arr = format.match(formattingTokens); const format2 = arr .map((x) => (x === "YYY" ? yyy(target.$d) : x)) .join(""); console.log("ktformat", format2); return new day(target.$d).format(format2); }; // Return the original property value if (typeof target[prop] !== "function") return target[prop]; // Wrap the original function with custom behavior return function (...args) { const result = target[prop].apply(target, args); const isReturnDayjs = Object.getPrototypeOf(result) === prototype; return isReturnDayjs ? p(result, handler) : result; }; }, set(obj, prop, v, receiver) { if (prop === "dte") { v = v.padStart(7, "0"); const yyy = parseInt(v.substring(0, 3)) + 1911; obj.$d.setFullYear(yyy); obj.$d.setMonth(parseInt(v.substring(3, 5)) - 1); obj.$d.setDate(parseInt(v.substring(5, 7))); return receiver; } if (prop === "tme") { v = v.padEnd(6, "0"); obj.$d.setHours(parseInt(v.substring(0, 2))); obj.$d.setMinutes(parseInt(v.substring(2, 4))); obj.$d.setSeconds(parseInt(v.substring(4, 6))); return receiver; } return Reflect.set(...arguments); }, }; const p = (...args) => new Proxy(day(...args), handler); export default p; 型別定義 // dayx.d.ts import dayjs from "dayjs"; declare module "dayjs" { interface Dayjs { age: number; dte: string; tme: string; } } export = dayjs; export as namespace dayjs; 使用方式 可直接設定民國年,此外時間的加減乘除都可以參照 dayjs 文件

Hero Image
[Tools] 10分鐘建造 proxy 克服跨網域資源存取(CORS)問題

前後端分離的開發環境以 Ajax 呼叫資源時時會遇到跨網域存取的問題, 一些比較全面的開發環境像是 webpack、vue-cli 等等通常提供內建開發代理伺服器可供設置, 如果要對於不熟悉的開發環境進行快速除錯 (例如 vue 開發者臨時檢查其他框架的程式碼遇到跨網域問題), 重新研究如何設置開發環境跨網域代理伺服器往往就花費多餘的時間 (不過最終還是要搭建起來阿,喂~~), 因此紀錄一下怎麼用 node.js 建立一個通用的代理伺服器處理跨網域問題,整個過程不超過5分鐘。 步驟 首先安裝 node.js 建立專案資料夾 建立一個資料夾叫做 proxy 存放這個專案吧,手動建立也可以。 mkdir proxy 起始專案 用指令移動到該專案資料夾下,起始專案: cd proxy npm init 安裝相依性 npm i express http-proxy-middleware cors 建立 app.js const express = require('express'); const cors = require('cors'); const { createProxyMiddleware } = require('http-proxy-middleware'); // 建立一個 Express 伺服器 const app = express(); app.use(cors()) // 設定 Express 伺服器的 Host、Port const PORT = 3000; const HOST = "localhost"; // 設定代理到 google 的 Proxy 端點 app.use('/google', createProxyMiddleware({ target: "https://www.google.com/", changeOrigin: true, pathRewrite: { [`^/google`]: '', }, })); // 設定代理到 yahoo 的 Proxy 端點 app.use('/yahoo', createProxyMiddleware({ target: "https://tw.yahoo.com/", changeOrigin: true, pathRewrite: { [`^/yahoo`]: '', }, })); // 啟動代理伺服器 app.listen(PORT, HOST, () => { console.log(`Starting Proxy at ${HOST}:${PORT}`); }); 啟動代理伺服器 $ node app.js [HPM] Proxy created: / -> https://www.google.com/ [HPM] Proxy rewrite rule created: "^/google" ~> "" [HPM] Proxy created: / -> https://tw.yahoo.com/ [HPM] Proxy rewrite rule created: "^/yahoo" ~> "" Starting Proxy at localhost:3000 測試 打開隨意網頁 F12,用 fetch api 透過 proxy 對 google 或 yahoo 發起 get 請求成功獲得資訊,且 Header 裡面會有Access-Control-Allow-Origin: *:

Hero Image
[JS] 提升(Hoisting)與暫時性死區(Temporal Dead Zone)

網路上時常見到充滿 function 與 var 的 JavaScript 求值題目, 筆者在釐清 Hoisting 和 TDZ 的觀念前時常覺得答案出乎意料, 雖然現在撰寫程式碼都已經避免使用 var,但是維護 legacy code 還是會用到,因此在這裡做個筆記。 Hoisting 對學過伺服器端語言(C#、C/C++…)的人來說,預期試圖對未宣告的變數取值會出現 ReferenceError是很正常的事, 在 JavaScript 中也是如此: console.log(x) // ReferenceError: x is not defined 但 JavaScript 把 var 宣告變數放在後面,x 前面對 x 取值就變成 undefine, 在 JavaScript 當中對變數取值獲得 undefine 代表變數處於宣告後已分配記憶體空間(初始化、initiation)但尚未賦值的狀態, 但是明明 JavaScript 在宣告前就取值,怎麼能夠認得 x、而且知道 x 被初始化為 undefined 呢? console.log(x) // undefined var x 原因在於 javascript 會先程式中的蒐集 var(let/const/function) 宣告並釐清對應的作用域, 最後再執行程式碼,這個行為就如同宣告被提升(Hoisting)到前面行數的程式碼中一樣。 JavaScript 跟傳統 OOP 語言一樣,在變數提升後、宣告初始化、賦值等等時機取用變數會獲得不同輸出結果, 這篇文章(link)中提到, 稱 JavaScript 為直譯式(解釋型)語言實際上是通俗的說法,語言本身沒有規定實現的方式(編/直譯), 舉例來說 Chrome 瀏覽器所使用的 Google V8 JavaScript engine, 所帶的解釋器 (interpreter) 會將程式編譯 (compile) 成字節碼 (bytecode), 最後再由編譯器 (compiler) 即時編譯 (JIT;just-in-time compilation) 編譯成機器碼執行, 而 JavaScript 被編譯時宣告會被蒐集到最頂端進行定義並區分作用域(scope),這個行為就是 Hoisting ,記住重點在於JS編譯後會先定義變數及區分作用域,編譯後的程式看起來就像這樣:

Hero Image
[JS] JavaScript 當中的原型繼承鏈模型

基於原型 (Prototype-Based) 的 JavaScript 一般物件導向式(OOP; Object Oriented Programming) 程式語言 (如:java、c++、c#) 當中的物件是由類別模板 (class) 產生實體物件 (instance),實體物件的屬性各自獨立。類別模板上可設置共用的靜態資源包含靜態方法 (static method)、靜態屬性 (static field),而這些靜態資源可以在沒有建立實體的情況下透過類別名稱直接取用。 JavaScript 中的物件通常隸屬於另一個物件,這種隸屬關係類似物件導向語言的繼承,而在這種關係中的上層物件稱為原型 (Prototype)。原型本身又有自己所屬的原型,這種物件層層繼承的關係稱為原型鏈 (Prototype Chain),幾乎所有物件的最上層原型是一個構造函數叫做 Object 的物件。 因此一般物件導向式語言稱為基於類別 (Class-Based) 的語言;而 Javascript 是基於原型 (Prototype-Based) 的語言。 建立物件原型 JavaScript 本身沒有類別模板的概念,是以構造函數 (constructor) 建立物件,物件可以將 constructor 屬性指向構造函數,但並非所有物件都有構造函數,具有構造函數的物件可直接以構造函數產生原型鏈下一層物件;不具有構造函數的物件只能在其他物件建立完成後,以其他方式設置為其他物件的原型。 建立原型的方法就是直接宣告一個函數,JavaScript 會自動把該函數作為構造函數,並自動建立一個隸屬於 Object.prototype 之下的匿名物件,並把宣告的函數指定給該匿名物件的 constructor 屬性。 // 宣告一個函數 Foo function Foo (){} // Foo.prototype 在 Foo 被宣告時自動建立 Foo.prototype // {constructor: ƒ} // Foo.prototype 的 constructor 屬性自動指向 Foo Foo.prototype.constructor === Foo // true 建立物件 透過構造函數 // 建立一個物件 let bar = new Foo{} // {} 直接對變數賦值 JavaScript 對變數賦值底層行為:以 Object 構造函數建立物件,然後對物件並賦值(故賦值發生在物件建立之後)