大大提高開發效率的十個JavaScript技巧
JavaScript 是前端開發中的必備語言。但是我發現很多同學對于 JavaScript 的技巧使用卻并不熟悉。所以,今天咱們就來分享一下 JavaScript 的10個好用的技巧,幫你更好地使用 JavaScript,提升開發效率!
1. 使用 flatMap
有些 JavaScript 方法盡管鮮為人知,但它們解決獨特挑戰的潛力能夠增強編碼效率, 比如 flatMap()
數組方法 flatMap() 本質上是 map()和 flat() 的組合,區別在于 flatMap 只能扁平1級,flat 可以指定需要扁平的級數,flatmap 比分別調用這兩個方法稍微高效一些。
- 使用 flat + map
const arr = [1, 2, [4, 5], 6, 7, [8]];
// 使用 map 對每個元素進行操作并用 flat 展平結果
const result = arr.map(element => Array.isArray(element) ? element : [element]).flat();
console.log(result); // output: [1, 2, 4, 5, 6, 7, 8]
- 使用 flatmap
const arr = [1, 2, [4, 5], 6, 7, [8]] ;
console.log(arr.flatMap((element) => element));
// output :[1, 2, 4, 5, 6, 7, 8]
flatmap 盡管是一個方法,但也會有 中間數組 \(指中間創建了必須進行垃圾收集的臨時數組\)[1]的產生,flatMap 非常適合在需要靈活性和可讀性的情況下使用。
2. console 的妙用
console 并不只有 console.log(), 實際生產中都會使用已經封裝好的log庫,而 控制臺對象 console 實際上內置了許多非常有用的方法,幫助您提高調試輸出的質量和可讀性,掌握它們能使您更輕松地 debug 和修復代碼中的問題。
// 1. console.time 和 console.timeEnd
// 測量執行一段代碼所需的時間。識別代碼中的性能瓶頸并對其進行優化
console.time('開始獲取數據');
fetch('https://reqres.in/api/users')
.then(response => response.json())
.then(data => {
console.timeEnd('獲取數據花費時間:');
// ...code
});
// 2. console.dir
// console.dir 方法以分層格式輸出對象的屬性。方便查看對象的結構以及其所有屬性和方法
const promise = new Promise((resolve, reject) => resolve('foo'));
console.dir(promise);
// 3. console.count
// console.count 方法來計算特定日志消息的輸出次數。這對于跟蹤特定代碼路徑的執行次數以及識別代碼中的熱點非常有用
const fun = (x) => console.count(x);
fun('刻晴'); // 1
fun('甘雨'); // 1
fun('刻晴'); // 2
// 4. console.trace
// trace 可以輸出堆棧跟蹤。對于理解代碼中的執行流程以及識別特定日志消息的來源非常有用
const foo = () => console.trace();
const bar = () => foo();
bar();
// 5. console.profile profileEnd
// 測量代碼塊的性能。這對于識別性能瓶頸以及優化代碼以提高速度和效率非常有用。
console.profile('MyProfile');
// 想要測量性能的代碼
for (let i = 0; i < 100000; i++) {
// ...code
}
console.profileEnd('MyProfile');
3. 深拷貝 structuredClone()
此前,如果開發人員想要深拷貝對象,經常需要依賴第三方庫來實現或者手動實現一個神拷貝,或者采取 const cloneObj = JSON.parse(JSON.stringify(obj)); 的 hack, 但其在處理包含循環引用或不符合 JSON 的數據類型(如 Map 和 Set,Blob 等 ) 的更復雜對象時,是有很多不足之處的
而現在,JavaScript 內置了一個 structuredClone() 的方法, 此方法提供了一種簡單有效的方法來深度克隆對象, 且適用于大多數現代瀏覽器和 Node.js v17 以上
// 將原始對象傳遞給該函數, 它將返回一個具有不同引用和對象屬性引用的深層副本
const obj = { name: 'Mike', friends: [{ name: 'Sam' }] };
const clonedObj = structuredClone(obj);
console.log(obj.name === clonedObj); // false
console.log(obj.friends === clonedObj.friends); // false
與眾所周知的 JSON.parse(JSON.stringify())” 不同, structuredClone() 允許您克隆循環引用,這是目前在 JavaScript 中使用深拷貝最簡單的方法。
4. 帶標簽的模板
帶標簽的模板(Tagged\_Templates[2]) - 是模板字符串(反引號)的一種更高級的形式,它允許你使用函數解析模板字面量。
這個高級特性我也是在 Next.js 14[3] 發布后人們都在討論的一張圖才去了解的??,盡管這個特性是 ES6 就有的,至今已有8年?。?!但我敢打賭知道這個并使用過這個特性的人屈指可數。
相信許多人已經見過下圖(因為這個知識點請停止嘲笑 ??Next.js 14), 相信許多人的第一反應就是回到二十年前 PHP 時代并且代碼容易遭受 sql 注入攻擊 , 但實際上是安全的。這得益于模板字符串的高級特性 - ( 帶標簽的模板 \-Tagged\_Templates[4])
如果你不理解 Tagged_Templates 如何工作, 那么就讓我用一個例子來簡單說明下吧:
const checkCurrency = function (currency, amount) {
const symbol = currency[0] === "USD" ? "$" : "¥";
console.log(currency[0], "--" ,currency[1])// Outputs: USD -- RMB
return `${symbol}${amount}`;
};
const amount = 200;
const currency = checkCurrency`USD${amount}RMB`;
console.log(currency); // Outputs: $200
// 1. checkCurrency是一個函數,標簽函數的第一個參數currency包含一個字符串值數組
// 2. 字符串數組由標簽模板里的字符串組成,在`USD${amount}RMB`里,字符串有USD和RMB
// 3. 因此 currency[0] 為第一個字符串 USD, currency[1] 則是第二個字符串 RMB
// 3. checkCurrency函數的其余參數則根據表達式直接插入到字符串中,如 amount = 200
// 4. 在checkCurrency函數的內部,判斷第一個參數數組首項是否是‘USD’,是則選擇"$"符號,否則是 "¥"
// 5. 函數內部將symbol和amount結合在一起返回一個新的字符串,symbol代表貨幣符號,而amount代表傳遞給函數的金額。
// 6. 返回的字符串賦值給 currency 常量, 因此 log為 $200
可以看到,Tagged Templates 的工作方式是將模板字符串里的所有字符串作為一個數組傳遞給函數的第一個參數,其余參數則根據相應的表達式直接插入到字符串中,Tagged Templates將 文字字符串 和表達式的結果 傳遞給函數,然后該函數可以以自定義方式操作并返回它們。這樣開發者在構建 SQL 查詢時,對輸入進行適當的轉義和驗證,從而避免 SQL 注入攻擊。
帶標簽的模板字符串可用于很多用途,例如 安全性、i18n和本地化 等。
5. 使用Symbols作為WeakMap的鍵
WeakMap 和 Map 很像,但不同點在于它的鍵(key) 只能是對象 Objects 和 symbol,這些鍵被作為**弱引用存儲(weakly)**。
為什么?因為 WeakMap 的鍵必須是可垃圾回收的。大多數原始數據類型可以任意創建并且沒有生命周期,因此它們不能用作鍵, 而 對象Objects 和 non-registered symbols 可以用作鍵,因為它們是垃圾可收集的 - MDN- WeakMap[5]。
這個特性意味著除了鍵之外內存中沒有其他對對象的引用,JavaScript 引擎可以在需要時對對象執行垃圾回收。
// map
let user = { name: "User" };
let map = new Map();
map.set(user, "刻晴");
user = null; // 置null來覆蓋引用,'user'被存在 map 的內部,通過 map.keys() 獲取
// WeakMap
let user = { name: "User" };
let weakMap = new WeakMap();
weakMap.set(user, "甘雨");
user = null; // 使用 WeakMap,'user' 已經被從內存中刪除
好了,那 WeakMap 到底有什么作用呢?根據其特點可以聯想到 WeakMap 的用途可以是自定義緩存以及檢測內存泄漏。
通過使用對象作為鍵,您可以將緩存的值與特定對象相關聯。當對象被垃圾收集時,相應的 WeakMap 條目將被自動刪除,立即清除緩存。
在 ES14 中, 使用 symbol 作為 WeakMap 的 key 已經成為可能, 這可以使鍵值對在 WeakMap 中扮演的角色更加清晰。因為唯一能在 WeakMap 中被作為 key 使用的原始類型只有 symbol, symbol 能保證 key 是 唯一的并且無法重新創建。
let mySymbol = Symbol('mySymbol');
let myWeakMap = new WeakMap();
let obj = {
name: '寫前端的刻貓貓'
};
myWeakMap.set(mySymbol, obj);
console.log(myWeakMap.get(mySymbol)); // Output: {name: '寫前端的刻貓貓'}
6. 充分使用 generator
生成器 (Generator) 和 迭代器 (iterators) 可能是 JavaScript 開發人員最不常使用的代碼,其知識僅限于編碼面試。(因為有更好用的語法糖 async/await ???)
?
生成器 (Generator) 是控制異步編程、生成可迭代對象和生成多個值的強大方法。生成器與傳統函數不同。他們可以多次啟動和停止執行。這使它們能夠產生大量值并在以后繼續執行,從而使它們非常適合管理異步操作、構造迭代器和處理無盡的數據流。
試想一下,假如在一個獲取數據的場景下,數據庫/ API 的數據量可能是無限的,而你必須將它們傳輸到前端,你會怎么做呢?
這種情況下, react 中最常用的方案就是無限加載方案, 如果是在 node 中或者原生JS,你該如何實現無限加載之類的功能。
async function *fetchProducts(){
while (true){
const productUrl = "https://fakestoreapi.com/products?limit=2";
const res = await fetch(productUrl)
const data = await res.json()
yield data;
// 在這里操作用戶界面
// 或將其保存在數據庫或其他地方
// 將其用作副作用的地方
// 即使某些條件匹配,也中斷流程
}
}
async function main() {
const itr = fetchProducts();
// 這應該根據用戶交互來調用
// 或者其他技巧,因為您不希望出現無限加載。
console.log( await itr.next() );
}
return main()
這就是 迭代器 (iterators) 真正有用的地方,而不是將請求的大量數據流式傳輸到本地存儲或者某些位置。這是使用 異步生成器(async generators) 執行此操作的這樣之一, 這樣我們就可以解決JS中的無限加載問題。
7. 私有類字段
現在,JavaScript類支持使用#符號的私有字段。
私有字段不能從類外部訪問,從而提供封裝和信息隱藏。
class Counter {
#count = 0;
increment() {
this.#count++;
}
getCount() {
return this.#count;
}
}
const counter = new Counter();
counter.increment();
console.log(counter.getCount()); // 1
8. Promise.allSettled()
Promise.allSettled() 方法返回一個 Promise,該 Promise 在所有給定的 Promise 已經 resolve 或 reject 后 resolve,提供每個 Promise 的結果數組。
const promises = [
Promise.resolve('Resolved'),
Promise.reject('Rejected')
];
Promise.allSettled(promises)
.then(results => {
console.log(results);
});
// [{ status: "fulfilled", value: "Resolved" }, { status: "rejected", reason: "Rejected" }]
9. globalThis 全局對象
globalThis對象提供了一種在不同環境下(包括瀏覽器和Node.js)訪問全局對象的一致方式。
console.log(globalThis === window); // 在瀏覽器場景下: true
console.log(globalThis === global); // 在 Node.js 中: outputs: true
10. 代理
代理對象允許你為基本對象操作創建自定義行為。
它允許截獲和修改對象操作,例如訪問屬性、賦值和調用方法。
const handler = {
get: function (obj, prop) {
return prop in obj ? obj[prop] : 37;
},
};
const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;
console.log(p.a, p.b); // 1, undefined
console.log("c" in p, p.c); // false, 37
Reference
[1]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap#description: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FArray%2FflatMap%23description
[2]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FTemplate_literals%23tagged_templates
[3]https://nextjs.org/: https://link.juejin.cn?target=https%3A%2F%2Fnextjs.org%2F
[4]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FTemplate_literals%23tagged_templates
[5]https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap: https://link.juejin.cn?target=https%3A%2F%2Fdeveloper.mozilla.org%2Fen-US%2Fdocs%2FWeb%2FJavaScript%2FReference%2FGlobal_Objects%2FWeakMap