整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          code-review之前端代碼優化

          謂無規矩不成方圓,前端時間在團隊 code-review 中發現,不同時期不同開發人員寫的代碼可謂五花八門。因此我們提出了一些相關代碼方面的規范,希望日后能形成團隊的編碼規范。

          本文主要針對一些 JavaScript 進行優化,使之更加健壯,可讀性更強,更以維護。

          gitthub地址:github.com/Michael-lzg…

          上一篇:code-review之前端代碼規范

          if 判斷的優化

          JavaScript 條件語句在我們平時的開發中是不可避免要用到的,但是很多時候我們的代碼寫的并不好,一連串的 if-else 或者多重嵌套判斷都會使得代碼很臃腫,下面舉例進行優化。

          需求:現在有 4 個產品,分別是手機、電腦、電視機、游戲機,當然每個產品顯示的價格不一樣。

          1、最簡單的方法:if 判斷

          let commodity = {
            phone: '手機',
            computer: '電腦',
            television: '電視',
            gameBoy: '游戲機',
          }
          
          function price(name) {
            if (name === commodity.phone) {
              console.log(1999)
            } else if (name === commodity.computer) {
              console.log(9999)
            } else if (name === commodity.television) {
              console.log(2999)
            } else if (name === commodity.gameBoy) {
              console.log(3999)
            }
          }
          price('手機') // 9999

          缺點:代碼太長了,維護和閱讀都很不友好

          2、好一點的方法:Switch

          let commodity = {
            phone: '手機',
            computer: '電腦',
            television: '電視',
            gameBoy: '游戲機',
          }
          const price = (name) => {
            switch (name) {
              case commodity.phone:
                console.log(1999)
                break
              case commodity.computer:
                console.log(9999)
                break
              case commodity.television:
                console.log(2999)
                break
              case commodity.gameBoy:
                console.log(3999)
                break
            }
          }
          price('手機') // 9999

          3、更優的方法: 策略模式

          策略模式利用組合、委托和多態等技術和思想,可以有效地避免多重條件選擇語句。它提供了對開放—封閉原則的完美支持,將算法封裝在獨立的 strategy 中,使得它們易于切換,易于理解,易于擴展。

          const commodity = new Map([
            ['phone', 1999],
            ['computer', 9999],
            ['television', 2999],
            ['gameBoy', 3999],
          ])
          
          const price = (name) => {
            return commodity.get(name)
          }
          price('phone') // 1999

          includes 的優化

          includes 是 ES7 新增的 API,與 indexOf 不同的是 includes 直接返回的是 Boolean 值,indexOf 則 返回的索引值, 數組和字符串都有 includes 方法。

          需求:我們來實現一個身份認證方法,通過傳入身份 Id 返回對應的驗證結果

          傳統方法

          function verifyIdentity(identityId) {
            if (identityId == 1 || identityId == 2 || identityId == 3 || identityId == 4) {
              return '你的身份合法,請通行!'
            } else {
              return '你的身份不合法'
            }
          }

          includes 優化

          function verifyIdentity(identityId) {
            if ([1, 2, 3, 4].includes(identityId)) {
              return '你的身份合法,請通行!'
            } else {
              return '你的身份不合法'
            }
          }

          for 循環

          在 JavaScript 中,我們可以使用 for(), while(), for(in),for(in)幾種循環,事實上,這三種循環中 for(in) 的效率極差,因為他需要查詢散列鍵,所以應該盡量少用。

          for 循環是最傳統的語句,它以變量 i 作為索引,以跟蹤訪問的位置,對數組進行操作。

          var arr = ['a', 'b', 'c']
          for (var i = 0; i < arr.length; i++) {
            console.log(arr[i]) //結果依次a,b,c
          }

          以上的方法有一個問題:就是當數組的長度到達百萬級時,arr.length 就要計算一百萬次,這是相當耗性能的。所以可以采用以下方法就行改良。

          var arr = ['a', 'b', 'c']
          for (var i = 0, length = arr.length; i < length; i++) {
            console.log(arr[i]) //結果依次a,b,c
          }

          此時 arr.length 只需要計算一次,優化了性能。

          for-in 一般用來來遍歷對象的屬性的,不過屬性需要 enumerable(可枚舉)才能被讀取到。同時 for-in 也可以遍歷數組,遍歷數組的時候遍歷的是數組的下標值。

          var obj = { 0: 'a', 1: 'b', 2: 'c' }
          for (var key in obj) {
            console.log(key) //結果為依次為0,1,2
          }
          
          var arr = ['a', 'b', 'c']
          for (var key in a) {
            console.log(key) //結果為依次為0,1,2
          }

          for-of 語句看著有點像 for-in 語句,但是和 for-of 語句不同的是它不可以循環對象,只能循環數組。

          var arr = ['a', 'b', 'c']
          for (var value of arr) {
            console.log(value) // 結果依次為a,b,c
          }

          for-of 比 for-in 循環遍歷數組更好。for-of 只要具有 Iterator 接口的數據結構,都可以使用它迭代成員。它直接讀取的是鍵值。for-in 需要窮舉對象的所有屬性,包括自定義的添加的屬性也能遍歷到。且 for-in 的 key 是 String 類型,有轉換過程,開銷比較大。

          所以在開發過程中循環數組盡量避免使用 for-in。

          數組去重

          數組去重是實際開發處理數據中經常遇到的,方法有很多,這里就不一一例舉了。

          1、最傳統的方法:利用數組的 indexOf 下標屬性來查詢。

          function unique4(arr) {
            var newArr = []
            for (var i = 0; i < arr.length; i++) {
              if (newArr.indexOf(arr[i]) === -1) {
                newArr.push(arr[i])
              }
            }
            return newArr
          }
          console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
          // [1, 2, 3, 5, 6, 7, 4]

          2、優化:利用 ES6 的 Set 方法。

          Set 本身是一個構造函數,用來生成 Set 數據結構。Set 函數可以接受一個數組(或者具有 iterable 接口的其他數據結構)作為參數,用來初始化。Set 對象允許你存儲任何類型的值,無論是原始值或者是對象引用。它類似于數組,但是成員的值都是唯一的,沒有重復的值。

          function unique4(arr) {
            return Array.from(new Set(arr)) // 利用Array.from將Set結構轉換成數組
          }
          console.log(unique4([1, 1, 2, 3, 5, 3, 1, 5, 6, 7, 4]))
          // [1, 2, 3, 5, 6, 7, 4]

          箭頭函數

          箭頭函數表達式的語法比函數表達式更簡潔。所以在開發中更推薦使用箭頭函數。特別是在 vue 項目中,使用箭頭函數不需要在更 this 重新賦一個變量。

          // 使用functions
          var arr = [5, 3, 2, 9, 1]
          var arrFunc = arr.map(function (x) {
            return x * x
          })
          console.log(arrFunc)
          
          // 使用箭頭函數
          var arr = [5, 3, 2, 9, 1]
          var arrFunc = arr.map((x) => x * x)

          要注意的是,箭頭函數不綁定 arguments,取而代之用 rest 參數…解決。

          // 不能使用 arguments
          let fun1 = (b) => {
            console.log(arguments)
          }
          fun1(2, 92, 32, 32) // Uncaught ReferenceError: arguments is not defined
          
          // 使用rest 參數
          let fun2 = (...c) => {
            console.log(c)
          }
          fun2(3, 82, 32, 11323) // [3, 82, 32, 11323]

          Dom 的創建

          創建多個 dom 元素時,先將元素 append 到 DocumentFragment 中,最后統一將 DocumentFragment 添加到頁面。

          常規方法;

          for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p')
            el.innerHTML = i
            document.body.appendChild(el)
          }

          使用 DocumentFragment 優化多次 append

          var frag = document.createDocumentFragment()
          for (var i = 0; i < 1000; i++) {
            var el = document.createElement('p')
            el.innerHTML = i
            frag.appendChild(el)
          }
          document.body.appendChild(frag)

          更優的方法:使用一次 innerHTML 賦值代替構建 dom 元素

          var html = []
          for (var i = 0; i < 1000; i++) {
            html.push('<p>' + i + '</p>')
          }
          document.body.innerHTML = html.join('')

          內存泄漏

          系統進程不再用到的內存,沒有及時釋放,就叫做內存泄漏(memory leak)。當內存占用越來越高,輕則影響系統性能,重則導致進程崩潰。

          引起內存泄漏的原因

          全局變量

          1、未聲明變量或者使用 this 創建的變量(this 的指向是 window)都會引起內存泄漏

          function fn() {
            a = "Actually, I'm a global variable"
          }
          fn()
          
          function fn() {
            this.a = "Actually, I'm a global variable"
          }
          fn()

          解決方法:

          • 避免創建全局變量
          • 使用嚴格模式,在 JavaScript 文件頭部或者函數的頂部加上 use strict。

          2、在 vue 單頁面應用,聲明的全局變量在切換頁面的時候沒有清空

          <template>
            <div id="home">
              這里是首頁
            </div>
          </template>
          
          <script>
            export default {
              mounted() {
                window.test = {
                  // 此處在全局window對象中引用了本頁面的dom對象
                  name: 'home',
                  node: document.getElementById('home')
                }
              }
            }
          </script>

          解決方案: 在頁面卸載的時候順便處理掉該引用。

          destroyed () {
            window.test = null // 頁面卸載的時候解除引用
          }

          閉包

          閉包引起的內存泄漏原因:閉包可以維持函數內局部變量,使其得不到釋放。

          function fn() {
            var a = "I'm a"
            return function () {
              console.log(a)
            }
          }

          解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對 dom 的引用。

          定時器或事件監聽

          由于項目中有些頁面難免會碰到需要定時器或者事件監聽。但是在離開當前頁面的時候,定時器如果不及時合理地清除,會造成業務邏輯混亂甚至應用卡死的情況,這個時就需要清除定時器事件監聽,即在頁面卸載(關閉)的生命周期函數里,清除定時器。

          methods:{
            resizeFun () {
              this.tableHeight = window.innerHeight - document.getElementById('table').offsetTop - 128
            },
            setTimer() {
              this.timer = setInterval(() => { })
            },
            clearTimer() {//清除定時器
                  clearInterval(this.timer)
              this.timer = null
              }
          },
          mounted() {
            this.setTimer()
            window.addEventListener('resize', this.resizeFun)
          },
          beforeDestroy() {
            window.removeEventListener('resize', this.resizeFun)
            this.clearTimer()
          }

          防抖與節流

          在前端開發的過程中,我們經常會需要綁定一些持續觸發的事件,如 resize、scroll、mousemove 等等,但有些時候我們并不希望在事件持續觸發的過程中那么頻繁地去執行函數。這時候就用到防抖與節流。

          案例 1:遠程搜索時需要通過接口動態的獲取數據,若是每次用戶輸入都接口請求,是浪費帶寬和性能的。

          <Select :remote-method="remoteMethod">
              <Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}</Option>
          </Select>
          
          <script>
          function debounce(fn, wait) {
            let timeout = null
            return function () {
              if (timeout !== null) clearTimeout(timeout)
              timeout = setTimeout(fn, wait)
            }
          }
          
          export default {
            methods:{
              remoteMethod:debounce(function (query) {
                  // to do ...
              }, 200),
            }
          }
          <script>

          案例 2:持續觸發 scroll 事件時,并不立即執行 handle 函數,當 1000 毫秒內沒有觸發 scroll 事件時,才會延時觸發一次 handle 函數。

          function debounce(fn, wait) {
            let timeout = null
            return function () {
              if (timeout !== null) clearTimeout(timeout)
              timeout = setTimeout(fn, wait)
            }
          }
          function handle() {
            console.log(Math.random())
          }
          window.addEventListener('scroll', debounce(handle, 1000))

          異步加載 js

          默認情況下,瀏覽器是同步加載 js 腳本,解析 html 過程中,遇到 <script> 標簽就會停下來,等腳本下載、解析、執行完后,再繼續向下解析渲染。

          如果 js 文件體積比較大,下載時間就會很長,容易造成瀏覽器堵塞,瀏覽器頁面會呈現出“白屏”效果,用戶會感覺瀏覽器“卡死了”,沒有響應。此時,我們可以讓 js 腳本異步加載、執行。

          <script src="path/to/home.js" defer></script>
          <script src="path/to/home.js" async></script>

          上面代碼中,<script> 標簽分別有 defer 和 async 屬性,瀏覽器識別到這 2 個屬性時 js 就會異步加載。也就是說,瀏覽器不會等待這個腳本下載、執行完畢后再向后執行,而是直接繼續向后執行

          defer 與 async 區別:

          • defer:DOM 結構完全生成,以及其他腳本執行完成,才會執行(渲染完再執行)。有多個 defer 腳本時,會按照頁面出現的順序依次加載、執行。
          • async:一旦下載完成,渲染引擎就會中斷渲染,執行這個腳本以后,再繼續渲染(下載完就執行)。有多個 async 腳本時,不能保證按照頁面出現順序加載、執行

          覺得文章不錯的話,麻煩大家轉發和關注,小編為大家也準備了一些資料,回復資料二字即可獲得


          作者:lzg9527 鏈接:https://segmentfault.com/a/1190000023254297 來源:SegmentFault 思否 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。Java學習、面試;文檔、視頻資源免費獲取作者:lzg9527 鏈接:https://segmentfault.com/a/1190000023254297 來源:SegmentFault 思否 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

          avaScript 語句

          JavaScript 語句向瀏覽器發出的命令。語句的作用是告訴瀏覽器該做什么。

          下面的 JavaScript 語句向 id="demo"的 HTML 元素輸出文本 "Hello World":

          分號 ;

          分號用于分隔 JavaScript 語句。

          通常我們在每條可執行的語句結尾添加分號。

          使用分號的另一用處是在一行中編寫多條語句。

          提示:您也可能看到不帶有分號的案例。

          在 JavaScript 中,用分號來結束語句是可選的。

          JavaScript 代碼

          JavaScript 代碼(或者只有 JavaScript)是 JavaScript 語句的序列。

          瀏覽器會按照編寫順序來執行每條語句。

          本例將操作兩個 HTML 元素:

          document.getElementById("demo").innerHTML="Hello World";
          document.getElementById("myDIV").innerHTML="How are you?";
          

          JavaScript 代碼塊

          JavaScript 語句通過代碼塊的形式進行組合。

          塊由左花括號開始,由右花括號結束。

          塊的作用是使語句序列一起執行

          JavaScript 函數是將語句組合在塊中的典型例子。

          下面的例子將運行可操作兩個 HTML 元素的函數:

          function myFunction()
          {
          document.getElementById("demo").innerHTML="Hello World";
          document.getElementById("myDIV").innerHTML="How are you?";
          }
          

          JavaScript 對大小寫敏感。

          JavaScript 對大小寫是敏感的。

          當編寫 JavaScript 語句時,請留意是否關閉大小寫切換鍵。

          函數 getElementById 與 getElementbyID 是不同的。

          同樣,變量 myVariable 與 MyVariable 也是不同的。

          空格

          注:JavaScript 會忽略多余的空格。您可以向腳本添加空格,來提高其可讀性。下面的兩行代碼是等效的:

          var name="Hello";
          var name = "Hello";
          

          對代碼行進行折行

          您可以在文本字符串中使用反斜杠對代碼行進行換行。下面的例子會正確地顯示:

          document.write("Hello \
          World!"); //正確的的折行
          document.write \
          ("Hello World!"); //錯誤的折行
          

          提示:JavaScript 是腳本語言。瀏覽器會在讀取代碼時,逐行地執行腳本代碼。而對于傳統編程來說,會在執行前對所有代碼進行編譯。

          變量

          變量是存儲信息的容器

          變量可以使用短名稱(比如 x 和 y),也可以使用描述性更好的名稱(比如 age, sum, totalvolume)。

          • 變量必須以字母開頭
          • 變量也能以 $ 和 _ 符號開頭(不過我們不推薦這么做)
          • 變量名稱對大小寫敏感(y 和 Y 是不同的變量)

          提示:JavaScript 語句和 JavaScript 變量都對大小寫敏感。

          一個好的編程習慣是,在代碼開始處,統一對需要的變量進行聲明。

          您可以在一條語句中聲明很多變量。該語句以 var 開頭,并使用逗號分隔變量即可:

          var name="Gates", age=56, job="CEO";
          //也可以多行
          var name="Gates",
          age=56,
          job="CEO";
          
          • Value = undefined
          • 在計算機程序中,經常會聲明無值的變量。未使用值來聲明的變量,其值實際上是 undefined。
          • 在執行過以下語句后,變量 carname 的值將是 undefined:
          var carname;
          
          • 重新聲明 JavaScript 變量
          • 如果重新聲明 JavaScript 變量,該變量的值不會丟失:
          • 在以下兩條語句執行后,變量 carname 的值依然是 "Volvo":
          var carname="Volvo";
          var carname;
          

          數據類型

          字符串、數字、布爾、數組、對象、Null、Undefined

          當您向變量分配文本值時,應該用雙引號或單引號包圍這個值。

          當您向變量賦的值是數值時,不要使用引號。如果您用引號包圍數值,該值會被作為文本來處理。

          1.字符串

          字符串是存儲字符(比如 "Bill Gates")的變量。

          字符串可以是引號中的任意文本。您可以使用單引號或雙引號:

          var answer="Nice to meet you!";

          var answer="He is called 'Bill'";

          var answer='He is called "Bill"'; //內有引號的字符串

          2.數字 數字可以帶小數點,也可以不帶;極大或極小的數字可以通過科學(指數)計數法來書寫:var z=123e-5;

          3.布爾 布爾(邏輯)只能有兩個值:true 或 false。布爾常用在條件測試中

          4.數組

          var cars=new Array();
          cars[0]="Audi";
          cars[1]="BMW";
          cars[2]="Volvo";
          var cars=new Array("Audi","BMW","Volvo");//(condensed array)
          var cars=["Audi","BMW","Volvo"];//(literal array)
          

          5.對象

          對象由花括號分隔。在括號內部,對象的屬性以名稱和值對的形式 (name : value) 來定義。屬性由逗號分隔:

          //person 對象有三個屬性
          var person={firstname:"Bill", lastname:"Gates", id:5566};
          

          6.Null

          7.Undefined

          JavaScript 擁有動態類型。這意味著相同的變量可用作不同的類型:

          s數據類型有哪些

          JavaScript共有八種數據類型,分別是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt

          其中 SymbolBigInt 是ES6 中新增的數據類型:

          • Symbol代表創建后獨一無二且不可變的數據類型,它主要是為了解決可能出現的全局變量沖突的問題。
          • BigInt 是一種數字類型的數據,它可以表示任意精度格式的整數,使用 BigInt 可以安全地存儲和操作大整數,即使這個數已經超出了 Number 能夠表示的安全整數范圍。

          這些數據可以分為原始數據類型引用數據類型(復雜數據類型),他們在內存中的存儲方式不同。

          • 堆: 存放引用數據類型,引用數據類型占據空間大、大小不固定。如果存儲在棧中,將會影響程序運行的性能;引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址,如Object、Array、Function。
          • 棧: 存放原始數據類型,棧中的簡單數據段,占據空間小,屬于被頻繁使用的數據,如String、Number、Null、Boolean。

          null和undefined區別

          Undefined 和 Null 都是基本數據類型,這兩個基本數據類型分別都只有一個值,就是 undefined 和 null。

          • undefined 代表的含義是未定義,一般變量聲明了但還沒有定義的時候會返回 undefined,typeof為undefined
          • null 代表的含義是空對象,null主要用于賦值給一些可能會返回對象的變量,作為初始化,typeof為object
          null == undefined // true
          
          null === undefined //false
          


          instanceof 運算符的實現原理及實現

          instanceof運算符適用于檢測構造函數的prototype屬性上是否出現在某個實例對象的原型鏈上

          instanceof 運算符的原理是基于原型鏈的查找。當使用 obj instanceof Constructor 進行判斷時,JavaScript 引擎會從 obj 的原型鏈上查找 Constructor.prototype 是否存在,如果存在則返回 true,否則繼續在原型鏈上查找。如果查找到原型鏈的頂端仍然沒有找到,則返回 false。

          instanceof運算符只能用于檢查某個對象是否是某個構造函數的實例,不能用于基本類型的檢查,如string、number等

          typeof 和 instanceof 區別

          typeof與instanceof 都是判斷數據類型的方法,區別如下:

          • typeof會返回一個運算數的基本類型,instanceof 返回的是布爾值
          • instanceof 可以準確判斷引用數據類型,但是不能正確判斷原始數據類型
          • typeof雖然可以判斷原始數據類型(null 除外),但是無法判斷引用數據類型(function 除外)

          那為什么typeof判斷null為object?

          這是 JavaScript 語言的一個歷史遺留問題,在第一版JS代碼中用32位比特來存儲值,通過值的1-3位來識別類型,前三位為000表示對象類型。而null是一個空值,二進制表示都為0,所以前三位也就是000,所以導致 typeof null 返回 "object"

          為什么0.1+0.2 ! == 0.3,如何讓其相等

          因為浮點數運算的精度問題。在計算機運行過程中,需要將數據轉化成二進制,然后再進行計算。 因為浮點數自身小數位數的限制而截斷的二進制在轉化為十進制,就變成0.30000000000000004,所以在計算時會產生誤差。

          解決方案

          • 將其先轉換成整數,再相加之后轉回小數。具體做法為先乘10相加后除以10
           let x=(0.1*10+0.2*10)/10;
          console.log(x===0.3)
          
          • 使用number對象的toFixed方法,只保留一位小數點。
           (n1 + n2).toFixed(2)
          

          判斷數組的方式有哪些

          • 通過Object.prototype.toString.call()做判斷
           Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
          
          • 通過原型鏈做判斷
           obj.__proto__ === Array.prototype;
          
          • 通過ES6的Array.isArray()做判斷
           Array.isArrray(obj);
          
          • 通過instanceof做判斷
           obj instanceof Array
          

          對類數組對象的理解,如何轉化為數組

          類數組也叫偽數組,類數組和數組類似,但不能調用數組方法,常見的類數組有arguments、通過document.getElements獲取到的內容等,這些類數組具有length屬性。

          轉換方法

          • 通過 call 調用數組的 slice 方法來實現轉換
           Array.prototype.slice.call(arrayLike)
          
          • 通過 call 調用數組的 splice 方法來實現轉換
           Array.prototype.splice.call(arrayLike, 0)
          
          • 通過 apply 調用數組的 concat 方法來實現轉換
           Array.prototype.concat.apply([], arrayLike)
          
          • 通過 Array.from 方法來實現轉換
           Array.from(arrayLike)
          

          Array.propotype.slice.call()是什么 比如Array.prototype.slice.call(arguments)這句里,就是把 arguments 當做當前對象。

          也就是說 要調用的是 arguments 的 slice 方法,而typeof arguments="Object" 而不是 Array

          它沒有slice這個方法,通過這么Array.prototype.slice.call調用,JS的內部機制應該是 把arguments對象轉化為Array

          數組有哪些原生方法?

          • 數組和字符串的轉換方法:toString()、toLocalString()、join() 其中 join() 方法可以指定轉換為字符串時的分隔符。
          • 數組尾部操作的方法 pop() 和 push(),push 方法可以傳入多個參數。
          • 數組首部操作的方法 shift() 和 unshift() 重排序的方法 reverse() 和 sort(),sort() 方法可以傳入一個函數來進行比較,傳入前后兩個值,如果返回值為正數,則交換兩個參數的位置。
          • 數組連接的方法 concat() ,返回的是拼接好的數組,不影響原數組。
          • 數組截取辦法 slice(),用于截取數組中的一部分返回,不影響原數組。
          • 數組插入方法 splice(),影響原數組查找特定項的索引的方法,indexOf() 和 lastIndexOf() 迭代方法 every()、some()、filter()、map() 和forEach()方法
          • 數組歸并方法 reduce() 和 reduceRight() 方法
          • 改變原數組的方法:fill()、pop()、push()、shift()、splice()、unshift()、reverse()、sort();
          • 不改變原數組的方法:concat()、every()、filter()、find()、findIndex()、forEach()、indexOf()、join()、lastIndexOf()、map()、reduce()、reduceRight()、slice()、some()。

          substring和substr的區別

          它們都是字符串方法,用于截取字符串的一部分,主要區別在于參數不同

          • substring(startIndex, endIndex): 接收兩個參數,一個起始索引和結束索引,來指定字符串范圍,如果省略第二個參數,則截取到字符串末尾。
          • substr(startIndex, length): 接收兩個參數,并返回從 startIndex 開始,長度為 length 的子字符串。如果省略第二個參數,則截取到字符串末尾。
          const str = "Hello, World!";
          
          console.log(str.substring(0, 5)); // 輸出: "Hello"
          
          console.log(str.substr(7, 5)); // 輸出: "World"
          


          object.assign和擴展運算法是深拷貝還是淺拷貝,兩者區別

          都是淺拷貝

          • Object.assign()方法接收的第一個參數作為目標對象,后面的所有參數作為源對象。然后把所有的源對象合并到目標對象中。它會修改了一個對象,因此會觸發 ES6 setter。
          • 擴展操作符(…)使用它時,數組或對象中的每一個值都會被拷貝到一個新的數組或對象中。它不復制繼承的屬性或類的屬性,但是它會復制ES6的 symbols 屬性。

          new操作符的實現原理

          new操作符用來創建一個對象,并將該對象綁定到構造函數的this上。

          new操作符的執行過程:

          1. 創建一個空對象
          2. 設置原型,將構造函數的原型指向空對象的 prototype 屬性。
          3. 將 this 指向這個對象,通過apply執行構造函數。
          4. 判斷函數的返回值類型,如果是值類型,返回創建的對象。如果是引用類型,就返回這個引用類型的對象

          「手寫代碼-實現一個new操作符」

          for...in和for...of的區別

          for...in和for...of都是JavaScript中的循環語句,而for…of 是ES6新增的遍歷方式,允許遍歷一個含有iterator接口的數據結構(數組、對象等)并且返回各項的值,和ES3中的for…in的區別如下

          • for…of 遍歷獲取的是對象的鍵值,for…in 獲取的是對象的鍵名
          • for… in 會遍歷對象的整個原型鏈,性能非常差不推薦使用,而 for … of 只遍歷當前對象不會遍歷原型鏈;
          • 對于數組的遍歷,for…in 會返回數組中所有可枚舉的屬性(包括原型鏈上可枚舉的屬性),for…of 只返回數組的下標對應的屬性值;

          總結:for...in 循環主要是為了遍歷對象而生,不適用于遍歷數組;for...of 循環可以用來遍歷數組、類數組對象,字符串、Set、Map 以及 Generator 對象。

          如何使用for...of遍歷對象

          為什么不能遍歷對象

          for…of是作為ES6新增的遍歷方式,能被其遍歷的數據內部都有一個遍歷器iterator接口,而數組、字符串、Map、Set內部已經實現,普通對象內部沒有,所以在遍歷的時候會報錯。想要遍歷對象,可以給對象添加一個Symbol.iterator屬性,并指向一個迭代器即可

          在迭代器里面,通過Object.keys獲取對象所有的key,然后遍歷返回key 、value。

          var obj = {
              a:1,
              b:2,
              c:3
          };
          obj[Symbol.iterator] = function*(){
              var keys = Object.keys(obj);
              for(var k of keys){
                  yield [k,obj[k]]
              }
          };
          
          for(var [k,v] of obj){
              console.log(k,v);
          }
          


          對AJAX的理解,實現一個AJAX請求

          AJAX是 Asynchronous JavaScript and XML 的縮寫,指的是通過 JavaScript 的 異步通信,從服務器獲取 XML 文檔從中提取數據,再更新當前網頁的對應部分,而不用刷新整個網頁。 創建AJAX請求的步驟:

          • 創建一個 XMLHttpRequest 對象。
          • 在這個對象上使用 open 方法創建一個 HTTP 請求,open 方法所需要的參數是請求的方法、請求的地址、是否異步和用戶的認證信息。
          • 在發起請求前,可以為這個對象添加一些信息和監聽函數。比如說可以通過 setRequestHeader 方法來為請求添加頭信息。還可以為這個對象添加一個狀態監聽函數。一個 XMLHttpRequest 對象一共有 5 個狀態,當它的狀態變化時會觸發onreadystatechange 事件,可以通過設置監聽函數,來處理請求成功后的結果。當對象的 readyState 變為 4 的時候,代表服務器返回的數據接收完成,這個時候可以通過判斷請求的狀態,如果狀態是 2xx 或者 304 的話則代表返回正常。這個時候就可以通過 response 中的數據來對頁面進行更新了。
          • 當對象的屬性和監聽函數設置完成后,最后調用 send 方法來向服務器發起請求,可以傳入參數作為發送的數據體。
          const SERVER_URL = "/server";
          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);
          
          


          ajax、axios、fetch的區別

          ajax

          • 基于原生XHR開發,XHR本身架構不清晰。
          • 針對MVC編程,不符合現在前端MVVM的浪潮。
          • 多個請求之間如果有先后關系的話,就會出現回調地獄
          • 配置和調用方式非常混亂,而且基于事件的異步模型不友好。

          axios

          • 支持PromiseAPI
          • 從瀏覽器中創建XMLHttpRequest
          • 從 node.js 創建 http 請求
          • 支持請求攔截和響應攔截
          • 自動轉換JSON數據
          • 客服端支持防止CSRF/XSRF

          fetch

          • 瀏覽器原生實現的請求方式,ajax的替代品
          • 基于標準 Promise 實現,支持async/await
          • fetchtch只對網絡請求報錯,對400,500都當做成功的請求,需要封裝去處理
          • 默認不會帶cookie,需要添加配置項
          • fetch沒有辦法原生監測請求的進度,而XHR可以。

          forEach和map方法有什么區別

          兩個方法都是用來遍歷循環數組,區別如下:

          • forEach()對數據的操作會改變原數組,該方法沒有返回值;
          • map()方法不會改變原數組的值,返回一個新數組,新數組中的值為原數組調用函數處理之后的值;

          什么是尾調用,使用尾調用有什么好處?

          尾調用就是在函數的最后一步調用函數,在一個函數里調用另外一個函數會保留當前執行的上下文,如果在函數尾部調用,因為已經是函數最后一步,所以這時可以不用保留當前的執行上下文,從而節省內存。但是ES6的尾調用只能在嚴格模式下開啟,正常模式是無效的。

          你用過哪些設計模式

          • 單例模式:保證類只有一個實例,并提供一個訪問它的全局訪問點。
          • 工廠模式:用來創建對象,根據不同的參數返回不同的對象實例。
          • 策略模式:定義一系列的算法,把它們一個個封裝起來,并且使它們可以相互替換。
          • 裝飾器模式:在不改變對象原型的基礎上,對其進行包裝擴展。
          • 觀察者模式:定義了對象間一種一對多關系,當目標對象狀態發生改變時,所有依賴它對對象都會得到通知。
          • 發布訂閱模式: 基于一個主題/事件通道,希望接收通知的對象通過自定義事件訂閱主題,被激活事件的對象(通過發布主題事件的方式被通知)。

          如何實現深淺拷貝

          深拷貝

          • JSON.stringify() 將js對象序列化,再通過JSON.parse反序列 如果對象中有函數、undefined、symbol時,都會丟失 如果有正則表達式、Error對象等,會得到空對象

          「手寫代碼-手寫深拷貝」

          淺拷貝

          • Objec.assign() 拷貝對象
          • 擴展運算符

          「手寫代碼-手寫淺拷貝」

          ES6

          let、const、var的區別

          • 塊級作用域: 塊作用域由 { }包裹,let和const具有塊級作用域,var不存在塊級作用域。塊級作用域解決了ES5中的兩個問題: 內層變量可能覆蓋外層變量 用來計數的循環變量泄露為全局變量
          • 變量提升: var存在變量提升,let和const不存在變量提升,即在變量只能在聲明之后使用,否在會報錯。
          • 給全局添加屬性: 瀏覽器的全局對象是window,Node的全局對象是global。var聲明的變量為全局變量,并且會將該變量添加為全局對象的屬性,但是let和const不會。
          • 重復聲明: var聲明變量時,可以重復聲明變量,后聲明的同名變量會覆蓋之前聲明的遍歷。const和let不允許重復聲明變量。
          • 初始值設置: 在變量聲明時,var 和let可以不用設置初始值。而const聲明變量必須設置初始值。
          • 暫時性死區:在使用let、const命令聲明變量之前,該變量都是不可用的。這在語法上,稱為暫時性死區。使用var聲明的變量不存在暫時性死區。

          箭頭函數與普通函數的區別

          • 箭頭函數是匿名函數,不能作為構造函數,使用new關鍵字。
          • 箭頭函數沒有arguments
          • 箭頭函數沒有自己的this,會獲取所在的上下文作為自己的this
          • call()、applay()、bind()方法不能改變箭頭函數中的this指向
          • 箭頭函數沒有prototype
          • 箭頭函數不能用作Generator函數,不能使用yeild關鍵字

          Set、Map的區別

          Set

          • 創建: new Set([1, 1, 2, 3, 3, 4, 2])
          • add(value):添加某個值,返回Set結構本身。
          • delete(value):刪除某個值,返回一個布爾值,表示刪除是否成功。
          • has(value):返回一個布爾值,表示該值是否為Set的成員。
          • clear():清除所有成員,沒有返回值。

          Map

          • set(key, val): 向Map中添加新元素
          • get(key): 通過鍵值查找特定的數值并返回
          • has(key): 判斷Map對象中是否有Key所對應的值,有返回true,否則返回false
          • delete(key): 通過鍵值從Map中移除對應的數據
          • clear(): 將這個Map中的所有元素刪除

          區別

          • Map是一種鍵值對的集合,和對象不同的是,鍵可以是任意值
          • Map可以遍歷,可以和各種數據格式轉換
          • Set是類似數組的一種的數據結構,類似數組的一種集合,但在Set中沒有重復的值

          map和Object的區別

          map和Object都是用鍵值對來存儲數據,區別如下:

          • 鍵的類型:Map 的鍵可以是任意數據類型(包括對象、函數、NaN 等),而 Object 的鍵只能是字符串或者 Symbol 類型。
          • 鍵值對的順序:Map中的鍵值對是按照插入的順序存儲的,而對象中的鍵值對則沒有順序。
          • 鍵值對的遍例:Map 的鍵值對可以使用 for...of 進行遍歷,而 Object 的鍵值對需要手動遍歷鍵值對。
          • 繼承關系:Map 沒有繼承關系,而 Object 是所有對象的基類。

          map和weakMap的區別

          它們是 JavaScript 中的兩種不同的鍵值對集合,主要區別如下:

          1. map的鍵可以是任意類型,weakMap鍵只能是對象類型。
          2. map 使用常規的引用來管理鍵和值之間的關系,因此即使鍵不再使用,map 仍然會保留該鍵的內存。weakMap 使用弱引用來管理鍵和值之間的關系,因此如果鍵不再有其他引用,垃圾回收機制可以自動回收鍵值對。

          說說你對Promise的理解

          Promise是異步編程的一種解決方案,將異步操作以同步操作的流程表達出來,避免了地獄回調。

          Promise的實例有三個狀態:

          • Pending(初始狀態)
          • Fulfilled(成功狀態)
          • Rejected(失敗狀態)

          Promise的實例有兩個過程:

          • pending -> fulfilled : Resolved(已完成)
          • pending -> rejected:Rejected(已拒絕)
          • 注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了,其過程是不可逆的。

          Promise構造函數接收一個帶有resolve和reject參數的回調函數。

          • resolve的作用是將Promise狀態從pending變為fulfilled,在異步操作成功時調用,并將異步結果返回,作為參數傳遞出去
          • reject的作用是將Promise狀態從pending變為rejected,在異步操作失敗后,將異步操作錯誤的結果,作為參數傳遞出去

          Promise的缺點:

          • 無法取消 Promise,一旦新建它就會立即執行,無法中途取消。
          • 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
          • 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。

          Promise方法

          • promise.then() 對應resolve成功的處理
          • promise.catch()對應reject失敗的處理
          • promise.all()可以完成并行任務,將多個Promise實例數組,包裝成一個新的Promise實例,返回的實例就是普通的Promise。有一個失敗,代表該Primise失敗。當所有的子Promise完成,返回值時全部值的數組
          • promise.race()類似promise.all(),區別在于有任意一個完成就算完成
          • promise.allSettled() 返回一個在所有給定的 promise 都已經 fulfilled 或 rejected 后的 promise ,并帶有一個對象數組,每個對象表示對應的promise 結果。

          promise.all 和 promise.allsettled 區別

          Promise.all() 和 Promise.allSettled() 都是用來處理多個 Promise 實例的方法,它們的區別在于以下幾點:

          • all: 只有當所有Promise實例都resolve后,才會resolve返回一個由所有Promise返回值組成的數組。如果有一個Promise實例reject,就會立即被拒絕,并返回拒絕原因。all是團隊的成功才算,如果有一個人失敗就算失敗。
          • allSettled: 等所有Promise執行完畢后,不管成功或失敗, 都會吧每個Promise狀態信息放到一個數組里面返回。

          對async/await 的理解

          async/await其實是Generator 的語法糖,它能實現的效果都能用then鏈來實現,它是為優化then鏈而開發出來的。通過async關鍵字聲明一個異步函數, await 用于等待一個異步方法執行完成,并且會阻塞執行。 async 函數返回的是一個 Promise 對象,如果在函數中 return 一個變量,async 會把這個直接量通過 Promise.resolve() 封裝成 Promise 對象。如果沒有返回值,返回 Promise.resolve(undefined)

          async/await對比Promise的優勢

          • 代碼可讀性高,Promise雖然擺脫了回掉地獄,但自身的鏈式調用會影響可讀性。
          • 相對Promise更優雅,傳值更方便。
          • 對錯誤處理友好,可以通過try/catch捕獲,Promise的錯誤捕獲?常冗余

          談談你對ES6的理解

          • 解構賦值
          • 擴展運算符
          • 箭頭函數
          • 模版字符串
          • Set、Map集合
          • 新增class類
          • Proxy
          • Promise
          • ...

          ES6模塊和CommonJS模塊有什么區別

          • 語法不同:ES6 模塊使用 import 和 export 關鍵字來導入和導出模塊,而 CommonJS 模塊使用 require 和 module.exports 或 exports 來導入和導出模塊。
          // ES6 模塊
          import { foo } from './module';
          export const bar = 'bar';
          
          // CommonJS 模塊
          const foo = require('./commonjs');
          exports.bar = 'bar';
          
          
          • 異步加載: ES6 模塊支持動態導入(dynamic import),可以異步加載模塊。這使得在需要時按需加載模塊成為可能,從而提高了性能。CommonJS 模塊在設計時沒有考慮異步加載的需求,通常在模塊的頂部進行同步加載。

          原型

          • prototype : js通過構造函數來創建對象,每個構造函數內部都會一個原型prototype屬性,它指向另外一個對象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。
          • proto: 當使用構造函數創建一個實例對象后,可以通過__proto__訪問到prototype屬性。
          • constructor:實例對象通過這個屬性可以訪問到構造函數

          原型鏈

          每個實例對象都有一個__proto__屬性指向它的構造函數的原型對象,而這個原型對象也會有自己的原型對象,一層一層向上,直到頂級原型對象null,這樣就形成了一個原型鏈。

          當訪問對象的一個屬性或方法時,當對象身上不存在該屬性方法時,就會沿著原型鏈向上查找,直到查找到該屬性方法位置。

          原型鏈的頂層原型是Object.prototype,如果這里沒有就只指向null

          實現寄生組合繼承

          利用Object.create()方法,將子類的原型指向父類,實現繼承父類的方法屬性,修改時也不影響父類。

          function Parent(name) {
            this.name = name;
            this.colors = ['red', 'green', 'blue'];
          }
          Parent.prototype.sayName = function() {
            console.log(this.name);
          };
          function Child(name, age) {
          	// 執行父類構造函數
            Parent.call(this, name);
            this.age = age;
          }
          // 將子類的原型  指向父類
          Child.prototype = Object.create(Parent.prototype);
          // 此時的狗早函數為父類的 需要指回自己
          Child.prototype.constructor = Child;
          
          Child.prototype.sayAge = function() {
            console.log(this.age);
          };
          var child1 = new Child('Tom', 18);
          child1.sayName(); // 'Tom'
          child1.sayAge(); // 18
          


          對閉包的理解已經它的使用場景

          閉包是指有權訪問另一個函數作用域中變量的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變量。

          閉包優點:

          • 創建全局私有變量,避免變量全局污染
          • 可以實現封裝、緩存等

          閉包缺點:

          • 創建的變量不能被回收,容易消耗內存,使用不當會導致內存溢出 解決: 在不需要使用的時候把變量設為null

          使用場景:

          • 用于創建全局私有變量
          • 封裝類和模塊
          • 實現函數柯里化

          閉包一定會造成內存泄漏嗎?

          閉包并不一定會造成內存泄漏,如果在使用閉包后變量沒有及時銷毀,可能會造成內存泄漏的風險。只要合理的使用閉包,就不會造成內存泄漏。

          對作用域、作用域鏈的理解

          作用域是一個變量或函數的可訪問范圍,作用域控制著變量或函數的可見性和生命周期。

          1. 全局作用域:可以全局訪問
          2. 最外層函數和最外層定義的變量擁有全局作用域
          3. window上的對象屬性方法擁有全局作用域
          4. 為定義直接復制的變量自動申明擁有全局作用域
          5. 過多的全局作用域變量會導致變量全局污染,命名沖突
          6. 函數作用域:只能在函數中訪問使用哦
          7. 在函數中定義的變量,都只能在內部使用,外部無法訪問
          8. 內層作用域可以訪問外層,外層不能訪問內存作用域
          9. ES6中的塊級作用域:只在代碼塊中訪問使用
          10. 使用ES6中新增的let、const什么的變量,具備塊級作用域,塊級作用域可以在函數中創建(由{}包裹的代碼都是塊級作用域)
          11. let、const申明的變量不會變量提升,const也不能重復申明
          12. 塊級作用域主要用來解決由變量提升導致的變量覆蓋問題

          作用域鏈: 變量在指定的作用域中沒有找到,會依次向一層作用域進行查找,直到全局作用域。這個查找的過程被稱為作用域鏈。

          call() 、bind()、 apply() 的區別?

          • 都可以用作改變this指向
          • call和apply的區別在于傳參,call、bind都是傳入對象。apply傳入一個數組。
          • call、apply改變this指向后會立即執行函數,bind在改變this后返回一個函數,不會立即執行函數,需要手動調用。

          連續多個 bind,最后this指向是什么?

          在 JavaScript 中,連續多次調用 bind 方法,最終函數的 this 上下文是由第一次調用 bind 方法的參數決定的

          const obj1 = { name: 'obj1' };
          const obj2 = { name: 'obj2' };
          const obj3 = { name: 'obj3' };
          
          function getName() {
            console.log(this.name);
          }
          
          const fn1 = getName.bind(obj1).bind(obj2).bind(obj3);
          fn1(); // 輸出 "obj1"
          


          瀏覽器的垃圾回收機制

          垃圾回收:JavaScript代碼運行時,需要分配內存空間來儲存變量和值。當變量不再參與運行時,就需要系統收回被占用的內存空間。如果不及時清理,會造成系統卡頓、內存溢出,這就是垃圾回收。

          在 V8 中,會把堆分為新生代和老生代兩個區域,新生代中存放的是生存時間短的對象,老生代中存放生存時間久的對象:

          • Major GC(主垃圾回收器):主要負責老生代垃圾的回收 內存占用比較小
          • Minor GC(副垃圾回收器):主要負責新生代垃圾的回收 對象的占用空間大 對象存活時間長

          新生代(副垃圾回收器)

          副垃圾回收器主要負責新?代的垃圾回收。大多數的對象最開始都會被分配在新生代,該存儲空間相對較小,分為兩個空間:from 空間(對象區)和 to 空間(空閑區)。

          • 新增變量會放到To空間,當空間滿后需要執行一次垃圾清理操作
          • 對垃圾數據進行標記,標記完成后將存活的數據復制到From空間中,有序排列
          • 交換兩個空間,原來的To變成From,舊的From變成To

          老生代(主垃圾回收器)

          主垃圾回收器主要負責??代中的垃圾回收。存儲一些占用空間大、存活時間長的數據,采用標記清除算法進行垃圾回收。

          主要分為標記清除兩個階段。

          • 標記:將所有的變量打上標記0,然后從根節點(window對象、DOM樹等)開始遍歷,把存活的變量標記為1
          • 清除:清除標記為0的對象,釋放內存。清除后將1的變量改為0,方便下一輪回收。

          對?塊內存多次執?標記清除算法后,會產??量不連續的內存碎?。?碎?過多會導致?對象?法分配到?夠的連續內存,于是?引?了另外?種算法——標記整理

          標記整理的標記過程仍然與標記清除算法?的是?樣的,先標記可回收對象,但后續步驟不是直接對可回收對象進?清理,?是讓所有存活的對象都向?端移動,然后直接清理掉這?端之外的內存。

          引用計數法

          一個對象被引用一次,引用數就+1,反之就-1。當引用為0,就會出發垃圾回收。

          這種方式會產生一個問題,在循環引用時,引用數永遠不會為0,無法回收。

          哪些情況會導致內存泄漏

          • 意外的全局變量:由于使用未聲明的變量,而意外的創建了一個全局變量,而使這個變量一直留在內存中無法被回收。
          • 被遺忘的計時器或回調函數:設置了 setInterval 定時器,而忘記取消它,如果循環函數有對外部變量的引用的話,那么這個變量會被一直留在內存中,而無法被回收。
          • 脫離 DOM 的引用:獲取一個 DOM 元素的引用,而后面這個元素被刪除,由于一直保留了對這個元素的引用,所以它也無法被回收。
          • 閉包:不合理的使用閉包,從而導致某些變量一直被留在內存當中。


          作者:wakaka378
          鏈接:https://juejin.cn/post/7270471613547249699


          主站蜘蛛池模板: 国产一区二区在线观看app| 国产乱人伦精品一区二区| 精品永久久福利一区二区| 亚洲AV成人一区二区三区在线看 | 久久99精品波多结衣一区| 无码精品人妻一区二区三区免费| 亚洲AV无码一区二区三区牲色| 久久人妻av一区二区软件| 国产亚洲欧洲Aⅴ综合一区| 精品一区二区三区在线播放| 国产福利无码一区在线| 亚洲熟女www一区二区三区| 3d动漫精品啪啪一区二区中| 无码人妻一区二区三区在线| 国语精品一区二区三区| 中文字幕Av一区乱码| 九九久久99综合一区二区| 国精品无码一区二区三区左线| 中文字幕一区二区区免| 中文字幕永久一区二区三区在线观看 | 精品欧洲av无码一区二区| 无码少妇一区二区性色AV| 成人午夜视频精品一区| 国产日韩精品一区二区三区在线| 一区二区精品久久| 久久精品无码一区二区app| 波多野结衣高清一区二区三区| 爆乳熟妇一区二区三区| 精品日本一区二区三区在线观看| 国产亚洲情侣一区二区无码AV| 日韩一区二区电影| 影音先锋中文无码一区| 亚洲AV成人精品日韩一区| 国产凸凹视频一区二区| 偷拍精品视频一区二区三区| 无人码一区二区三区视频| 人妻少妇久久中文字幕一区二区| 在线观看国产一区二三区| 国产成人一区二区三区精品久久| 四虎一区二区成人免费影院网址 | 亚洲av无一区二区三区|