前言
JavaScript一直是我滿喜歡的程式語言,原因可能是因為他的執行環境很方便(只要打開瀏覽器就可以使用),不過更多的可能是一種”情結”,畢竟我第一個接觸的程式語言就是JavaScript。
對於JavaScript的評價有褒有貶,有些人推崇它的彈性,有些人深陷它奇怪的行為。
這邊先不評論太多,只是整理一些有趣的小技巧。
Trick 1: 判斷變數類型是否為Primitive
Object建構式可以用來包裝(wrapper)變數,例如將“number”包裝成“number物件”,而已經是object的變數則Object建構式會回傳值。
因此透過判斷Object回傳的內容是否等於變數值,可以用來判斷是否為物件。
1 2 3 4 5 6 7 8 9
| let v1 = 1; let v2 = [2, 3];
function isPrimitive(val) { return Object(val) !== val; }
isPrimitive(v1); isPrimitive(v2);
|
Trick 2: 建立純物件(Pure Object)
純物件(Pure Object)代表物件不包含任何function(預設的也沒有)。
1 2
| var obj = {}; var obj = Object.create(null);
|
Trick 3: 移除陣列內重複的元素
利用Set物件只能保存唯一值的特性,可以用來將陣列內重複的元素移除。
1 2
| let arr = [1, 2, 3, 3, 4, 4, 4, 5]; let newArr = [...new Set(arr)];
|
Trick 4: Declaration與Expression
針對何時應該使用Function Declaration與Function Expression進行比較。
首先是考慮提昇(Hoisting),Function Declaration會被hoist,而Function Expression則否。
1 2 3 4 5 6 7 8 9 10 11
| fun1(); function fun1(){ }
fun2(); const fun2 = ()=>{ }
|
因此如果是全域功能的function,可以使用function declaration,否則可以使用function expression以避免污染全域變數。
另外也可以進一步使用IIFE(Immediately Invoked Function Expressions)。
1 2 3
| array.map( item => { });
|
Trick 5: 走訪物件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| for (const property in obj) { const value = obj[property]; console.log(property, value); }
const allProperties = Object.keys(obj); for (const property of allProperties) { const value = obj[property]; console.log(property, value); }
for (const [key, value] of Object.entries(obj)) { console.log(key, value); }
|
Trick 6: 深拷貝(Deep Copy)
一般的做物件複製的時候,如果只是單純assign的話,只能複製到第一層的內容,而物件內包含的物件概念比較像是複製了”指標”,如此一來當物件內的物件內容被改動時,原本複製的內容也會被改動。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const obj1 = { key: 'value', nestedObj: { key2: 'value2' } }
const obj2 = Object.assign({}, obj1);
obj1.nestedObj.key2 = 'newValue';
console.log(obj2);
|
此時可以利用JSON stringify/paser做深拷貝:
1
| const obj2 = JSON.parse(JSON.stringify(obj));
|
Trick 7: 型別轉換
1 2 3 4 5
| let var1 = 99 + ""
let var2 = !!1
|
Trick 8: 字串反轉
1 2 3
| let str = "ABCDEFG"; const reverse = string => string.split("").reverse().join(""); console.log(reverse(str));
|
Trick 9: 快速Console Log
1 2
| c = console.log.bind(document); c("message")
|
Trick 10: 檢查DOM Element是否可見
可以用來檢查DOM element是否進入畫面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const callback = (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { console.log(${entry.target.id}); } }); };
const options = { threshold: 1.0, }; const observer = new IntersectionObserver(callback, options); const element = document.getElementById('element'); observer.observe(element);
|
Trick 11: 展開Array
1 2 3 4 5 6
| const flat = (array) => { return array.reduce((acc, it) => acc.concat(Array.isArray(it) ? flat(it) : it), []); };
const array = [1, [2, [3, [4, [5]]]]]; const flatArray = flat(array);
|
Trick 12: 測試經過時間
1 2 3
| console.time()
console.timeEnd()
|
Trick 13: For Loop效能優化
透過將length變數初始化,可以避免每次loop都需要get一次
1 2 3 4
| arr = [1,2,3,4,5,6,7,8,9,0] for (let i = 0, length = arr.length; i < length; i++){ }
|
Trick 14: String轉Number
1 2 3 4
| let int = "9"; int = +int;
console.log(typeof int);
|
Trick 15: 取得浮點數的整數部分
1 2 3 4 5
| let float = 12.3 console.log(float_var | 0)
let neg_float = -45.6 console.log(neg_float_var | 0)
|
Trick 16: 用??(Nullish Coalescing)運算子取代||運算子
使用??運算子設定預設值是常見的做法,例如:
1 2 3 4 5 6
| let parameter = { var1 = 'v1', }
let var1 = parameter.var1 || 'default'; let var2 = parameter.var2 || 'default';
|
不過如果設定的值是0或fals時,可能會造成誤判。
此時可以使用更嚴謹的??運算子,他只會判斷undifined與null的狀況。
1 2 3 4 5
| let parameter = { var1 = false, } let var1 = parameter.var1 ?? 'default'; let var2 = parameter.var2 || 'default';
|
Trick 17: 用?.(Optoinal Chaining)運算子
用?.運算子可以簡化用&&判斷null或undefined的情況。
1 2 3 4 5 6 7 8 9
| let var1 = parameter.property && parameter.property.value; let var1 = parameter.property?.value
let input = form.querySelector('input[name=userInput]') let inputValue = input ? input.value : undefined
let input = form.querySelector('input[name=userInput]')?.value
|
Trick 18: 動態import
一般常使用import進行module的載入,不過這種靜態的載入方式有一些限制,
例如必須在script tag上宣告type為module,且不夠彈性。
HTML:
1
| <script src="script.js" type="module"></script>
|
JavaScript:
1
| import submodule from './submodule.js';
|
此時可以採用動態的import:
1 2 3 4 5 6 7 8 9 10 11
| import('./submodule.js') .then((module) => { });
(async () => { let module = await import('./submodule.js'); })();
|
Trick 19: 取得最後一個元素
1 2 3
| arr[arr.length-1]
arr.at(-1)
|
Trick 20: 設定private變數
在class內宣告#開頭的變數可以將其設為private,此變數不會被繼承。
1 2 3 4 5 6 7 8 9
| class ClassWithPrivateField { #privateField; #privateMethod() { return 0; } constructor() { this.#privateField = 0; } }
|