javascript-hardest-concepts

#最難理解的JavaScript概念 昨天我在Twitter上問了個問題: “在JavaScript中,最複雜的主題是什麼?你學習它花費最多時間的是哪個?” 我收到了200多個回覆。 以下是一些經常被提到的內容: this 非同步JavaScript(promises, callbacks, async/await) 閉包 事件循環 遞歸 作用域 變數提升 原型繼承 bind(), call(), apply() reduce() 生成器 fetch() 除了生成器這個比較小眾的概念外,其餘的都是我們每天在JavaScript中都會用到的。 我在這個博客上有關於這些主題的文章,你可以使用搜索功能來找到它們,但我問這個問題的原因是我正在為11月份準備一個高質量的新課程,名為The JavaScript Course。 這個課程將從基礎知識開始,因為我不想把還不了解JavaScript的人排除在外,但我們會很快深入到真正的內容。 我想確保覆蓋到所有令人困惑的內容。 我正在組織這個新課程,這是我以前從未做過的方式,非常特別。 這個課程的形式將回顧我在2019年做過的一個課程,每天發送一封郵件,其中包含一些問題和挑戰。 這個課程將持續4週,總共20天(星期一到星期五)。每天都將解鎖一個新的課程,並且以有趣和互動的方式來完成。我們將全力投入到當天的主題中。 每天你都會收到一封郵件,推動你采取行動。穩定性很重要,一旦你註冊,我不希望你只是旁觀。我希望你能加入其中,與其他註冊課程的人一起學習。 這樣一個大型社區,為期20天,可以一起成長。 我為此建立了一個新的課程平台,希望這將是一次很棒的體驗。 當然,我們將建立一個社區,因為這是一個特殊的活動,所有特殊的活動都值得建立一個社區來互相幫助。 嗯,我上面列出的所有主題都將得到特殊對待,因為這可能是一個能讓它們一次擊中要害的課程。

Node.js事件循環

事件循環是了解Node.js最重要的方面之一。 為什麼這個很重要?因為它解釋了Node.js為什麼可以是非同步的並且具有非阻塞的I/O,也解釋了Node.js的"殺手級應用",這正是使它變得如此成功的原因。 Node.js的JavaScript代碼運行在單個線程上,一次只能發生一件事。 這實際上是一種很有幫助的限制,因為它簡化了編程,不需要擔心並發問題。 您只需注意如何編寫代碼,避免阻塞線程的任何事情,例如同步網絡調用或無限循環。 總的來說,在大多數瀏覽器中,每個瀏覽器標簽都有一個事件循環,以確保每個進程都是隔離的,避免網頁中的無限循環或重型處理導致整個瀏覽器被阻塞。 環境管理多個並發的事件循環,以處理API調用等。Web Worker也運行在它們自己的事件循環中。 您只需要關心您的代碼將運行在一個單獨的事件循環上,並根據這一點來編寫代碼,以避免阻塞它。 阻塞事件循環 任何需要太長時間才能將控制權交還給事件循環的JavaScript代碼將會阻塞頁面上的任何JavaScript代碼的執行,甚至阻塞UI線程,使用戶無法點擊、滾動頁面等。 在JavaScript中,幾乎所有的I/O原語都是非阻塞的,例如網絡請求、文件系統操作等。阻塞是例外,這就是為什麼JavaScript在很大程度上基於回調,最近則是基於Promise和async/await的原因。 調用堆疊 調用堆疊是一個後進先出(LIFO)的隊列。 事件循環會不斷檢查調用堆疊,以查看是否有需要運行的函數。 在這個過程中,它將找到的每個函數調用添加到調用堆疊中,並按順序執行每個函數。 您可能熟悉調試器或瀏覽器控制台中的錯誤堆疊跟踪,瀏覽器通過查找調用堆疊中的函數名稱來告訴您哪個函數產生了當前的調用,如下圖所示: 一個簡單的事件循環解釋 讓我們舉個例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') bar() baz() } foo() 這段代碼將打印出: foo bar baz 如預期一樣。 當代碼運行時,首先調用foo(),在foo()內部,我們首先調用bar(),然後調用baz()。 此時,調用堆疊如下: 事件循環在每次迭代時檢查調用堆疊,並執行其中的所有函數,直到調用堆疊為空。 排隊函數的執行 上面的示例看起來很正常,沒有什麼特別之處,JavaScript找到要執行的東西,按順序執行它們。 讓我們來看看如何延遲一個函數,直到堆疊清空。 setTimeout(() => {}, 0)的用法是調用一個函數,但是在代碼中的其他函數執行完畢之後再執行它。 請看下面的例子: const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console....

