<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          閉包的使用場景,使用閉包需要注意什么

          共 15984字,需瀏覽 32分鐘

           ·

          2021-04-10 13:06

          閉包

          什么是閉包

          閉包很簡單,就是能夠訪問另一個函數(shù)作用域變量的函數(shù),更簡單的說,閉包就是函數(shù),只不過是聲明在其它函數(shù)內(nèi)部而已。

          例如:

          function getOuter(){
            var count = 0
            function getCount(num){
              count += num
              console.log(count) //訪問外部的date
            }
            return getCount //外部函數(shù)返回
          }
          var myfunc = getOuter()
          myfunc(1// 1
          myfunc(2// 3

          myfunc 就是閉包, myfunc 是執(zhí)行 getOuter 時創(chuàng)建的 getCount 函數(shù)實例的引用。getCount 函數(shù)實例維護(hù)了一個對它的詞法環(huán)境的引用,所以閉包就是函數(shù)+詞法環(huán)境

          當(dāng) myfunc 函數(shù)被調(diào)用時,變量 count 依然是可用的,也可以更新的

          function add(x){
              return function(y){
                  return x + y
              };
          }

          var addFun1 = add(4)
          var addFun2 = add(9)

          console.log(addFun1(2)) //6
          console.log(addFun2(2))  //11

          add 接受一個參數(shù) x ,返回一個函數(shù),它的參數(shù)是 y ,返回 x+y

          add 是一個函數(shù)工廠,傳入一個參數(shù),就可以創(chuàng)建一個參數(shù)和其他參數(shù)求值的函數(shù)。

          addFun1addFun2 都是閉包。他們使用相同的函數(shù)定義,但詞法環(huán)境不同, addFun1x4 ,后者是 5

          即:

          • 閉包可以訪問當(dāng)前函數(shù)以外的變量
          • 即使外部函數(shù)已經(jīng)返回,閉包仍能訪問外部函數(shù)定義的變量與參數(shù)
          • 閉包可以更新外部變量的值

          所以,閉包可以:

          • 避免全局變量的污染
          • 能夠讀取函數(shù)內(nèi)部的變量
          • 可以在內(nèi)存中維護(hù)一個變量

          使用閉包應(yīng)該注意什么

          • 代碼難以維護(hù): 閉包內(nèi)部是可以訪問上級作用域,改變上級作用域的私有變量,我們使用的使用一定要小心,不要隨便改變上級作用域私有變量的值

          • 使用閉包的注意點: 由于閉包會使得函數(shù)中的變量都保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄漏。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除(引用設(shè)置為 null ,這樣就解除了對這個變量的引用,其引用計數(shù)也會減少,從而確保其內(nèi)存可以在適當(dāng)?shù)臅r機回收)

          • 內(nèi)存泄漏: 程序的運行需要內(nèi)存。對于持續(xù)運行的服務(wù)進(jìn)程,必須及時釋放不再用到的內(nèi)存,否則占用越來越高,輕則影響系統(tǒng)性能,重則導(dǎo)致進(jìn)程崩潰。不再用到的內(nèi)存,沒有及時釋放,就叫做內(nèi)存泄漏

          • this指向: 閉包的this指向的是window

          應(yīng)用場景

          閉包通常用來創(chuàng)建內(nèi)部變量,使得這些變量不能被外部隨意修改,同時又可以通過指定的函數(shù)接口來操作。例如 setTimeout 傳參、回調(diào)、IIFE、函數(shù)防抖、節(jié)流、柯里化、模塊化等等

          setTimeout 傳參

          //原生的setTimeout傳遞的第一個函數(shù)不能帶參數(shù)
          setTimeout(function(param){
              alert(param)
          },1000)


          //通過閉包可以實現(xiàn)傳參效果
          function myfunc(param){
              return function(){
                  alert(param)
              }
          }
          var f1 = myfunc(1);
          setTimeout(f1,1000);

          回調(diào)

          大部分我們所寫的 JavaScript 代碼都是基于事件的 — 定義某種行為,然后將其添加到用戶觸發(fā)的事件之上(比如點擊或者按鍵)。我們的代碼通常作為回調(diào):為響應(yīng)事件而執(zhí)行的函數(shù)。

          例如,我們想在頁面上添加一些可以調(diào)整字號的按鈕??梢圆捎胏ss,也可以使用:

          <!DOCTYPE html>
          <html>
          <head>
              <meta charset="utf-8">
              <meta http-equiv="X-UA-Compatible" content="IE=edge">
              <title>test</title>
              <link rel="stylesheet" href="">
          </head>
          <style>
              body{
                  font-size12px;
              }
              h1{
                  font-size1.5rem;
              }
              h2{
                  font-size1.2rem;
              }
          </style>
          <body>
            
              <p>測試</p>

              <a href="#" id="size-12">12</a>
              <a href="#" id="size-14">14</a>
              <a href="#" id="size-16">16</a>

          <script>
              function changeSize(size){
                  return function(){
                      document.body.style.fontSize = size + 'px';
                  };
              }

              var size12 = changeSize(12);
              var size14 = changeSize(14);
              var size16 = changeSize(16);

              document.getElementById('size-12').onclick = size12;
              document.getElementById('size-14').onclick = size14;
              document.getElementById('size-16').onclick = size16;
          </script>
          </body>
          </html>

          IIFE

            var arr = [];
              for (var i=0;i<3;i++){
                //使用IIFE
                (function (i{
                  arr[i] = function ({
                    return i;
                  };
                })(i);
              }
              console.log(arr[0]()) // 0
              console.log(arr[1]()) // 1
              console.log(arr[2]()) // 2

          函數(shù)防抖、節(jié)流

          debouncethrottle 是開發(fā)中常用的高階函數(shù),作用都是為了防止函數(shù)被高頻調(diào)用,換句話說就是,用來控制某個函數(shù)在一定時間內(nèi)執(zhí)行多少次。

          使用場景

          比如綁定響應(yīng)鼠標(biāo)移動、窗口大小調(diào)整、滾屏等事件時,綁定的函數(shù)觸發(fā)的頻率會很頻繁。若稍處理函數(shù)微復(fù)雜,需要較多的運算執(zhí)行時間和資源,往往會出現(xiàn)延遲,甚至導(dǎo)致假死或者卡頓感。為了優(yōu)化性能,這時就很有必要使用 debouncethrottle了。

          debounce throttle 區(qū)別

          防抖 (debounce) :多次觸發(fā),只在最后一次觸發(fā)時,執(zhí)行目標(biāo)函數(shù)。

          節(jié)流(throttle):限制目標(biāo)函數(shù)調(diào)用的頻率,比如:1s內(nèi)不能調(diào)用2次。

          源碼實現(xiàn)

          debounce

          // 這個是用來獲取當(dāng)前時間戳的
          function now({
            return +new Date()
          }
          /**
           * 防抖函數(shù),返回函數(shù)連續(xù)調(diào)用時,空閑時間必須大于或等于 wait,func 才會執(zhí)行
           *
           * @param  {function} func        回調(diào)函數(shù)
           * @param  {number}   wait        表示時間窗口的間隔
           * @param  {boolean}  immediate   設(shè)置為ture時,是否立即調(diào)用函數(shù)
           * @return {function}             返回客戶調(diào)用函數(shù)
           */

          function debounce (func, wait = 50, immediate = true{
            let timer, context, args

            // 延遲執(zhí)行函數(shù)
            const later = () => setTimeout(() => {
              // 延遲函數(shù)執(zhí)行完畢,清空緩存的定時器序號
              timer = null
              // 延遲執(zhí)行的情況下,函數(shù)會在延遲函數(shù)中執(zhí)行
              // 使用到之前緩存的參數(shù)和上下文
              if (!immediate) {
                func.apply(context, args)
                context = args = null
              }
            }, wait)

            // 這里返回的函數(shù)是每次實際調(diào)用的函數(shù)
            return function(...params{
              // 如果沒有創(chuàng)建延遲執(zhí)行函數(shù)(later),就創(chuàng)建一個
              if (!timer) {
                timer = later()
                // 如果是立即執(zhí)行,調(diào)用函數(shù)
                // 否則緩存參數(shù)和調(diào)用上下文
                if (immediate) {
                  func.apply(this, params)
                } else {
                  context = this
                  args = params
                }
              // 如果已有延遲執(zhí)行函數(shù)(later),調(diào)用的時候清除原來的并重新設(shè)定一個
              // 這樣做延遲函數(shù)會重新計時
              } else {
                clearTimeout(timer)
                timer = later()
              }
            }
          }

          throttle

          /**
           * underscore 節(jié)流函數(shù),返回函數(shù)連續(xù)調(diào)用時,func 執(zhí)行頻率限定為 次 / wait
           *
           * @param  {function}   func      回調(diào)函數(shù)
           * @param  {number}     wait      表示時間窗口的間隔
           * @param  {object}     options   如果想忽略開始函數(shù)的的調(diào)用,傳入{leading: false}。
           *                                如果想忽略結(jié)尾函數(shù)的調(diào)用,傳入{trailing: false}
           *                                兩者不能共存,否則函數(shù)不能執(zhí)行
           * @return {function}             返回客戶調(diào)用函數(shù)
           */

          _.throttle = function(func, wait, options{
              var context, args, result;
              var timeout = null;
              // 之前的時間戳
              var previous = 0;
              // 如果 options 沒傳則設(shè)為空對象
              if (!options) options = {};
              // 定時器回調(diào)函數(shù)
              var later = function({
                // 如果設(shè)置了 leading,就將 previous 設(shè)為 0
                // 用于下面函數(shù)的第一個 if 判斷
                previous = options.leading === false ? 0 : _.now();
                // 置空一是為了防止內(nèi)存泄漏,二是為了下面的定時器判斷
                timeout = null;
                result = func.apply(context, args);
                if (!timeout) context = args = null;
              };
              return function({
                // 獲得當(dāng)前時間戳
                var now = _.now();
                // 首次進(jìn)入前者肯定為 true
                // 如果需要第一次不執(zhí)行函數(shù)
                // 就將上次時間戳設(shè)為當(dāng)前的
                // 這樣在接下來計算 remaining 的值時會大于0
                if (!previous && options.leading === false) previous = now;
                // 計算剩余時間
                var remaining = wait - (now - previous);
                context = this;
                args = arguments;
                // 如果當(dāng)前調(diào)用已經(jīng)大于上次調(diào)用時間 + wait
                // 或者用戶手動調(diào)了時間
                // 如果設(shè)置了 trailing,只會進(jìn)入這個條件
                // 如果沒有設(shè)置 leading,那么第一次會進(jìn)入這個條件
                // 還有一點,你可能會覺得開啟了定時器那么應(yīng)該不會進(jìn)入這個 if 條件了
                // 其實還是會進(jìn)入的,因為定時器的延時
                // 并不是準(zhǔn)確的時間,很可能你設(shè)置了2秒
                // 但是他需要2.2秒才觸發(fā),這時候就會進(jìn)入這個條件
                if (remaining <= 0 || remaining > wait) {
                  // 如果存在定時器就清理掉否則會調(diào)用二次回調(diào)
                  if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                  }
                  previous = now;
                  result = func.apply(context, args);
                  if (!timeout) context = args = null;
                } else if (!timeout && options.trailing !== false) {
                  // 判斷是否設(shè)置了定時器和 trailing
                  // 沒有的話就開啟一個定時器
                  // 并且不能不能同時設(shè)置 leading 和 trailing
                  timeout = setTimeout(later, remaining);
                }
                return result;
              };
            };

          柯里化

          在計算機科學(xué)中,柯里化(Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)。這個技術(shù)由 Christopher Strachey 以邏輯學(xué)家 Haskell Curry 命名的,盡管它是 Moses Schnfinkel 和 Gottlob Frege 發(fā)明的。

          var add = function(x{
            return function(y{
              return x + y;
            };
          };
          var increment = add(1);
          var addTen = add(10);
          increment(2);
          // 3
          addTen(2);
          // 12
          add(1)(2);
          // 3

          這里定義了一個 add 函數(shù),它接受一個參數(shù)并返回一個新的函數(shù)。調(diào)用 add 之后,返回的函數(shù)就通過閉包的方式記住了 add 的第一個參數(shù)。所以說 bind 本身也是閉包的一種使用場景。

          柯里化是將 f(a,b,c) 可以被以 f(a)(b)(c) 的形式被調(diào)用的轉(zhuǎn)化。JavaScript 實現(xiàn)版本通常保留函數(shù)被正常調(diào)用和在參數(shù)數(shù)量不夠的情況下返回偏函數(shù)這兩個特性。

          模塊化

          模塊化的目的在于將一個程序按照其功能做拆分,分成相互獨立的模塊,以便于每個模塊只包含與其功能相關(guān)的內(nèi)容,模塊之間通過接口調(diào)用

          模塊化開發(fā)和閉包息息相關(guān),通過模塊模式需要具備兩個必要條件可以看出:

          • 外部必須是一個函數(shù),且函數(shù)必須至少被調(diào)用一次(每次調(diào)用產(chǎn)生的閉包作為新的模塊實例)
          • 外部函數(shù)內(nèi)部至少有一個內(nèi)部函數(shù), 內(nèi)部函數(shù)用于修改和訪問各種內(nèi)部私有成員
          function myModule (){
              const moduleName = '我的自定義模塊'
              var name = 'sisterAn'

              // 在模塊內(nèi)定義方法(API)
              function getName(){
                  console.log(name)
              }
              function modifyName(newName){
                  name = newName
              }

              // 模塊暴露:  向外暴露API
              return {
                  getName,
                  modifyName
              }
          }

          // 測試
          const md = myModule()
          md.getName()    // 'sisterAn'
          md.modifyName('PZ')
          md.getName()    // 'PZ'

          // 模塊實例之間互不影響
          const md2 = myModule()
          md2.sayHello = function ({
              console.log('hello')
          }
          console.log(md) // {getName: ?, modifyName: ?}

          常見錯誤

          在循環(huán)中創(chuàng)建閉包

          var data = []

          for (var i = 0; i < 3; i++) {
            data[i] = function ({
              console.log(i)
            }
          }

          data[0]()   // 3
          data[1]()   // 3
          data[2]()   // 3

          這里的 i 是全局下的 i,共用一個作用域,當(dāng)函數(shù)被執(zhí)行的時候這時的 i=3,導(dǎo)致輸出的結(jié)構(gòu)都是3

          方案一:閉包

          var data = []

          function myfunc(num{
            return function(){
              console.log(num)
            }
          }

          for (var i = 0; i < 3; i++) {
            data[i] = myfunc(i)
          }

          data[0]()   // 0
          data[1]()   // 1
          data[2]()   // 2

          方案二:let

          如果不想使用過多的閉包,你可以用 ES6 引入的 let 關(guān)鍵詞:

          var data = []

          for (let i = 0; i < 3; i++) {
            data[i] = function ({
              console.log(i)
            }
          }

          data[0]()   // 0
          data[1]()   // 1
          data[2]()   // 2

          方案三:forEach

          如果是數(shù)組的遍歷操作(如下例中的 arr ),還有一個可選方案是使用 forEach()來遍歷:

          var data = []

          var arr = [012]
          arr.forEach(function (i{
            data[i] = function ({
              console.log(i)
            }
          })

          data[0]()   // 0
          data[1]()   // 1
          data[2]()   // 2

          來自:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/453

          最后

          歡迎關(guān)注「三分鐘學(xué)前端」,回復(fù)「交流」自動加入前端三分鐘進(jìn)階群,每日一道編程算法面試題(含解答),助力你成為更優(yōu)秀的前端開發(fā)!

          》》面試官也在看的前端面試資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  骚逼五月婷婷影院 | 国产精品成人网站 | 久操免观 | 曰韩三级 | 污网站在线看 |