<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                作為前端人員要回答這個問題,需要了解這三個知識點: * 同步 * 異步 * Async/Await 首先,**js 是單線程的(重復三遍)**,所謂單線程, 通俗的講就是,一根筋,執行代碼是一行一行的往下走(即所謂的**同步**), 如果上面的沒執行完,就癡癡的等著(是不是很像戀愛中在路邊等她/他的你,假裝 new 了個對象,啊哈哈哈,調皮一下很開心), 還是舉個 ?? 吧: ~~~javascript // chrome 75 function test() { let d = Date.now(); for (let i = 0; i < 1e8; i++) {} console.log(Date.now() - d); // 62ms左右 } function test1() { let d = Date.now(); console.log(Date.now() - d); // 0 } test(); test1(); ~~~ 上面僅僅是一個 for 循環,而在實際應用中,會有大量的網絡請求,它的響應時間是不確定的,這種情況下也要癡癡的等么?顯然是不行的,因而 js 設計了異步,即 發起網絡請求(諸如 IO 操作,定時器),由于需要等服務器響應,就先不理會,而是去做其他的事兒,等請求返回了結果的時候再說(即**異步**)。 那么如何實現異步呢?其實我們平時已經在大量使用了,那就是`callback`,例如: ~~~javascript $.ajax({ url: 'http://xxx', success: function(res) { console.log(res); }, }); ~~~ success 作為函數傳遞過去并不會立即執行,而是等請求成功了才執行,即**回調函數**(callback) ~~~javascript const fs = require('fs'); fs.rename('舊文件.txt', '新文件.txt', err => { if (err) throw err; console.log('重命名完成'); }); ~~~ 和網絡請求類似,等到 IO 操作有了結果(無論成功與否)才會執行第三個參數:`(err)=>{}` 從上面我們就可以看出,**實現異步的核心就是回調鉤子**,將 cb 作為參數傳遞給異步執行函數,當有了結果后在觸發 cb。想了解更多,去看看`event-loop`機制吧。 至于 async/await 是如何出現的呢,在 es6 之前,大多 js 數項目中會有類似這樣的代碼: ~~~javascript ajax1(url, () => { ajax2(url, () => { ajax3(url, () => { // do something }); }); }); ~~~ 這種函數嵌套,大量的回調函數,使代碼閱讀起來晦澀難懂,不直觀,形象的稱之為**回調地獄(callback hell)**,所以為了在寫法上能更通俗一點,es6+陸續出現了`Promise`、`Generator`、`Async/await`,力求在寫法上簡潔明了,可讀性強。 \=========================我是分割線========================== 以上只是鋪墊,下面在進入正題 ??,開始說道說道主角:`async/await` \=========================我是分割線========================== `async/await`是參照`Generator`封裝的一套異步處理方案,可以理解為`Generator`的語法糖, 所以了解`async/await`就不得不講一講`Generator`, 而`Generator`又依賴于迭代器`Iterator`, 所以就得先講一講`Iterator`, 而`Iterator`的思想呢又來源于單向鏈表, 終于找到源頭了:**單向鏈表** ## 1. 單向鏈表 > wiki:鏈表(Linked list)是一種常見的基礎數據結構,是一種[線性表](https://zh.wikipedia.org/wiki/%E7%BA%BF%E6%80%A7%E8%A1%A8),但是并不會按線性的順序儲存數據,而是在每一個節點里存到下一個節點的[指針](https://zh.wikipedia.org/wiki/%E6%8C%87%E6%A8%99_(%E9%9B%BB%E8%85%A6%E7%A7%91%E5%AD%B8))(Pointer)。由于不必須按順序儲存,鏈表在插入的時候可以達到 o(1)的復雜度,比另一種線性表[順序表](https://zh.wikipedia.org/wiki/%E9%A1%BA%E5%BA%8F%E8%A1%A8)快得多,但是查找一個節點或者訪問特定編號的節點則需要 o(n)的時間,而順序表響應的時間復雜度分別是 o(logn)和 o(1)。 總結一下鏈表優點: * 無需預先分配內存 * 插入/刪除節點不影響其他節點,效率高(典型的例子:dom 操作) 單向鏈表:是鏈表中最簡單的一種,它包含兩個域,一個信息域和一個指針域。這個鏈接指向列表中的下一個節點,而最后一個節點則指向一個空值。 ![](https://img.kancloud.cn/20/cf/20cf2a60be27c7ccc5750023171e35fa_825x418.png) 一個單向鏈表包含兩個值: 當前節點的值和一個指向下一個節點的鏈接 單鏈特點:節點的鏈接方向是單向的;相對于數組來說,單鏈表的的隨機訪問速度較慢,但是單鏈表刪除/添加數據的效率很高。 理解 js 原型鏈/作用域鏈的話,理解這個很容易,他們是相通的。編程語言中,數組的長度是固定的,所以數組中的增加和刪除比較麻煩,需要頻繁的移動數組中的其他元素,而 js 作為一門動態語言,數組本質是一個類似數組的對象,是動態的,不需要預先分配內存~ 那么如何設計一個單向鏈表呢?這個取決于我們需要哪些操作,通常有: * append(element):追加節點 * insert(element,index):在索引位置插入節點 * remove(element):刪除第一個匹配到的節點 * removeAt(index):刪除指定索引節點 * removeAll(element):刪除所有匹配的節點 * get(index):獲取指定索引的節點信息 * set(element,index):修改指定索引的節點值 * indexOf(element):獲取某節點的索引位置 * clear():清除所有節點 * length():返回節點長度 * printf():打印節點信息 看到這些方法是不是有些許熟悉,當你用原生 js 或 jq 時常會用上面類似的方法,現在根據上面列出的方法進行實現一個單向鏈: ~~~javascript // 節點模型 class LinkNode { constructor(element, next) { this.element = element; this.next = next; } } class LinkedList { constructor() { this._head = null; this._size = 0; this._errorBoundary = this._errorBoundary.bind(this); this._getNodeByIndex = this._getNodeByIndex.bind(this); this.append = this.append.bind(this); this.insert = this.insert.bind(this); this.remove = this.remove.bind(this); this.removeAt = this.removeAt.bind(this); this.removeAll = this.removeAll.bind(this); this.getElement = this.getElement.bind(this); this.setIndex = this.setIndex.bind(this); this.indexOf = this.indexOf.bind(this); this.clear = this.clear.bind(this); this.length = this.length.bind(this); this.printf = this.printf.bind(this); } // 邊界檢驗 _errorBoundary(index) { if (index < 0 || index >= this._size) { throw `超出邊界(${0}~${this._size}),目標位置${index}不存在!`; } } // 根據索引獲取目標對象 _getNodeByIndex(index) { this._errorBoundary(index); let obj = this._head; for (let i = 0; i < index; i++) { obj = obj.next; } return obj; } // 追加節點 append(element) { if (this._size === 0) { this._head = new LinkNode(element, null); } else { let obj = this._getNodeByIndex(this._size - 1); obj.next = new LinkNode(element, null); } this._size++; } // 在索引位置插入節點 insert(element, index) { if (index === 0) { this._head = new LinkNode(element, this._head); } else { let obj = this._getNodeByIndex(index - 1); obj.next = new LinkNode(element, obj.next); } this._size++; } // 刪除第一個匹配到的節點 remove(element) { if (this._size < 1) return null; if (this._head.element == element) { this._head.element = this._head.next; this._size--; return element; } else { let temp = this._head; while (temp.next) { if (temp.next.element == element) { temp.next = temp.next.next; this._size--; return element; } else { temp = temp.next; } } } return null; } // 刪除指定索引節點 removeAt(index) { this._errorBoundary(index); let element = null; if (index === 0) { element = this._head.element; this._head = this._head.next; } else { let prev = this._getNodeByIndex(index - 1); element = prev.next.element; prev.next = prev.next.next; } this._size--; return element; } // 刪除所有匹配的節點 removeAll(element) { // 創建虛擬頭節點, let v_head = new LinkNode(null, this._head); let tempNode = v_head; // let tempEle = null; while (tempNode.next) { if (tempNode.next.element == element) { tempNode.next = tempNode.next.next; this._size--; // tempEle = element; } else { tempNode = tempNode.next; } } this._head = v_head.next; } // 獲取指定索引的節點信息 getElement(index) { return this._getNodeByIndex(index).element; } // 修改指定索引的節點值 setIndex(element, index) { this._errorBoundary(index); let obj = this._getNodeByIndex(index); obj.element = element; } // 獲取某節點的索引位置 indexOf(element) { let obj = this._head; let index = -1; for (let i = 0; i < this._size; i++) { if (obj.element == element) { index = i; break; } obj = obj.next; } return index; } // 清除所有節點 clear() { this._head = null; this._size = 0; } // 返回節點長度 length() { return this._size; } // 打印節點信息 printf() { let obj = this._head; const arr = []; while (obj != null) { arr.push(obj.element); obj = obj.next; } const str = arr.join('->'); return str || null; } } const obj = new LinkedList(); obj.append(0); obj.append(1); obj.append(2); obj.printf(); // "0->1->2" obj.insert(3, 3); obj.printf(); // "0->1->2->3" obj.remove(3); obj.printf(); // "0->1->2" obj.removeAt(0); obj.printf(); // "1->2" obj.setIndex(0, 0); obj.printf(); // "0->2" obj.indexOf(2); // 1 obj.length(); // 2 obj.clear(); obj.printf(); // null ~~~ [查看源碼](https://github.com/Mr-jiangzhiguo/book/code/linked.js) 通過以上,我假裝你明白什么是單向鏈表,并且能夠用代碼實現一個單向鏈表了,下一步開始說一說**迭代器**`Iterator` ## 2. Iterator `Iterator`翻譯過來就是 **迭代器(遍歷器)** 讓我們先來看看它的遍歷過程(類似于單向鏈表): * 創建一個**指針對象**,指向當前數據結構的起始位置 * 第一次調用指針對象的`next`方法,將指針指向數據結構的第一個成員 * 第二次調用指針對象的`next`方法,將指針指向數據結構的第二個成員 * 不斷的調用指針對象的`next`方法,直到它指向數據結構的結束位置 一個對象要變成可迭代的,必須實現`@@iterator`方法,即對象(或它原型鏈上的某個對象)必須有一個名字是`Symbol.iterator`的屬性(原生具有該屬性的有:字符串、數組、類數組的對象、Set 和 Map): | 屬性 | 值 | | --- | --- | | [Symbol.iterator]: | 返回一個對象的無參函數,被返回對象符合迭代器協議 | 當一個對象需要被迭代的時候(比如開始用于一個`for..of`循環中),它的`@@iterator`方法被調用并且無參數,然后返回一個用于在迭代中獲得值的迭代器 迭代器協議:產生一個有限或無限序列的值,并且當所有的值都已經被迭代后,就會有一個默認的返回值 當一個對象只有滿足下述條件才會被認為是一個迭代器: 它實現了一個`next()`的方法,該方法**必須返回一個對象**,對象有兩個必要的屬性: * `done`(bool) * true:迭代器已經超過了可迭代次數。這種情況下,value 的值可以被省略 * 如果迭代器可以產生序列中的下一個值,則為 false。這等效于沒有指定 done 這個屬性 * `value`迭代器返回的任何 JavaScript 值。done 為 true 時可省略 根據上面的規則,咱們來自定義一個簡單的迭代器: ~~~javascript const makeIterator = arr => { let nextIndex = 0; return { next: () => nextIndex < arr.length ? { value: arr[nextIndex++], done: false } : { value: undefined, done: true }, }; }; const it = makeIterator(['人月', '神話']); console.log(it.next()); // { value: "人月", done: false } console.log(it.next()); // { value: "神話", done: false } console.log(it.next()); // {value: undefined, done: true } ~~~ 我們還可以自定義一個可迭代對象: ~~~javascript const myIterable = {}; myIterable[Symbol.iterator] = function*() { yield 1; yield 2; yield 3; }; for (let value of myIterable) { console.log(value); } // 1 // 2 // 3 //or console.log([...myIterable]); // [1, 2, 3] ~~~ 了解了迭代器,下面可以進一步了解生成器了 ## 3. Generator `Generator`:生成器對象是生成器函數(GeneratorFunction)返回的,它符合**可迭代協議**和**迭代器協議**,既是迭代器也是可迭代對象,可以調用`next`方法,但它不是函數,更不是構造函數 生成器函數(GeneratorFunction): > function\* name([param[, param[, ... param]]]) { statements } > > * name:函數名 > * param:參數 > * statements:js 語句 調用一個生成器函數并不會馬上執行它里面的語句,而是返回一個這個生成器的迭代器對象,當這個迭代器的`next()`方法被首次(后續)調用時,其內的語句會執行到第一個(后續)出現`yield`的位置為止(讓執行處于**暫停狀**),`yield`后緊跟迭代器要返回的值。或者如果用的是`yield*`(多了個星號),則表示將執行權移交給另一個生成器函數(當前生成器**暫停執行**),調用`next()`(再啟動)方法時,如果傳入了參數,那么這個參數會作為**上一條執行的`yield`語句的返回值**,例如: ~~~javascript function* another() { yield '人月神話'; } function* gen() { yield* another(); // 移交執行權 const a = yield 'hello'; const b = yield a; // a='world' 是 next('world') 傳參賦值給了上一個 yidle 'hello' 的左值 yield b; // b=! 是 next('!') 傳參賦值給了上一個 yidle a 的左值 } const g = gen(); g.next(); // {value: "人月神話", done: false} g.next(); // {value: "hello", done: false} g.next('world'); // {value: "world", done: false} 將 'world' 賦給上一條 yield 'hello' 的左值,即執行 a='world', g.next('!'); // {value: "!", done: false} 將 '!' 賦給上一條 yield a 的左值,即執行 b='!',返回 b g.next(); // {value: undefined, done: false} ~~~ 看到這里,你可能會問,`Generator`和`callback`有啥關系,如何處理異步呢?其實二者沒有任何關系,我們只是通過一些方式強行的它們產生了關系,才會有`Generator`處理異步 我們來總結一下`Generator`的本質,暫停,它會讓程序執行到指定位置先暫停(`yield`),然后再啟動(`next`),再暫停(`yield`),再啟動(`next`),而這個暫停就很容易讓它和異步操作產生聯系,因為我們在處理異步時:開始異步處理(網絡求情、IO 操作),然后暫停一下,等處理完了,再該干嘛干嘛。不過值得注意的是,**js 是單線程的(又重復了三遍)**,異步還是異步,callback 還是 callback,不會因為`Generator`而有任何改變 下面來看看,用`Generator`實現異步: ~~~javascript const promisify = require('util').promisify; const path = require('path'); const fs = require('fs'); const readFile = promisify(fs.readFile); const gen = function*() { const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); }; const g = gen(); const g1 = g.next(); console.log('g1:', g1); g1.value .then(res1 => { console.log('res1:', res1); const g2 = g.next(res1); console.log('g2:', g2); g2.value .then(res2 => { console.log('res2:', res2); g.next(res2); }) .catch(err2 => { console.log(err2); }); }) .catch(err1 => { console.log(err1); }); // g1: { value: Promise { <pending> }, done: false } // res1: { // "a": 1 // } // { // "a": 1 // } // g2: { value: Promise { <pending> }, done: false } // res2: { // "b": 2 // } // { // "b": 2 // } ~~~ 以上代碼是`Generator`和`callback`結合實現的異步,可以看到,仍然需要手動執行`.then`層層添加回調,但由于`next()`方法返回對象`{value: xxx,done: true/false}`所以我們可以簡化它,寫一個自動執行器: ~~~javascript const promisify = require('util').promisify; const path = require('path'); const fs = require('fs'); const readFile = promisify(fs.readFile); function run(gen) { const g = gen(); function next(data) { const res = g.next(data); // 深度遞歸,只要 `Generator` 函數還沒執行到最后一步,`next` 函數就調用自身 if (res.done) return res.value; res.value.then(function(data) { next(data); }); } next(); } run(function*() { const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); // { // "a": 1 // } const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); // { // "b": 2 // } }); ~~~ 說了這么多,怎么還沒有到`async/await`,客官別急,馬上來了(其實我已經漏了一些內容沒說:Promise 和 callback 的關系,thunk 函數,co 庫,感興趣的可以去 google 一下,yuanyifeng 老師講的[es6 入門](http://es6.ruanyifeng.com/)非常棒,我時不時的都會去看一看) ## 4\. Async/Await 首先,`async/await`是`Generator`的語法糖,上面*我是分割線*下的第一句已經講過,先來看一下二者的對比: ~~~javascript // Generator run(function*() { const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); }); // async/await const readFile = async ()=>{ const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' }); console.log(res1); const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' }); console.log(res2); return 'done'; } const res = readFile(); ~~~ 可以看到,`async function`代替了`function*`,`await`代替了`yield`,同時也無需自己手寫一個自動執行器`run`了 現在再來看看`async/await`的特點: * 當`await`后面跟的是 Promise 對象時,才會異步執行,其它類型的數據會同步執行 * 執行`const res = readFile();`返回的仍然是個 Promise 對象,上面代碼中的`return 'done';`會直接被下面`then`函數接收到 ~~~javascript res.then(data => { console.log(data); // done }); ~~~ ## 摘自 [第 9 題:Async/Await 如何通過同步的方式實現異步](https://www.muyiy.cn/question/async/9.html)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看