在 JavaScript 中如何在循環中使用 await

下面是如何使用 for..of 循環來迭代一個數組並在循環內部使用 await: const fun = (prop) => { return new Promise(resolve => { setTimeout(() => resolve(`done ${prop}`), 1000); }) } const go = async () => { const list = [1, 2, 3] for (const prop of list) { console.log(prop) console.log(await fun(prop)) } console.log('done all') } go() 需要將循環放置在一個 async 函數中,然後才能使用 await,循環會停止迭代,直到等待的 Promise 解決為止。 您也可以使用 for..in 循環來迭代對象: const fun = (prop) => { return new Promise(resolve => { setTimeout(() => resolve(`done ${prop}`), 1000); }) } const go = async () => { const obj = { a: 1, b: 2, c: 3 }; for (const prop in obj) { console....

如何使用 Node.js 的 fs 模組與 async/await

Node.js 內建的模組以往被稱為非 promise-based 模組。 這是由於在 promise 之前,這些模組就已經存在了。 我們已經有了 promisify 有一段時間了。但我最近發現 Node.js 提供了一個新的基於 promise 的 API。 我以為這是新功能,但它其實已經在 Node.js 10(2018年)中引入了,已經過了一段時間! 目前這個功能只適用於 fs 內建模組。 我不確定這是否很快就會移植到其他原生模組。 以下是如何使用它: import * as fs from 'node:fs/promises'; | 注意到現在可以使用 node:fs 约定来識別內建模組。 現在你可以使用任何 fs 方法,使用 promises 或 await: const posts = await fs.readdir('content');

如何使用Async和Await與Array.prototype.map()

結合async/await和map()的使用可能有點棘手。了解如何使用。 您想在map()調用中執行一個異步函數,對數組的每個元素執行操作並取回結果。 該如何做到這一點? 這是正確的語法: const list = [1, 2, 3, 4, 5] //... 填充了數值的數組 const functionThatReturnsAPromise = item => { //返回一個Promise的函數 return Promise.resolve('ok') } const doSomethingAsync = async item => { return functionThatReturnsAPromise(item) } const getData = async () => { return Promise.all(list.map(item => doSomethingAsync(item))) } getData().then(data => { console.log(data) }) 主要要注意的是Promise.all()的使用,當所有的Promise都被解析時,它才會被解析。 list.map()返回一個Promise列表,所以在result中,我們將得到當我們運行的所有內容都被解析時的值。 請記住,在調用await的任何代碼周圍都必須包裹在一個async函數中。 有關Promise的詳細信息,請參見Promises文章,以及async/await指南。 使用這些占位符函數名稱來展示示例可能很難理解,所以這是一個簡單的例子,展示了如何使用這種技巧,這是我為Twitter克隆版本編寫的Prisma數據刪除函數,首先刪除推文,然後刪除用戶: export const clearData = async (prisma) => { const users = await prisma.user.findMany({}) const tweets = await prisma....

如何在 JavaScript 中使用頂層等待

學習如何在當前的 v8 中使用這個新功能 通常只能在異步函數內部使用等待。因此通常要宣告立即調用的異步函數表達式來包裹它: (async () => { await fetch(/* ... */) })() 或者也可以先宣告一個函數然後再調用它: const doSomething = async () => { await fetch(/* ... */) } doSomething() 頂層等待將允許我們簡單地運行: await fetch(/* ... */) 而不需要這麼多樣板代碼。 注意:這僅在ES模塊中運作。 對於單個的 JavaScript 文件,無需打包工具,你只需將其保存為 .mjs 擴展名,就可以使用頂層等待。

如何在 Node.js 的回調函數中使用 promises 和 await

大部分的 Node.js API 在還沒有 promises 的時候就已經建立了,在這些 API 中,使用了回調函數的解決方案。 一般的 Node.js API 使用方式如下: doSomething(param, (err, result) => { }) 這個也同樣適用於一些庫,例如 node-redis。當我在一個項目中使用它時,到了某個時候我真的需要把所有的回調函數都移除掉,因為我有太多層次的嵌套回調函數,這種情況下就會陷入所謂的 “回調地獄”。 另外,有時候必須避免使用回調函數,因為你需要從函數中返回函數調用的結果。如果這個結果是通過回調函數返回的,那麼想要獲取結果的唯一方法就是將它作為另一個函數的參數返回,進而持續進行回調函數的操作。 const myFunction = () => { doSomething(param, (err, result) => { return result //無法從 `myFunction` 中返回這個結果 }) } const myFunction = callback => { doSomething(param, (err, result) => { callback(result) //不行 }) } myFunction(result => { console.log(result) }) 但是,有一個簡單的解決方案,這也是由 Node.js 自身提供的。我們可以通過使用內置的 util 模塊中的 promisify 方法 “promisify” 一個不支援 promises 的函數(以及相應的 async/await 語法)。...

如何在JavaScript中返回異步函數的結果

找出如何返回異步函數(使用Promise或回調函數)的結果,使用JavaScript。 假設你遇到了這個問題:你需要進行一個異步調用,並且需要將該調用的結果從原始函數中返回。 像這樣: const mainFunction = () => { const result = asynchronousFunction() return result } 但是asynchronousFunction()在內部執行了一些異步調用(例如fetch()調用),並且無法直接返回結果。該函數可能內部有一個需要等待的Promise,或者使用了回調函數。像這樣: const asynchronousFunction = () => { return fetch('./file.json').then(response => { return response }) } 那麼該怎麼辦呢? 使用async/await是最直接的解決方案。您可以使用await關鍵字代替基於Promise的方法,像我們之前使用的方法一樣: const asynchronousFunction = async () => { const response = await fetch('./file.json') return response } 在這種情況下,在mainFunction中,我們需要在函數簽名前加上async,並在調用asynchronousFunction()之前加上await: const mainFunction = async () => { const result = await asynchronousFunction() return result } 現在,因為它是一個async函數,它會返回一個Promise: mainFunction() // 返回一個Promise 為了獲取結果,您可以將其封裝在IIFE中,就像這樣: (async () => { console....

如何在Node.js中使用MongoDB

在本教程中,我將向您展示如何從Node.js與MongoDB數據庫進行互動。 如果您對MongoDB不熟悉,請查閱我們的基礎指南,以及如何安裝和使用它的指南:) 我們將使用官方的mongodb npm套件。如果您已經有一個正在進行中的Node.js項目,可以使用以下命令來安裝它: npm install mongodb 如果您從頭開始,請在終端中創建一個新的文件夾,然後運行npm init -y來啟動新的Node.js項目,然後運行npm install mongodb命令。 連接到MongoDB 您需要引入mongodb套件,然後從中獲取MongoClient對象。 const mongo = require('mongodb').MongoClient 創建MongoDB服務器的URL。如果您在本地使用MongoDB,URL將是類似mongodb://localhost:27017的形式,因為27017是默認端口。 const url = 'mongodb://localhost:27017' 然後使用mongo.connect()方法來獲取對MongoDB實例客戶端的引用。 mongo.connect(url, { useNewUrlParser: true, useUnifiedTopology: true }, (err, client) => { if (err) { console.error(err) return } // ... }) 現在,您可以使用client.db()方法選擇數據庫。 const db = client.db('kennel') 創建並獲取集合 您可以使用db.collection()方法獲取集合。如果集合還不存在,它將被創建。 const collection = db.collection('dogs') 向集合插入數據 在app.js中添加以下函數,它使用insertOne()方法將一個對象添加到dogs集合中。 collection.insertOne({name: 'Roger'}, (err, result) => { }) 您可以使用insertMany()來添加多個項目,將數組作為第一個參數傳入。 collection.insertMany([{name: 'Togo'}, {name: 'Syd'}], (err, result) => { }) 查找所有文檔 使用集合上的find()方法來獲取添加到集合中的所有文檔。...

如何將回調轉換為async/await

我有一些使用回調函數的程式碼。不涉及太多實現細節,以下是要點: const uploadFile = (callback) => { // 上傳文件,然後將文件位置通過回調函數返回 callback(location) } uploadFile((location) => { // 執行後續操作 }) 如上所示,我調用uploadFile函數,當它完成所需操作後,就會調用該回調函數。 但是我希望在整個程式碼中都使用async/await,因此我決定在這裡使用async/await來替代回調函數。 這是我所做的:我將uploadFile函數的主體代碼用return new Promise()包裹起來,並在獲取到要返回的數據後調用resolve(): const uploadFile = () => { return new Promise((resolve, reject) => { // 上傳文件,然後通過回調函數返回文件位置 resolve(location) }) } const location = await uploadFile() 現在,我可以在第一層級中使用location數據,而不是它被包裹在回調函數中。 這有助於讓我的代碼更加整潔,並更好地理解它。 如果您有興趣,這是實際函數的完整代碼,您可以在其中看到此概念的具體示例: const uploadFile = (fileName, id, callback) => { const fileContent = fs.readFileSync(fileName) const params = { Bucket: process.env.AWS\_BUCKET\_NAME, Key: `file.jpg`, Body: fileContent } s3....