<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                [TOC] ## JavaScript有哪些數據類型,他們的區別是什么? * js中8中數據類型:Undefined、Null、Boolean、Number、String、Object(Array)、Symbol(ES6)、BigInt(ES6)。 * 原始數據類型(存于棧中):Undefined、Null、Boolean、Number、String,也稱基本數據類型。 * 引用類型數據(存于堆中):Object(Array、Function)。 * null和undefined區別: undefined表示未定義,一般聲明變量未賦值會返回undefined。 null代表的含義是空對象,主要用于賦值給一些可能返回對象的變量,作為初始化。 * ts類型:Boolean、Number、String、Array、Object、元數組、枚舉、Any、Void、Null、Undefined、Never。(可能會問ts比es6中多哪些類型) **棧和堆的區別** * 存取方式:棧是先進后出,堆是一個優先隊列,是按照優先級進行排序的,優先級可以按照大小來規定。 * 操作系統中:棧區是有編譯器自動分配的,存放函數的參數值、局部變量等;堆區一般是由開發者分配釋放的,若開發者不釋放,程序結束時可能由垃圾回收機制回收(垃圾回收機制原理:通過計數為0或者標記進行回收)。 **后面兩種是ES6新增的數據類型:** * Symbol代表創建后獨一無二且不可變的數據類型,主要為了解決出現全局變量沖突問題。 * BigInt是一種數字類型的數據,他可以表示任意精度格式的整數,可以安全存儲和操作大整數,可超出Number能夠表示的安全整數范圍。 ## 數據類型檢測方式有哪些 * typeof:數組、對象、null都會判斷為object,其他判斷都正確。 * instanceof:可以判斷對象類型(引用類型數據),內部原理是在其原型鏈中是否能找到該類型的原型。 * constructor:如 (2).constructor === Number // true,constructor有兩個作用,意識判斷數據類型,二是對象實例通過constructor對象訪問它的構造函數,需要注意的是,這里用來創建一個對象來改變它的原型,就不能用來判斷類型了。 * Object.prototype.toString.call():一般用于封裝一個工具類函數,用于判斷數據類型。 * 其他判斷數組的方式:Array.isArray()、Array.prototype.isPrototypeOf(obj) ## ES6相關知識 **變量提升原因和存在問題** * 提升原因:提高性能(js代碼執行之前,會進行語法檢查和預編譯,并且這操作只進行一次每次執行代碼就不用再次解析一遍,因為變量和函數的代碼不會隨著執行而改變)和容錯性更好(在一定程度上提供容錯性,比如變量可以先試用后定義)。 * 存在問題,如下: ``` var tmp = 'vvmily'; function fn(){ console.log(tmp); if(false){ var tmp = 'hello world'; } } fn(); // undefined // 看到這里,在ES6中出現let和const,解決ES5之前的兩個問題,具體請往下看。 ``` **let const var區別** * 塊級作用域:塊級作用域有{ }包括著,let和const具有塊級作用域,var不存在塊級作用域。 * 塊級作用域解決ES5的兩個問題: 1. 內層變量可能覆蓋外層變量; 2. 用來計數的循環變量泄露為全局變量。 * var:存在變量提升;定義變量為全局變量;可以重復聲明變量; * let/const:不存在變量提升,變量只能先定義在使用,否則報錯;不會給全局定義變量;let和const都是ES6新增的用于創建變量的語法,let創建的變量是可以更改指針指向(可以重新賦值);但const聲明的變量是不允許改變指針的指向。 ``` const Len = 3; // Len已經不可以改變 const Obj = { name: 'vvmily' }; // Obj.name是可以改變的 ``` * const:const定義變量必須設置初始值。 **箭頭函數相比于普通函數** * 箭頭函數:沒有自己的this,繼承上一層作用域的this,所以箭頭函數中的this的指向在它定義時已經確定了,之后不會再改變。 * 不能作為構造函數使用、也沒有prototype、沒有argument、不能作用Generator函數不能使用yeild關鍵字。 * apply、bind和call都不能改變箭頭函數中的this。 **擴展運算符(...)作用和使用場景** * 對 對象/數組 解構賦值。 * 將字符串轉化為真正的數組:`[...'hello'] // [ "h", "e", "l", "l", "o" ]`。 * 將Iterator接口對象等,如arguments轉真正的對象`const args = [...arguments]`。 * 其他:`const numbers = [2,1,5,3]; Math.min(...numbers); // 1`。 **map和weakMap的區別** * **map**:本質上就是鍵值對的集合,但是普通的Object的鍵只能是字符串,但是map的鍵不限制范圍,可以任意類型。 * Map數據結構有以下操作方法: 1. **size**: `map.size` 返回Map結構的成員總數。 2. **set(key,value)**:設置鍵名key對應的鍵值value,然后返回整個Map結構,如果key已經有值,則鍵值會被更新,否則就新生成該鍵。(因為返回的是當前Map對象,所以可以鏈式調用) 3. **get(key)**:該方法讀取key對應的鍵值,如果找不到key,返回undefined。 4. **has(key)**:該方法返回一個布爾值,表示某個鍵是否在當前Map對象中。 5. **delete(key)**:該方法刪除某個鍵,返回true,如果刪除失敗,返回false。 6. **clear()**:map.clear()清除所有成員,沒有返回值。 * Map結構原生提供是三個遍歷器生成函數和一個遍歷方法: 1. keys():返回鍵名的遍歷器。 2. values():返回鍵值的遍歷器。 3. entries():返回所有成員的遍歷器。 4. forEach():遍歷Map的所有成員。 * **weakMap**:對象也是一組鍵值對集合,其中的鍵是弱引用的,鍵必須是對象(null除外),基礎數據(String)不能作為鍵。 * 該對象也有以下幾種方法: 1. **set(key,value)**:設置鍵名key對應的鍵值value,然后返回整個Map結構,如果key已經有值,則鍵值會被更新,否則就新生成該鍵。(因為返回的是當前Map對象,所以可以鏈式調用) 2. **get(key)**:該方法讀取key對應的鍵值,如果找不到key,返回undefined。 3. **has(key)**:該方法返回一個布爾值,表示某個鍵是否在當前Map對象中。 4. **delete(key)**:該方法刪除某個鍵,返回true,如果刪除失敗,返回false。 **ES6模塊ES6Module與CommonJS模塊有什么區別** 相同點:都可以對引入的對象進行賦值(對 對象內部屬性的值進行改變)。 不同點:CommonJS對模塊的淺拷貝,可以對CommonJS對象重新賦值(可以修改指針)。 ES6Module對模塊的引用(只存只讀),不能改變其值,或者說指針不能變,類似const定義引用類型。 ## JavaScript基礎 **new操作符的原理** * 創建的過程: 1. 創建一個新的對象; 2. 設置原型,將函數的prototype對象賦給這個新對象的原型; 3. 讓函數的this指向這個新對象,執行構造函數代碼(給新對象添加屬性); 4. 判斷函數返回值的類型,如果是值類型,則返回創建的新對象,如果是引用類型則返回這個引用類型的對象。 * 大致實現代碼: ``` js function MyNew(){ var obj = {}, // 截取類數組arguments第一個參數并返回,其實就是構造函數 // 注意:shift會改變原數組,故改變指向使用apply對應第二個參數為去除構造函數剩余的數組arguments Constructor = [].shift.call(arguments) // 構造函數原型賦值給obj對象 obj.__proto__ = Constructor.prototype // 改變構造函數指向,指向obj對象 Constructor.apply(obj, arguments) // 第三步,這里返回值可以根據第四步說明判斷相繼完善即可 return obj } MyNew(fn,params) //使用方法 ``` **JavaScript類數組對象** * 一個擁有length屬性和索引屬性的對象可以被稱之為類數組對象(函數也算一個,因為函數也有length屬性值),但是不能調用數組的方法。 * 類數組轉真正的數組方法: 1. Array.prototype.slice.call(arrayLike) // arrayLike類數組 2. Array.prototype.splice.call(arrayLike,0) 3. Array.prototype.cancat.apply([],arrayLike) 4. Array.from(arrayLike) 5. [...arrayLike] **escape、encodeURI、encodeURIComponent 的區別** * encodeURI 是對整個 URI 進行轉義,將 URI 中的非法字符轉換為合法字符,所以對于一些在 URI 中有特殊意義的字符不會進行轉義。 * encodeURIComponent 是對 URI 的組成部分進行轉義,所以一些特殊字符也會得到轉義。 * escape 和 encodeURI 的作用相同,不過它們對于 unicode 編碼為 0xff 之外字符的時候會有區別,escape 是直接在字符的 unicode 編碼前加上 %u,而 encodeURI 首先會將字符轉換為 UTF-8 的格式,再在每個字節前加上 %。 **對AJAX的理解,實現一個AJAX請求** * AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。 * 創建AJAX請求步驟: 1. 創建一個XMLHttpRequest對象 2. 在這個對象上使用open方法創建一個http請求,open方法參數是 請求的方法、請求的地址、是否異步和用戶認證的信息。 3. 發起請求前,可以為這個對象添加一些信息和監聽函數,比如通過setRequestHeader為請求頭設置信息,通過這個對象的onreadystatechange的readyState變為4,表示服務器返回數據接收完成,狀態為200/304等代表正常。 4. 當對象屬性和監聽方法設置完成后,最后調用send方法向服務器發起請求,可以傳入參數作為發送數據體。 ``` const SERVER_URL = "/base"; let xhr = new XMLHttpRequest(); // 創建 Http 請求 xhr.open("GET", url, true); // 設置狀態監聽函數 xhr.onreadystatechange = function() { if (this.readyState !== 4) return; // 當請求成功時 if (this.status === 200) { handle(this.response); } else { console.error(this.statusText); } }; // 設置請求失敗時的監聽函數 xhr.onerror = function() { console.error(this.statusText); }; // 設置請求頭信息 xhr.responseType = "json"; xhr.setRequestHeader("Accept", "application/json"); // 發送 Http 請求 xhr.send(null); ``` **for...in和for...of區別** * for...of是ES6新增的遍歷方式,允許遍歷一個含有iterator接口的數據結構(數組、對象等),并且返回的各項的值。 * 兩者區別: 1. for…of 遍歷獲取的是對象的鍵值,for…in 獲取的是對象的鍵名; 2. for… in 會遍歷對象的整個原型鏈,性能非常差不推薦使用,而 for … of 只遍歷當前對象不會遍歷原型鏈; 3. 對于數組的遍歷,for…in 會返回數組中所有可枚舉的屬性(包括原型鏈上可枚舉的屬性),for…of 只返回數組的下標對應的屬性值; * 總結:for...in 循環主要是為了遍歷對象而生,不適用于遍歷數組;for...of 循環可以用來遍歷數組、類數組對象,字符串、Set、Map 以及 Generator 對象。 **ajax、axios、fetch的區別** * ajax即AsynchronousJavascriptAndXML(異步JavaScript和XML),是指一種創建交互式網頁應用的網頁開發技術。它是一種無需在加載整個網頁的情況下,能夠更新部分網頁的技術。通過在后臺與服務器進行少量的數據交換,Ajax實現網頁異步更新。 * fetch在ES6出現,號稱是Ajax替代品,使用了ES6中promise對象,或者說基于promise設計的。fetch不是Ajax的進一步封裝,而是原生JavaScript,沒有使用XMLHttpRequest對象。 * fetch優點: 1. 語法簡潔,更加語義化 2. 基于標準的promise對象實現支持async/await 3. 更加底層,提供的API更豐富(request、response) 4. 脫離了XML,是ES規范里新的實現方式 * fetch缺點: 1. 只針對網絡請求報錯,對400、500并不會reject,而是當作成功請求,反之當網絡錯誤不能完成請求,才會被reject。 2. 默認不會帶Cooke,需要添加配置項: fetch(url, {credentials: 'include'}) 3. 不支持abort,不支持超時監控,使用setTimeout及Promise.reject的實現的超時控制并不能阻止請求過程繼續在后臺運行,造成了流量的浪費 4. fetch沒有辦法原生監測請求的進度,而XHR可以 * axios:是一種基于Promise封裝的HTTP客戶端,其特點如下: 1. 瀏覽器端發起XMLHttpRequest請求、node端發起HTTP請求 2. 支持Promise API 3. 監聽請求和返回,對請求和返回進行轉化 4. 取消請求 5. 自動轉換json數據 6. 客戶端支持抵御XSRF攻擊 **forEach和map方法有什么區別** 這方法都是用來遍歷數組的,兩者區別如下: * forEach()方法會針對每一個元素執行提供的函數,對數據的操作會改變原數組,該方法沒有返回值。 * map()方法不會改變原數組的值,返回一個新數組,新數組中的值為原數組調用函數處理之后的值。 ## 原型與原型鏈 **對原型和原型鏈理解** * 在JavaScript中是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性,它的屬性值是一個對象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當使用構造函數新建一個對象后,在這個對象的內部將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 __proto__ 屬性來訪問這個屬性,但是最好不要使用這個屬性,因為它不是規范中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,可以通過這個方法來獲取對象的原型。 * 原型鏈:當訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就是新建的對象為什么能夠使用 toString() 等方法的原因。 * 特點:JavaScript 對象是通過引用來傳遞的,創建的每個新對象實體中并沒有一份屬于自己的原型副本。當修改原型時,與之相關的對象也會繼承這一改變。 ![](https://img.kancloud.cn/ee/40/ee40304a3ee611a22dfb3593de1b6a97_618x781.png) **原型修改、重寫** ``` function Person(name) { this.name = name } // 修改原型 Person.prototype.getName = function() {} var p = new Person('hello') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // true // 重寫原型 Person.prototype = { getName: function() {} } var p = new Person('hello') console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // false ``` 可以看到修改原型的時候p的構造函數不是指向Person了,因為直接給Person的原型對象直接用對象賦值時,它的構造函數指向的了根構造函數Object,所以這時候`p.constructor === Object` ,而不是`p.constructor === Person`。要想成立,就要用constructor指回來: ``` Person.prototype = { getName: function() {} } var p = new Person('hello') p.constructor = Person console.log(p.__proto__ === Person.prototype) // true console.log(p.__proto__ === p.constructor.prototype) // true ``` **原型鏈指向** ``` p.__proto__ // Person.prototype Person.prototype.__proto__ // Object.prototype p.__proto__.__proto__ //Object.prototype p.__proto__.constructor.prototype.__proto__ // Object.prototype Person.prototype.constructor.prototype.__proto__ // Object.prototype p1.__proto__.constructor // Person Person.prototype.constructor // Person ``` **原型鏈的終點是什么?如何打印出原型鏈的終點?** 由于`Object`是構造函數,原型鏈終點是`Object.prototype.__proto__`,而`Object.prototype.__proto__=== null // true`,所以,原型鏈的終點是`null`。原型鏈上的所有原型都是對象,所有的對象最終都是由`Object`構造的,而`Object.prototype`的下一級是`Object.prototype.__proto__`。 ![](https://img.kancloud.cn/3e/81/3e81af5d27841525cf5b08f521a4d1b2_490x146.png) **如何獲得對象非原型鏈上的屬性?** 使用后`hasOwnProperty()`方法來判斷屬性是否屬于原型鏈的屬性: ``` function iterate(obj){ var res=[]; for(var key in obj){ if(obj.hasOwnProperty(key)) res.push(key+': '+obj[key]); } return res; } ``` ## 閉包的理解 **閉包是指有權訪問另一個函數作用域中變量的函數**,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變量。 * 閉包有兩個常用的用途: 1. 閉包的第一個用途是使我們在函數外部能夠訪問到函數內部的變量。通過使用閉包,可以通過在外部調用閉包函數,從而在外部訪問到函數內部的變量,可以使用這種方法來創建私有變量。 2. 閉包的另一個用途是使已經運行結束的函數上下文中的變量對象繼續留在內存中,因為閉包函數保留了這個變量對象的引用,所以這個變量對象不會被回收。 * 比如,函數 A 內部有一個函數 B,函數 B 可以訪問到函數 A 中的變量,那么函數 B 就是閉包。 ``` function A() { let a = 1 window.B = function () { console.log(a) } } A() B() // 1 ``` * 經典面試題:循環中使用閉包解決 var 定義函數的問題,在 JS 中,閉包存在的意義就是讓我們可以間接訪問函數內部的變量。 ``` for (var i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) // 最后輸出,都是6,解決方式有多種,往下看 }, i * 1000) } ``` 首先因為 `setTimeout` 是個異步函數,所以會先把循環全部執行完畢,這時候 `i` 就是 6 了,所以會輸出一堆 6。解決方式提供下面三種: 1. 方式一(使用閉包的方式): 首先使用了立即執行函數將 `i` 傳入函數內部,這個時候值就被固定在了參數 `j` 上面不會改變,當下次執行 `timer` 這個閉包的時候,就可以使用外部函數的變量 `j`,從而達到目的。 ``` for (var i = 1; i <= 5; i++) { ;(function(j) { setTimeout(function timer() { console.log(j) }, j * 1000) })(i) } ``` 2. 方式二(就是使用 `setTimeout` 的第三個參數,這個參數會被當成 `timer` 函數的參數傳入。): ``` for (var i = 1; i <= 5; i++) { setTimeout( function timer(j) { console.log(j) }, i * 1000, i ) } ``` 3. 方式三(就是使用 `let` 定義 `i` 了來解決問題了,這個也是最為推薦的方式): ``` for (let i = 1; i <= 5; i++) { setTimeout(function timer() { console.log(i) }, i * 1000) } ``` ## 作用域、作用域鏈的理解 **全局作用域和函數作用域** * 全局作用域 1. 最外層函數和最外層函數外面定義的變量擁有全局作用域 2. 所有未定義直接賦值的變量自動聲明為全局作用域 3. 所有window對象的屬性擁有全局作用域 4. 全局作用域有很大的弊端,過多的全局作用域變量會污染全局命名空間,容易引起命名沖突。 * 函數作用域 1. 函數作用域聲明在函數內部的變零,一般只有固定的代碼片段可以訪問到 2. 作用域是分層的,內層作用域可以訪問外層作用域,反之不行 **塊級作用域** * 使用ES6中新增的let和const指令可以聲明塊級作用域,塊級作用域可以在函數中創建也可以在一個代碼塊中的創建(由`{ }`包裹的代碼片段) * let和const聲明的變量不會有變量提升,也不可以重復聲明 * 在循環中比較適合綁定塊級作用域,這樣就可以把聲明的計數器變量限制在循環內部。 **作用域鏈:** * 在當前作用域中查找所需變量,但是該作用域沒有這個變量,那這個變量就是自由變量。如果在自己作用域找不到該變量就去父級作用域查找,依次向上級作用域查找,直到訪問到window對象就被終止,這一層層的關系就是作用域鏈。 * 作用域鏈的作用是**保證對執行環境有權訪問的所有變量和函數的有序訪問,通過作用域鏈,可以訪問到外層環境的變量和函數。** * 作用域鏈的本質上是一個指向變量對象的指針列表。變量對象是一個包含了執行環境中所有變量和函數的對象。作用域鏈的前端始終都是當前執行上下文的變量對象。全局執行上下文的變量對象(也就是全局對象)始終是作用域鏈的最后一個對象。 * 當查找一個變量時,如果當前執行環境中沒有找到,可以沿著作用域鏈向后查找。 ## call/apply/bind **call() 和 apply() 的區別?** 它們的作用一模一樣,區別僅在于傳入參數的形式的不同。 * apply 接受兩個參數,第一個參數指定了函數體內 this 對象的指向,第二個參數為一個帶下標的集合,這個集合可以為數組,也可以為類數組,apply 方法把這個集合中的元素作為參數傳遞給被調用的函數。 * call 傳入的參數數量不固定,跟 apply 相同的是,第一個參數也是代表函數體內的 this 指向,從第二個參數開始往后,每個參數被依次傳入函數。 **實現call、apply 及 bind 函數** * call 函數的實現步驟: 1. 判斷調用對象是否為函數,即使是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。 2. 判斷傳入上下文對象是否存在,如果不存在,則設置為 window 。 3. 處理傳入的參數,截取第一個參數后的所有參數。 4. 將函數作為上下文對象的一個屬性。 5. 使用上下文對象來調用這個方法,并保存返回結果。 6. 刪除剛才新增的屬性。 7. 返回結果。 ``` Function.prototype.myCall = function(context) { // 判斷調用對象 if (typeof this !== "function") { console.error("type error"); } // 獲取參數 let args = [...arguments].slice(1), result = null; // 判斷 context 是否傳入,如果未傳入則設置為 window context = context || window; // 將調用函數設為對象的方法 context.fn = this; // 調用函數 result = context.fn(...args); // 將屬性刪除 delete context.fn; return result; }; ``` * apply 函數的實現步驟: 1. 判斷調用對象是否為函數,即使是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。 2. 判斷傳入上下文對象是否存在,如果不存在,則設置為 window 。 3. 將函數作為上下文對象的一個屬性。 4. 判斷參數值是否傳入 5. 使用上下文對象來調用這個方法,并保存返回結果。 6. 刪除剛才新增的屬性 7. 返回結果 ``` Function.prototype.myApply = function(context) { // 判斷調用對象是否為函數 if (typeof this !== "function") { throw new TypeError("Error"); } let result = null; // 判斷 context 是否存在,如果未傳入則為 window context = context || window; // 將函數設為對象的方法 context.fn = this; // 調用方法 if (arguments[1]) { result = context.fn(...arguments[1]); } else { result = context.fn(); } // 將屬性刪除 delete context.fn; return result; }; ``` * bind 函數的實現步驟: 1. 判斷調用對象是否為函數,即使是定義在函數的原型上的,但是可能出現使用 call 等方式調用的情況。* 保存當前函數的引用,獲取其余傳入參數值。 2. 創建一個函數返回 3. 函數內部使用 apply 來綁定函數調用,需要判斷函數作為構造函數的情況,這個時候需要傳入當前函數的 this 給 apply 調用,其余情況都傳入指定的上下文對象。 ``` Function.prototype.myBind = function(context) { // 判斷調用對象是否為函數 if (typeof this !== "function") { throw new TypeError("Error"); } // 獲取參數 var args = [...arguments].slice(1), fn = this; return function Fn() { // 根據調用方式,傳入不同綁定值 return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; }; ``` ## 實現異步編程的方式 * **回調函數** 的方式,使用回調函數的方式有一個缺點是,多個回調函數嵌套的時候會造成回調函數地獄,上下兩層的回調函數間的代碼耦合度太高,不利于代碼的可維護。 * **Promise** 的方式,使用 Promise 的方式可以將嵌套的回調函數作為鏈式調用。但是使用這種方法,有時會造成多個 then 的鏈式調用,可能會造成代碼的語義不夠明確。 * **generator** 的方式,它可以在函數的執行過程中,將函數的執行權轉移出去,在函數外部還可以將執行權轉移回來。當遇到異步函數執行的時候,將函數執行權轉移出去,當異步函數執行完畢時再將執行權給轉移回來。因此在 generator 內部對于異步操作的方式,可以以同步的順序來書寫。使用這種方式需要考慮的問題是何時將函數的控制權轉移回來,因此需要有一個自動執行 generator 的機制,比如說 co 模塊等方式來實現 generator 的自動執行。 * **async 函數** 的方式,async 函數是 generator 和 promise 實現的一個自動執行的語法糖,它內部自帶執行器,當函數內部執行到一個 await 語句的時候,如果語句返回一個 promise 對象,那么函數將會等待 promise 對象的狀態變為 resolve 后再繼續向下執行。因此可以將異步邏輯,轉化為同步的順序來書寫,并且這個函數可以自動執行。 **對Promise的理解** * Promise是異步編程的一種解決方案,它是一個對象,可以獲取異步操作的消息,他的出現大大改善了異步編程的困境,避免了地獄回調,它比傳統的解決方案回調函數和事件更合理和更強大。 * 所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。 1. Promise的實例有**三個狀態**: * Pending(進行中) * Resolved(已完成) * Rejected(已拒絕) * 當把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。 2. Promise的實例有**兩個過程**: * pending -> fulfilled : Resolved(已完成) * pending -> rejected:Rejected(已拒絕) * 注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。 * Promise的缺點: 1. 無法取消Promise,一旦新建它就會立即執行,無法中途取消。 2. 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。 3. 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。 * 總結: 1. Promise 對象是異步編程的一種解決方案,最早由社區提出。Promise 是一個構造函數,接收一個函數作為參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者rejected 狀態,并且狀態一經改變,就凝固了,無法再被改變了。 2. 狀態的改變是通過 resolve() 和 reject() 函數來實現的,可以在異步操作結束后調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態的改變注冊回調函數。這個回調函數屬于微任務,會在本輪事件循環的末尾執行。 * 注意:在構造 `Promise` 的時候,構造函數內部的代碼是立即執行的。 ## 垃圾回收機制與內存泄漏 **瀏覽器的垃圾回收機制** * 垃圾回收的概念 **垃圾回收**:JavaScript代碼運行時,需要分配內存空間來儲存變量和值。當變量不在參與運行時,就需要系統收回被占用的內存空間,這就是垃圾回收。 * **回收機制**: 1. Javascript 具有自動垃圾回收機制,會定期對那些不再使用的變量、對象所占用的內存進行釋放,原理就是找到不再使用的變量,然后釋放掉其占用的內存。 2. JavaScript中存在兩種變量:局部變量和全局變量。全局變量的生命周期會持續要頁面卸載;而局部變量聲明在函數中,它的生命周期從函數執行開始,直到函數執行結束,在這個過程中,局部變量會在堆或棧中存儲它們的值,當函數執行結束后,這些局部變量不再被使用,它們所占有的空間就會被釋放。 3. 不過,當局部變量被外部函數使用時,其中一種情況就是閉包,在函數執行結束后,函數外部的變量依然指向函數內部的局部變量,此時局部變量依然在被使用,所以不會回收。 * 垃圾回收方式:瀏覽器通常使用的垃圾回收方法有兩種:標記清除,引用計數。 * 標記清除 1. 標記清除是瀏覽器常見的垃圾回收方式,當變量進入執行環境時,就標記這個變量“進入環境”,被標記為“進入環境”的變量是不能被回收的,因為他們正在被使用。當變量離開環境時,就會被標記為“離開環境”,被標記為“離開環境”的變量會被內存釋放。 2. 垃圾收集器在運行的時候會給存儲在內存中的所有變量都加上標記。然后,它會去掉環境中的變量以及被環境中的變量引用的標記。而在此之后再被加上標記的變量將被視為準備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后。垃圾收集器完成內存清除工作,銷毀那些帶標記的值,并回收他們所占用的內存空間。 * 引用計數 1. 另外一種垃圾回收機制就是引用計數,這個用的相對較少。引用計數就是跟蹤記錄每個值被引用的次數。當聲明了一個變量并將一個引用類型賦值給該變量時,則這個值的引用次數就是1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數就減1。當這個引用次數變為0時,說明這個變量已經沒有價值,因此,在在機回收期下次再運行時,這個變量所占有的內存空間就會被釋放出來。 2. 這種方法會引起**循環引用**的問題:例如: `obj1`和`obj2`通過屬性進行相互引用,兩個對象的引用次數都是2。當使用循環計數時,由于函數執行完后,兩個對象都離開作用域,函數執行結束,`obj1`和`obj2`還將會繼續存在,因此它們的引用次數永遠不會是0,就會引起循環引用。 **哪些情況會導致內存泄漏** 1. **意外的全局變量:**由于使用未聲明的變量,而意外的創建了一個全局變量,而使這個變量一直留在內存中無法被回收。 2. **被遺忘的計時器或回調函數:**設置了 setInterval 定時器,而忘記取消它,如果循環函數有對外部變量的引用的話,那么這個變量會被一直留在內存中,而無法被回收。 3. **脫離 DOM 的引用:**獲取一個 DOM 元素的引用,而后面這個元素被刪除,由于一直保留了對這個元素的引用,所以它也無法被回收。 4. **閉包:**不合理的使用閉包,從而導致某些變量一直被留在內存當中。
                  <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>

                              哎呀哎呀视频在线观看