<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、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # ES ## 全局作用域中,用 const 和 let 聲明的變量不在 window 上,那到底在哪里~ 如何去獲取~ ~~~js var a = 1 let b = 2 const c = 3 console.dir(new Function()) ~~~ * 在全局作用域中,用 let 和 const 聲明的全局變量并沒有在全局對象中,只是一個塊級作用域(Script)中 * 在定義變量的塊級作用域中獲取 ~~~js let b = 2 const c = 3 // like (function() { var b = 2 var c = 3 })() ~~~ ## var、let 和 const 區別的實現原理是什么~ ①、var 聲明變量會掛在window, let const 不會 ②、let, const 聲明形成 作用域 ③、同一作用域下 let const 不能聲明 同名變量, 而var 可以 ④、暫存死區 ⑤、const 聲明后不得修改 * 聲明過程 * var:遇到有var的作用域,在任何語句執行前都已經完成了聲明和初始化,也就是變量提升而且拿到undefined的原因由來~ * function: 聲明、初始化、賦值一開始就全部完成,所以函數的變量提升優先級更高 * let:解析器進入一個塊級作用域,發現let關鍵字,變量只是先完成聲明,并沒有到初始化那一步。 此時如果在此作用域提前訪問,則報錯xx is not defined,這就是暫時性死區的由來。 等到解析到有let那一行的時候,才會進入初始化階段。如果let的那一行是賦值操作,則初始化和賦值同時進行 * 內存分配 * var 的話會直接在棧內存里預分配內存空間,然后等到實際語句執行的時候,再存儲對應的變量; * let 是不會在棧內存里預分配內存空間,而且在棧內存分配變量時,做一個檢查,如果已經有相同變量名存在就會報錯 * const 也不會預分配內存空間,在棧內存分配變量時也會做同樣的檢查。不過const存儲的變量是不可修改的; 對于基本類型來說你無法修改定義的值 對于引用類型來說你無法修改棧內存里分配的指針,但是你可以修改指針指向的對象里面的屬性 ## ES5/ES6 的繼承除了寫法以外還有什么區別~ * class 聲明內部會啟用嚴格模式。 ~~~js // 引用一個未聲明的變量 function Bar() { baz = 42; // it's ok } const bar = new Bar() class Foo { constructor() { fol = 42 // ReferenceError: fol is not defined } } const foo = new Foo() ~~~ * class 的所有方法(包括靜態方法和實例方法)都是不可枚舉的。 ~~~js function Bar() { this.bar = 42 } Bar.answer = function() { return 42 } Bar.prototype.print = function() { console.log(this.bar) } const barKeys = Object.keys(Bar) // ['answer'] const barProtoKeys = Object.keys(Bar.prototype) // ['print'] class Foo { constructor() { this.foo = 42 } static answer() { return 42 } print() { console.log(this.foo) } } const fooKeys = Object.keys(Foo); // [] const fooProtoKeys = Object.keys(Foo.prototype); // [] ~~~ * class 的所有方法(包括靜態方法和實例方法)都沒有原型對象 prototype,也沒有 construct,不能使用 new 來調用。 ~~~js function Bar() { this.bar = 42 } Bar.prototype.print = function() { console.log(this.bar) } const bar = new Bar() const barPrint = new bar.print() // it's ok class Foo { constructor() { this.foo = 42 } print() { console.log(this.foo) } } const foo = new Foo() const fooPrint = new foo.print() // TypeError: foo.print is not a constructor ~~~ * 必須使用 new 調用 class。 ~~~js function Bar() { this.bar = 42 } const bar = Bar() // it's ok class Foo { constructor() { this.foo = 42 } } const foo = Foo() // TypeError: Class constructor Foo cannot be invoked without 'new' ~~~ * class 內部無法重寫類名。 ~~~js function Bar() { Bar = 'Baz' // it's ok this.bar = 42; } const bar = new Bar() // bar: Bar {bar: 42} class Foo { constructor() { this.foo = 42 Foo = 'Fol' // TypeError: Assignment to constant variable } } const foo = new Foo() Foo = 'Fol' // it's ok ~~~ ## 箭頭函數與普通函數(function)的區別是什么?構造函數(function)可以使用 new 生成實例,那么箭頭函數可以嗎~ 為什么~ * 箭頭函數體內的 this 對象,就是定義時所在的對象,而不是使用時所在的對象。 * 不可以使用 arguments 對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。 * 不可以使用 new 生成實例: * 沒有自己的 this,無法調用 call,apply。 * 沒有 prototype 屬性 ,而 new 命令在執行時需要將構造函數的 prototype 賦值給新的對象的**proto** * 不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數。 ~~~js // new的過程 var objectFactory = function() { // 從Object.prototype上克隆一個空對象 var obj = new Object() // 取得外部傳入的構造器,在此是Person var Constructor = [].shift.call( arguments ) // 指向正確的原型 obj.__proto__ = Constructor.prototype // 借用構造函數給obj設置屬性 var ret = Constructor.apply(obj, arguments) return typeof ret === 'object' ? ret : obj } ~~~ ## 使用 JavaScript Proxy 實現簡單的數據綁定 ~~~html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> hello,world <input type="text" id="model"> <p id="word"></p> <script> const model = document.getElementById('model') const word = document.getElementById('word') var obj= {} const newObj = new Proxy(obj, { get: function(target, key, receiver) { console.log(`getting ${key}!`) return Reflect.get(target, key, receiver) }, set: function(target, key, value, receiver) { console.log('setting',target, key, value, receiver) if (key === 'text') { model.value = value word.innerHTML = value } return Reflect.set(target, key, value, receiver) } }) model.addEventListener('keyup',function(e){ newObj.text = e.target.value }) </script> </body> </html> ~~~ ## 介紹模塊化發展歷程 > 可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、`<`script type="module"`>`這幾個角度考慮 **模塊化主要是用來抽離公共代碼,隔離作用域,避免變量沖突等。** `IIFE`: 使用自執行函數來編寫模塊化,特點:在一個單獨的函數作用域中執行代碼,避免變量沖突。 ~~~js (function(){ return { data:[] } })() ~~~ `AMD`: 使用requireJS 來編寫模塊化,特點:依賴必須提前聲明好。 ~~~js define('./index.js', function( code ){ // code 就是index.js 返回的內容 }) ~~~ `CMD`: 使用seaJS 來編寫模塊化,特點:支持動態引入依賴文件。 ~~~js define(function(require, exports, module) { var indexCode = require('./index.js') }) ~~~ `CommonJS`: nodejs 中自帶的模塊化。 ~~~js var fs = require('fs') ~~~ `UMD`:兼容AMD,CommonJS 模塊化語法。 webpack(require.ensure):webpack 2.x 版本中的代碼分割。 `ES Modules`: ES6 引入的模塊化,支持import 來引入另一個 js ~~~js import a from 'a' ~~~ ## 介紹下 Set、Map、WeakSet 和 WeakMap 的區別 *Set 是一種叫做`集合`的數據結構,Map 是一種叫做`字典`的數據結構* * 集合 是以`[value, value]`的形式儲存元素,字典 是以`[key, value]`的形式儲存 ~~~js var a = [1, 2, 3, 4] var b = { name: 'zhangsan', age: 15} ~~~ ### Set > ES6 提供了新的數據結構`Set`。它類似于數組,但是成員的值都是`唯一`的,沒有重復的值。`Set`本身是一個構造函數,用來生成`Set數據結構`。 * 基礎語法 * `new Set([iterable])` * 參數: iterable傳遞一個可迭代對象,它的所有元素將不重復地被添加到新的`Set`, 返回一個新的`Set`對象。 * 可迭代對象(需要遵守[可迭代協議](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols "https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols")) * 內置的[可迭代對象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%86%85%E7%BD%AE%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1 "https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%86%85%E7%BD%AE%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1")`String`、`Array`、`TypedArray`、`Map`和`Set`. * 實例的屬性 * `Set.prototype.constructor`: Set 的構造函數 * `Set.prototype.size`:返回 Set 實例的成員數量 ~~~js let set = new Set([1, 2, 3, 2, 1]) console.info(set.size) // 3 ~~~ * 實例的方法 * `add(value)`: 添加某個值,返回 Set 結構本身。 * `delete(value)`:刪除某個值,返回一個布爾值,表示刪除是否成功。 * `has(value)`: 返回一個布爾值,表示該值是否為Set的成員。 * `clear()`: 清除所有成員,沒有返回值。 * 遍歷的方法 * `keys()`:返回鍵名的遍歷器 * `values()`: 返回鍵值得遍歷器 * `entries()`: 返回鍵值對的遍歷器 * `forEach()`: 使用回調函數遍歷每個成員 ~~~js // Set的基礎操作 let set = new Set() set.add(1).add(2).add(3) set.has(1) // true set.has(3) // true set.delete(1) set.has(1) // false // 結合Array.from const items = new Set([1, 2, 3, 2, 1]) const array = Array.from(items) console.info(array) // [1, 2, 3] // 支持解構 const arr = [...set] console.info(arr) // [1, 2, 3] // 遍歷 let set = new Set([1, 2, 3]) console.log(set.keys()) // SetIterator {1, 2, 3} console.log(set.values()) // SetIterator {1, 2, 3} console.log(set.entries()) // SetIterator {1, 2, 3} for (let item of set.keys()) { console.log(item) // 1 2 3 } for (let item of set.entries()) { console.log(item) // [1, 1] [2, 2] [3, 3] } set.forEach((value, key) => { console.log(key + ' : ' + value) // 1 : 1 2 : 2 3 : 3 }) console.log([...set]) // [1, 2, 3] // Set 和容易實現 交集(Intersect)、并集(Union)、差集(Difference) let set1 = new Set([1, 2, 3]) let set2 = new Set([4, 3, 2]) // 交集 const intersect = [...set1].filter(item => set2.has(item)) // 并集 const union = new Set([...set1, ...set2]) // 差集 const difference = [...set1].filter(item => !set2.has(item)) console.info(intersect, union, difference) // [2, 3] Set{1, 2, 3, 4} [1] ~~~ ### Map > JavaScript 的對象(Object),本質上是鍵值對的集合(Hash 結構),但是傳統上只能用字符串當作鍵。這給它的使用帶來了很大的限制。 ~~~js const data = {} const element = document.getElementById('myDiv') data[element] = 'metadata' data['[object HTMLDivElement]'] // 'metadata' ~~~ 上面代碼原意是將一個 DOM 節點作為對象data的鍵,但是由于對象只接受字符串作為鍵名,所以element被自動轉為字符串`[object HTMLDivElement]`。 為了解決這個問題,ES6 提供了`Map`數據結構。它類似于`對象`,也是鍵值對的集合,但是`鍵`的范圍不限于字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object 結構提供了`字符串—值`的對應,`Map`結構提供了`值—值`的對應,是一種更完善的`Hash`結構實現。如果你需要`鍵值對`的數據結構,Map 比 Object 更合適。 **Map 的鍵實際上是跟`內存地址`綁定的,只要內存地址不一樣,就視為兩個鍵。** * 基礎語法 * `new Map([iterable])` * 參數: iterable接受一個數組作為參數,該數組的成員是一個個表示鍵值對的數組。 * 實例的屬性 * `Map.prototype.constructor`: Map 的構造函數 * `Map.prototype.size`:返回 Map 實例的成員數量 ~~~js const map = new Map([ ['name', 'An'], ['des', 'JS'] ]) console.info(map.size) // 2 ~~~ * 實例的方法 * `set(key, value)`: 設置Map對象中鍵的值。返回該Map對象。 * `get(key)`: 返回鍵對應的值,如果不存在,則返回undefined。 * `delete(value)`:如果 Map 對象中存在該元素,則移除它并返回 true;否則如果該元素不存在則返回 false。 * `has(key)`: 返回一個布爾值,表示Map實例是否包含鍵對應的值。 * `clear()`: 移除Map對象的所有鍵/值對, 沒有返回值。 * 遍歷的方法 * `keys()`:返回鍵名的遍歷器 * `values()`: 返回鍵值得遍歷器 * `entries()`: 返回鍵值對的遍歷器 * `forEach()`: 使用回調函數遍歷每個成員 ### WeakSet WeakSet 對象允許你將弱引用對象儲存在一個集合中 與`Set`的區別 * WeakSet 只能儲存對象引用,不能存放值,而 Set 對象都可以 * WeakSet 對象中儲存的對象值都是被弱引用的,即垃圾回收機制不考慮 WeakSet 對該對象的應用,如果沒有其他的變量或屬性引用這個對象值,則這個對象將會被垃圾回收掉(不考慮該對象還存在于 WeakSet 中),所以,WeakSet 對象里有多少個成員元素,取決于垃圾回收機制有沒有運行,運行前后成員個數可能不一致,遍歷結束之后,有的成員可能取不到了(被垃圾回收了),WeakSet 對象是無法被遍歷的(ES6 規定 WeakSet 不可遍歷),也沒有辦法拿到它包含的所有元素 * 基礎語法 * `new WeakSet([iterable])` * 參數: iterable傳遞一個可迭代對象,它的所有元素將不重復地被添加到新的`WeakSet`, 返回一個新的`WeakSet`對象。 * 實例的屬性 * `Set.prototype.constructor`: Set 的構造函數 * 實例的方法 * `add(value)`: 添加某個值,返回 Set 結構本身。 * `delete(value)`:刪除某個值,返回一個布爾值,表示刪除是否成功。 * `has(value)`: 返回一個布爾值,表示該值是否為Set的成員。 * 弱引用 > JavaScript 語言中,內存的回收并不是斷開引用后即時觸發的,而是根據運行環境的不同、在不同的運行環境下根據不同瀏覽器的回收機制而異的。比如在 Chrome 中,我們可以在控制臺里點擊 CollectGarbage 按鈕來進行內存回收 ~~~js var test = { name : 'test', content : { name : 'content', will : 'be clean' } }; var ws = new WeakSet() ws.add(test.content) console.log('清理前', ws) test.content = null console.log('清理后', ws) ~~~ ### WeakMap WeakMap 對象是一組鍵值對的集合,其中的鍵是弱引用對象,而值可以是任意。 WeakMap 中,每個鍵對自己所引用對象的引用都是弱引用,在沒有其他引用和該鍵引用同一對象,這個對象將會被垃圾回收(相應的key則變成無效的),所以,WeakMap 的 key 是不可枚舉的。 * 基礎語法 * `new WeakMap([iterable])` * 參數: iterable接受一個數組作為參數,該數組的成員是一個個表示鍵值對的數組。 * 實例的屬性 * `Map.prototype.constructor`: Map 的構造函數 * 實例的方法 * `set(key, value)`: 設置Map對象中鍵的值。返回該Map對象。 * `get(key)`: 返回鍵對應的值,如果不存在,則返回undefined。 * `delete(value)`:如果 Map 對象中存在該元素,則移除它并返回 true;否則如果該元素不存在則返回 false。 * `has(key)`: 返回一個布爾值,表示Map實例是否包含鍵對應的值。 ~~~js let myElement = document.getElementById('logo') let myWeakmap = new WeakMap() myWeakmap.set(myElement, {timesClicked: 0}) myElement.addEventListener('click', function() { let logoData = myWeakmap.get(myElement) logoData.timesClicked++ }, false) ~~~ > 上面代碼中,myElement是一個 DOM 節點,每當發生click事件,就更新一下狀態。我們將這個狀態作為鍵值放在 WeakMap 里,對應的鍵名就是myElement。一旦這個 DOM 節點刪除,該狀態就會自動消失,不存在內存泄漏風險。 ### 總結 * Set * 成員唯一、無序且不重復 * \[value, value\],鍵值與鍵名是一致的(或者說只有鍵值,沒有鍵名) * 可以遍歷,方法有:add、delete、has * WeakSet * 成員都是對象 * 成員都是弱引用,可以被垃圾回收機制回收,可以用來保存DOM節點,不容易造成內存泄漏 * 不能遍歷,方法有add、delete、has * Map * 本質上是鍵值對的集合,類似集合 * 可以遍歷,方法很多可以跟各種數據格式轉換 WeakMap * 只接受對象作為鍵名(null除外),不接受其他類型的值作為鍵名 * 鍵名是弱引用,鍵值可以是任意的,鍵名所指向的對象可以被垃圾回收,此時鍵名是無效的 * 不能遍歷,方法有get、set、has、delete * Set 與 WeakSet 的區別 * WeakSet只能存放對象 * WeakSet不支持遍歷, 沒有size屬性 * WeakSet存放的對象不會計入到對象的引用技術, 因此不會影響GC的回收 * WeakSet存在的對象如果在外界消失了, 那么在WeakSet里面也會不存在 * Map 與 WeakMap 的區別 * WeakMap只能接受對象作為鍵名字(null除外),不接受其他類型的值作為鍵名 * WeakMap不支持遍歷, 沒有size屬 * WeakMap鍵名指向對象不會計入到對象的引用技術, 因此不會影響GC的回收 ## 垃圾回收機制文章 [JavaScript垃圾回收機制](https://www.jianshu.com/p/c99dd69a8f2c "https://www.jianshu.com/p/c99dd69a8f2c")[JavaScript 內存泄漏教程](http://www.ruanyifeng.com/blog/2017/04/memory-leak.html "http://www.ruanyifeng.com/blog/2017/04/memory-leak.html") ## 認識一下遍歷器 > 存在的意義 JavaScript 原有的表示`集合`的數據結構,主要是數組(`Array`)和對象(`Object`),ES6 又添加了`Map`和`Set`。這樣就有了四種數據集合,用戶還可以組合使用它們,定義自己的數據結構,比如數組的成員是Map,Map的成員是對象。這樣就需要一種`統一`的接口機制,來處理所有不同的數據結構。 遍歷器(Iterator)就是這樣一種機制。它是一種接口,為各種不同的數據結構提供統一的訪問機制。任何數據結構只要部署`Iterator`接口,就可以完成遍歷操作(即依次處理該數據結構的所有成員)。 > 作用 * 一是為各種數據結構,提供一個統一的、簡便的訪問接口 * 二是使得數據結構的成員能夠按某種次序排列; * 三是 ES6 創造了一種新的遍歷命令`for...of`循環,Iterator 接口主要供`for...of`使用。 > 過程或使用 * 創建一個指針對象,指向當前數據結構的起始位置。(也就是說,遍歷器對象本質上,就是一個指針對象) * 第一次調用指針對象的next方法,可以將指針指向數據結構的第一個成員。 * 第二次調用指針對象的next方法,指針就指向數據結構的第二個成員。 * 不斷調用指針對象的next方法,直到它指向數據結構的結束位置。 > 每一次調用next方法,都會返回數據結構的當前成員的信息。具體來說,就是返回一個包含value和done兩個屬性的對象。其中,value屬性是當前成員的值,done屬性是一個布爾值,表示遍歷是否結束。 ~~~js var it = makeIterator(['a', 'b']); it.next() // { value: "a", done: false } it.next() // { value: "b", done: false } it.next() // { value: undefined, done: true } function makeIterator(array) { var nextIndex = 0 return { next() { return nextIndex < array.length ? {value: array[nextIndex++], done: false} : {value: undefined, done: true} } } } ~~~ 對于遍歷器對象來說,`done: false`和`value: undefined`屬性都是可以省略的。 一種數據結構只要部署了`Iterator`接口,我們就稱這種數據結構是`可遍歷的`(iterable)。 ES6 規定,默認的`Iterator`接口部署在數據結構的`Symbol.iterator`屬性,或者說,一個數據結構只要具有`Symbol.iterator`屬性,就可以認為是`可遍歷的`(iterable)。`Symbol.iterator`屬性本身是一個函數,就是當前數據結構默認的遍歷器生成函數。執行這個函數,就會返回一個遍歷器。 內置的[可迭代對象](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%86%85%E7%BD%AE%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1 "https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Iteration_protocols#%E5%86%85%E7%BD%AE%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1") * `String` * `Array` * `TypedArray` * `Map` * `Set` ~~~js let arr = ['a', 'b', 'c'] let iter = arr[Symbol.iterator]() iter.next() // { value: 'a', done: false } iter.next() // { value: 'b', done: false } iter.next() // { value: 'c', done: false } iter.next() // { value: undefined, done: true } ~~~ 對于原生部署`Iterator`接口的數據結構,不用自己寫遍歷器生成函數,`for...of`循環會自動遍歷它們。除此之外,其他數據結構(主要是對象)的`Iterator`接口,都需要自己在`Symbol.iterator`屬性上面部署,這樣才會被`for...of`循環遍歷。 對象(Object)之所以沒有默認部署 Iterator 接口,是因為對象的哪個屬性先遍歷,哪個屬性后遍歷是不確定的,需要開發者手動指定。本質上,遍歷器是一種線性處理,對于任何非線性的數據結構,部署遍歷器接口,就等于部署一種線性轉換。不過,嚴格地說,對象部署遍歷器接口并不是很必要,因為這時對象實際上被當作 Map 結構使用,ES5 沒有 Map 結構,而 ES6 原生提供了。 ~~~js class objIterator { constructor(obj) { this.values = Object.keys(obj).map(key => obj[key]) this.length = this.values.length } [Symbol.iterator]() { let index = 0 return { next: () => { console.info(index, this.length) return { value: this.values[index++], done: index > this.length } } } } } let newObj = new objIterator({ id: 12, name: '張三'}) for(let item of newObj) { console.info(item) } ~~~ 重寫數組的`Symbol.iterator`的方法 ~~~js let arr = ['zhangsan', 12, 'hello'] arr[Symbol.iterator] = function() { let index = 0 return { next: () => { if (index < this.length) { let value = this[index] if (typeof value == 'string') { value = value + '加點啥' } index++ return { value, done: false } } return { done: true, value: '就要輸出值'} } } } for(let a of arr) { console.info(a) } ~~~ 一個類似數組的對象調用數組的`Symbol.iterator`方法的例子。 ~~~js let iterable = { 0: 'a', 1: 'b', 2: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] } for (let item of iterable) { console.log(item) // 'a', 'b', 'c' } ~~~ 普通對象部署數組的`Symbol.iterator`方法,并無效果。 ~~~js let iterable = { a: 'a', b: 'b', c: 'c', length: 3, [Symbol.iterator]: Array.prototype[Symbol.iterator] } for (let item of iterable) { console.log(item) // undefined, undefined, undefined } ~~~ javaScript 原有的`for...in`循環,只能獲得對象的鍵名,不能直接獲取鍵值。ES6 提供`for...of`循環,允許遍歷獲得鍵值。 ~~~js // array var arr = ['a', 'b', 'c', 'd'] for (let a in arr) { console.log(a) // 0 1 2 3 } for (let a of arr) { console.log(a) // a b c d } // string var str = 'abc' for (let a in str) { console.log(a) // 0 1 2 } for (let a of str) { console.log(a) // a b c } ~~~ ## JS 異步解決方案的發展歷程以及優缺點 - 滴滴、挖財、微醫、海康 **異步編程的語法目標,就是怎樣讓它更像同步編程。** ### 為什么JavaScript是`單線程` JavaScript語言的一大特點就是單線程,也就是說,同一個時間只能做一件事。那么,為什么JavaScript不能有多個線程呢?這樣能提高效率啊。 JavaScript的單線程,與它的用途有關。作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。 > 假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程為準? 所以,為了避免復雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特征,將來也不會改變。 為了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以,這個新標準并沒有改變JavaScript單線程的本質。 ### 什么是`異步` 所謂`異步`,簡單說就是一個任務分成兩段,先執行第一段,然后轉而執行其他任務,等做好了準備,再回過頭執行第二段。比如,有一個任務是讀取文件進行處理,異步的執行過程就是下面這樣。 ![異步](vscode-webview-resource://6f0f06f2-19fc-435d-90f8-d810823e6ed0/file///Users/magicdata/Documents/share/ES/img/%E5%BC%82%E6%AD%A5.png)上圖中,任務的第一段是向操作系統發出請求,要求讀取文件。然后,程序執行其他任務,等到操作系統返回文件,再接著執行任務的第二段(處理文件)。 **這種不連續的執行,就叫做異步**。相應地,連續的執行,就叫做同步。 ![同步](vscode-webview-resource://6f0f06f2-19fc-435d-90f8-d810823e6ed0/file///Users/magicdata/Documents/share/ES/img/%E5%90%8C%E6%AD%A5.png)上圖就是同步的執行方式。由于是連續執行,不能插入其他任務,所以操作系統從硬盤讀取文件的這段時間,程序只能干等著。 ### `回調函數` JavaScript 語言對異步編程的實現,就是回調函數。所謂回調函數,就是把任務的第二段單獨寫在一個函數里面,等到重新執行這個任務的時候,就直接調用這個函數。它的英語名字 callback,直譯過來就是"重新調用"。 ~~~js setTimeout(() => { // callback 函數體 }, 1000) ~~~ ### `Promise` 回調函數本身并沒有問題,它的問題出現在多個回調函數嵌套。不難想象,如果依次讀取多個文件,就會出現多重嵌套。這種情況就稱為[回調函數噩夢](http://callbackhell.com/ "http://callbackhell.com/")(callback hell)。 ~~~js ajax('XXX1', () => { // callback 函數體 ajax('XXX2', () => { // callback 函數體 ajax('XXX3', () => { // callback 函數體 ... }) }) }) ~~~ `Promise`就是為了解決這個問題而提出的。它不是新的語法功能,而是一種新的寫法。 ~~~js ajax('XXX1'). then(res => { // 操作邏輯 return ajax('XXX2') }) .then(res => { // 操作邏輯 return ajax('XXX3') }) .then(res => { // 操作邏輯 }) .catch(function(error) { // 處理錯誤 }) ~~~ Promise 實現了`鏈式`調用,也就是說每次`then`后返回的都是一個全新`Promise`,如果我們在`then`中`return`,`return`的結果會被`Promise.resolve()`包裝。 `Promise`的最大問題是代碼冗余,原來的任務被`Promise`包裝了一下,不管什么操作,一眼看去都是一堆`then`,原來的語義變得很不清楚。 `變相中止`Promise 與[取消狀態](https://github.com/tc39/proposal-cancelable-promises/issues/70 "https://github.com/tc39/proposal-cancelable-promises/issues/70") ~~~js Promise.resolve().then(function() { return new Promise(function() {}) }) ~~~ > 跟傳統的`try/catch`代碼塊不同的是,如果沒有使用`catch()`方法指定錯誤處理的回調函數,`Promise`對象拋出的錯誤不會傳遞到外層代碼,即不會有任何反應。 ~~~js // normal function someAsyncThing() { console.info(x + 2) } someAsyncThing() setTimeout(() => { console.log(123) }, 2000) // Uncaught (in promise) ReferenceError: x is not defined // 中止運行 // promise const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行會報錯,因為x沒有聲明 resolve(x + 2) }) } someAsyncThing().then(function() { console.log('everything is great') }) setTimeout(() => { console.log(123) }, 2000) // Uncaught (in promise) ReferenceError: x is not defined // 123 ~~~ 上面代碼中,`someAsyncThing()`函數產生的`Promise`對象,內部有語法錯誤。瀏覽器運行到這一行,會打印出錯誤提示`ReferenceError: x is not defined`,但是不會退出進程、終止腳本執行,`2`秒之后還是會輸出`123`。這就是說,Promise 內部的錯誤不會影響到 Promise 外部的代碼,通俗的說法就是`Promise 會吃掉錯誤`。 ### `Generator` `Generator`函數是協程在 ES6 的實現,最大特點就是`可以交出函數的執行權`(即**暫停執行**)。 ~~~js function fetch(url, fn) { return fn() } function *action() { yield fetch('XXX1', () => { return 'zhangsan' }) yield fetch('XXX2', () => { return 'lisi' }) yield fetch('XXX3', () => { return 'wangwu' }) } // 非鏈式 let it = action() let result1 = it.next() // zhangsan let result2 = it.next() // lisi let result3 = it.next() // wangwu // 鏈式 var g = action() var result1 = g.next() result1.value.then(function(data){ return data }) .then(function(data){ return g.next(data).value }) .then(function(data){ return data.json() }) ~~~ * 它不同于普通函數,是可以暫停執行的,所以函數名之前要加星號,以示區別。整個`Generator`函數就是一個封裝的異步任務,或者說是異步任務的容器。異步操作需要暫停的地方,都用`yield`語句注明。 * `Generator`函數不同于普通函數的另一個地方是調用`Generator`函數,會返回一個內部指針(即遍歷器 )`it`,即執行它不會返回結果,返回的是指針對象。調用指針`it`的 next 方法,會移動內部指針。 > `next`方法的作用是分階段執行`Generator`函數。每次調用`next`方法,會返回一個對象,表示當前階段的信息(`value`屬性和`done`屬性)。`value`屬性是`yield`語句后面表達式的值,表示當前階段的值;`done`屬性是一個布爾值,表示`Generator`函數是否執行完畢,即是否還有下一個階段。 **雖然`Generator`函數將異步操作表示得很簡潔,但是流程管理卻不方便(即何時執行第一階段、何時執行第二階段)** ~~~js function* gen(){ var url = 'https://api.github.com/users/github' var result = yield fetch(url) console.log(result.bio) } // 執行 var g = gen() var result = g.next() result.value.then(function(data){ return data.json() }).then(function(data){ g.next(data) }) ~~~ `簡易自執行函數` ~~~js function run(gen){ var g = gen() function next(data){ var result = g.next(data) if (result.done) { return result.value } // 使用then執行next,把上一個結果data傳入 result.value.then(function(data){ next(data) }) } // 執行next next() } // 自動運行gen函數 run(gen) ~~~ ### `async/await` Generator 函數,依次讀取兩個文件 ~~~js const gen = function* () { const f1 = yield readFile('/etc/fstab') const f2 = yield readFile('/etc/shells') console.log(f1.toString()) console.log(f2.toString()) } ~~~ 上面代碼的函數`gen`可以寫成`async`函數,就是下面這樣。 ~~~js const asyncReadFile = async function () { const f1 = await readFile('/etc/fstab') const f2 = await readFile('/etc/shells') console.log(f1.toString()) console.log(f2.toString()) } ~~~ 一比較就會發現,async函數就是將`Generator`函數的星號(`*`)替換成`async`,將`yield`替換成`await`。 * `async`函數自帶執行器。`async`函數的執行,與普通函數一模一樣,只要一行。不像 Generator 函數,需要調用next方法,才能真正執行,得到最后結果。 * `async`和`await`,比起`星號`和`yield`,語義更清楚了。`async`表示函數里有異步操作,`await`表示緊跟在后面的表達式需要等待結果。 * 返回值是`Promise`,這比`Generator`函數的返回值是`Iterator`對象方便多了。你可以用`then`方法指定下一步的操作。 **`async`函數的實現原理,就是將`Generator`函數和自動執行器,包裝在一個函數里。** ### 與其他異步處理方法的比較 我們通過一個例子,來看`async`函數與`Promise`、`Generator`函數的比較。 **假定某個`DOM`元素上面,部署了一系列的動畫,前一個動畫結束,才能開始后一個。如果當中有一個動畫出錯,就不再往下執行,返回上一個成功執行的動畫的返回值。** 首先是`Promise`的寫法。 ~~~js function chainAnimationsPromise(elem, animations) { // 變量ret用來保存上一個動畫的返回值 let ret = null // 新建一個空的Promise let p = Promise.resolve() // 使用then方法,添加所有動畫 for(let anim of animations) { p = p.then(function(val) { ret = val return anim(elem) }) } // 返回一個部署了錯誤捕捉機制的Promise return p.catch(function(e) { /* 忽略錯誤,繼續執行 */ }).then(function() { return ret }) } ~~~ 雖然`Promise`的寫法比回調函數的寫法大大改進,但是一眼看上去,代碼完全都是`Promise`的`API`(`then`、`catch`等等),操作本身的語義反而不容易看出來。 接著是`Generator`函數的寫法。 ~~~js function chainAnimationsGenerator(elem, animations) { return spawn(function*() { let ret = null try { for(let anim of animations) { ret = yield anim(elem) } } catch(e) { /* 忽略錯誤,繼續執行 */ } return ret }) } ~~~ 自執行函數`spawn` ~~~js function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF() function step(nextF) { let next try { next = nextF() } catch(e) { return reject(e) } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v) }) }, function(e) { step(function() { return gen.throw(e) }) }) } step(function() { return gen.next(undefined) }) }) } ~~~ 上面代碼使用`Generator`函數遍歷了每個動畫,語義比`Promise`寫法更清晰,用戶定義的操作全部都出現在spawn函數的內部。這個寫法的問題在于,必須有一個任務運行器,自動執行`Generator`函數,上面代碼的`spawn`函數就是自動執行器,它返回一個`Promise`對象,而且必須保證`yield`語句后面的表達式,必須返回一個`Promise`。 最后是`async`函數的寫法。 ~~~js async function chainAnimationsAsync(elem, animations) { let ret = null try { for(let anim of animations) { ret = await anim(elem) } } catch(e) { /* 忽略錯誤,繼續執行 */ } return ret } ~~~ 可以看到`Async`函數的實現最簡潔,最符合語義,幾乎沒有語義不相關的代碼。它將`Generator`寫法中的自動執行器,改在語言層面提供,不暴露給用戶,因此代碼量最少。如果使用`Generator`寫法,自動執行器需要用戶自己提供。 ### 異步總結 * `回調函數` * 優點 * **解決了同步的問題**(只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。) * 缺點(**回調地獄**) * `缺乏順序性`:回調地獄導致的調試困難。 * 嵌套函數存在耦合性,一旦有所改動,就會牽一發而動全身,即(`控制反轉`)。 * 嵌套函數過多的多話,很難處理錯誤。 * `Promise` * 特點 * 對象的狀態不受外界影響。 * 一旦狀態改變,就不會再變,任何時候都可以得到這個結果。 * 優點 * 解決了`回調地獄`的問題 * 回調函數變成了鏈式寫法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以實現許多強大的功能。 * 缺點 * 無法取消Promise,一旦新建它就會立即執行,無法中途取消。 * 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。 * 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成,要在用戶界面展示進度條)。 * `Generator` * 特點 * 可以交出函數的執行權(即暫停執行) * 優點 * 將異步操作表示得很簡潔 * 缺點 * 流程管理卻不方便 * 需要手動執行next * `async/await` * 優點 * 代碼清晰,語義化更強,不用像`Promise`寫一大堆`then`鏈 * 返回值是`Promise` * `async`函數自帶執行器 * 缺點 * await 將異步代碼改造成同步代碼,如果多個異步操作沒有依賴性而使用`await`會導致性能上的降低。 ### 引用 [Javascript異步編程的4種方法](http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html "http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html") [再談Event Loop](http://www.ruanyifeng.com/blog/2014/10/event-loop.html "http://www.ruanyifeng.com/blog/2014/10/event-loop.html") [Generator 函數的含義與用法](http://www.ruanyifeng.com/blog/2015/04/generator.html "http://www.ruanyifeng.com/blog/2015/04/generator.html") [Generator](https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112 "https://www.liaoxuefeng.com/wiki/1022910821149312/1023024381818112") [Generator-ES6入門](https://es6.ruanyifeng.com/#docs/generator "https://es6.ruanyifeng.com/#docs/generator") [深入理解 Generators](http://www.alloyteam.com/2016/02/generators-in-depth/ "http://www.alloyteam.com/2016/02/generators-in-depth/") [JS 異步解決方案的發展歷程以及優缺點](https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/11 "https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/11") [co函數庫](http://www.ruanyifeng.com/blog/2015/05/co.html "http://www.ruanyifeng.com/blog/2015/05/co.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>

                              哎呀哎呀视频在线观看