作用域/作用域鏈 閉包及其使用

一、作用域、作用域鏈

作用域(scope)

淺顯的理解: 作用域就是變量的可用范圍(scope)

為什么要有作用域: 目的是防止不同范圍的變量之間互相干擾

js中包含2級(jí)作用域:全局作用域 & 函數(shù)作用域。

  1. 全局作用域

    不屬于任何函數(shù)的外部范圍稱為全局作用域,保存在全局的作用域的變量稱為全局變量。

    優(yōu)點(diǎn):可反復(fù)使用;缺點(diǎn):會(huì)造成全局污染。

  2. 函數(shù)作用域

    一個(gè)函數(shù)內(nèi)部的范圍稱為函數(shù)作用域,保存在函數(shù)作用域的變量為局部變量。

    【注】函數(shù)形參變量也是函數(shù)內(nèi)的局部變量。

    優(yōu)點(diǎn):不會(huì)污染;缺點(diǎn):無法反復(fù)使用。

  3. 作用域的形成

    只有函數(shù)的{}可以形成作用域,其他情況的{}不能形成作用域。比如對(duì)象的大括號(hào)不是作用域,對(duì)象的屬性不是局部變量。

    【注】es6的塊級(jí)作用域是實(shí)現(xiàn)其實(shí)是匿名函數(shù)自調(diào)用形成的,實(shí)際上也是函數(shù)作用域的原理。

作用域鏈(scopes / scope chain)

js規(guī)定,一個(gè)函數(shù),及能用自己的作用域,又能用外層的作用域變量。所以需要一個(gè)訪問變量的"路線圖"。

每個(gè)函數(shù)在定義時(shí),就已經(jīng)規(guī)劃好了自己專屬的查找變量的”線路圖“,蹭我作用域鏈。

【注】給從未聲明的變量賦值,不會(huì)報(bào)錯(cuò),但會(huì)生成全局變量。(非嚴(yán)格模式)

總結(jié): Js中只有兩種局部變量: 1. 函數(shù)內(nèi)var出來的 2. 函數(shù)的形參變量 看不見var,形參里也沒有, 就不是局部變量。

var a=10;
function fun(){
    var a=100;
    a++;
    console.log(a);
}
fun(); // 101
console.log(a);  // 10
// 形參題目---------
var a=10;
function fun(a){
  a++;
  console.log(a); 
}
fun(a); //11
console.log(a); //10

作用域的本質(zhì)

js中的作用域和作用域鏈都是對(duì)象結(jié)構(gòu)

debug時(shí)查看作用域

在fun函數(shù)還未調(diào)用時(shí),fun的作用域鏈:

fun的作用域鏈

進(jìn)入fun函數(shù)時(shí),作用域鏈增加一個(gè)local作用域

作用域鏈增加一個(gè)local作用域
作用域本質(zhì)
函數(shù)作用域釋放

總結(jié): 函數(shù)作用域 其實(shí)是js引擎在調(diào)用函數(shù)時(shí)才臨時(shí)創(chuàng) 建的一個(gè)作用域?qū)ο?。其中保存函?shù) 的局部變量。而函數(shù)調(diào)用完,函數(shù)作 用域?qū)ο缶歪尫帕恕?/p>

所以,JS中函數(shù)作用域?qū)ο?,還有個(gè)別名 ——”活動(dòng)的對(duì)象(Actived Object)” 簡稱, AO。 所以,局部變量不可重用

二、閉包

閉包c(diǎn)losure:能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。

既重用變量又保護(hù)變量不被污染的一種編程方法。

只要希望給一個(gè)函數(shù),保存一個(gè)即可反復(fù)使用,又不會(huì)被 外界污染的專屬局部變量時(shí),就用閉包。

使用閉包3步:

  1. 外層函數(shù)包裹 要保護(hù)的變量和使用變量的內(nèi)層函數(shù)
  2. 外層函數(shù) 返回 內(nèi)層函數(shù)
  3. 調(diào)用外層函數(shù),用變量接住返回的內(nèi)層函數(shù)
//第1步: 用外層函數(shù)包裹要保護(hù)的變量和內(nèi)層函數(shù)
function mother(){
    var total=1000;
    //第2步: 返回內(nèi)層函數(shù)對(duì)象
    return function (money){
        total-=money
        console.log(`花了${money}還剩${total}元`)
    }
}
//第3步: 調(diào)用外層函數(shù),用變量接住內(nèi)層函數(shù)對(duì)象
var pay=mother();
//pay接住的就是mother()返回出來的內(nèi)層函數(shù)對(duì)象
pay(100);//應(yīng)該剩900
total=0;//別人程序執(zhí)行了代碼
pay(100);//應(yīng)該剩800
pay函數(shù)的作用域

可以看出pay函數(shù)的作用域中有個(gè)閉包來自mother其中保存了total變量。

一句話概括:

閉包如何形成: 外層函數(shù)調(diào)用后, 外層函數(shù)的作用域?qū)ο螅?被返回的內(nèi)層函數(shù)的作用域鏈引用著, 無法釋放, 就形成了閉包對(duì)象

閉包缺點(diǎn):容易造成內(nèi)存泄漏。解決辦法:及時(shí)釋放不用的閉包:將保存內(nèi)存函數(shù)的變量賦值為null。

題目:閉包的應(yīng)用

  1. 柯里化
// 要求定義函數(shù)add 實(shí)現(xiàn)add(1)(2)(4)得到1+2+4的結(jié)果。
// 函數(shù)柯里化:可以給一個(gè)函數(shù)反復(fù)傳參,傳的參數(shù)還可以累計(jì)到函數(shù)中
var add = function(x1){
  var sum = x1;
  var fun = function(x2) {
    sum+=x2;
    return fun;
    // 因?yàn)榭吕锘罂梢赃B續(xù)傳參,所以需要return出這個(gè)累加函數(shù),確??梢岳^續(xù)傳參時(shí)繼續(xù)調(diào)用。如果直接返回sum,那么只傳兩個(gè)參數(shù),該過程就結(jié)束了。
  }
  fun.toString = function() {
    // toString方法在隱式轉(zhuǎn)字符串時(shí)調(diào)用,可以返回fun中函數(shù)閉包中的sum變量的值
    return sum
  }
  fun.valueOf = function() {
    // valueOf方法在隱式轉(zhuǎn)number時(shí)調(diào)用,可以通過該方法返回sum值
    return sum
  }
  return fun; // 為了可以傳入第二個(gè)參數(shù),add()第一次調(diào)用時(shí),需要返回函數(shù),才能繼續(xù)第二次的傳參調(diào)用
}
alert(add(2)(3)(4)(1))
console.log(0+add(1)(2)(3)(3))
  1. 二維游戲:2048/消消樂類型
<head>
  <style>
    .container{
      width: 200px;
      height: 200px;
      border-radius: 6px;
      background: rgb(230, 203, 87);
    }
    .container div{
      cursor: pointer;
      width: 40px; height: 40px;
      background: #fff;
      float: left;
      margin-left: 8px;
      margin-top: 8px;
      border-radius: 4px;
      text-align: center;
      line-height: 40px;
    }
  </style>
</head>
<body>
  <div id="container" class="container"></div>
  <script>
    // 4*4個(gè)div,每個(gè)表格中都有其坐標(biāo):(0,0) - (3,3),點(diǎn)擊格子彈出該格子的點(diǎn)擊次數(shù),每個(gè)格子互不干擾(閉包)
    var div = document.getElementById('container');
    // // 方案1:直接給每個(gè)格子添加click事件,用一個(gè)外層函數(shù)自調(diào)用包裹,定義一個(gè)自己的變量n記錄點(diǎn)擊次數(shù),然后return一個(gè)實(shí)際的累加操作函數(shù)
    // // 缺點(diǎn):每個(gè)格子的n都是獨(dú)立的,對(duì)于二維游戲來說,格子之間需要計(jì)算,比如2048格子之間的數(shù)值累加,此方法無法實(shí)現(xiàn)
    // for(var r =0;r<4;r++) { // 行循環(huán)生成
    //   for(var c = 0;c<4;c++) { // 列循環(huán)生成
    //     var cell = document.createElement('div')
    //     cell.innerText = `${r},${c}`
    //     div.appendChild(cell);
    //     // 添加點(diǎn)擊事件
    //     cell.onclick = (function() {
    //       var n = 0;
    //       return function() {
    //         n++;
    //         alert(`點(diǎn)擊了${n}次`)
    //       }
    //     })()
    //   }
    // }

    // // 方案2:使用二維數(shù)組來記錄每個(gè)格子的值,每個(gè)按鈕只保存其行號(hào)和列號(hào)(坐標(biāo)),當(dāng)點(diǎn)擊時(shí),通過按鈕自己保存的行號(hào)和列號(hào)來找到二維數(shù)組中自己對(duì)應(yīng)的值
    // // 缺點(diǎn):會(huì)存在全局變量arr,容易被篡改
    // var arr = [
    //   [0,0,0,0],
    //   [0,0,0,0],
    //   [0,0,0,0],
    //   [0,0,0,0]
    // ]
    // for(var r =0;r<4;r++) { // 行循環(huán)生成
    //   for(var c = 0;c<4;c++) { // 列循環(huán)生成
    //     var cell = document.createElement('div')
    //     cell.innerText = `${r},${c}`
    //     div.appendChild(cell);
    //     // 只保存自己的行號(hào)列號(hào)
    //     cell.onclick = (function(r,c) {
    //       return function() {
    //         arr[r][c]++
    //         alert(`點(diǎn)擊了${arr[r][c]}次`)
    //       }
    //     })(r, c)
    //   }
    // }

    // 方案3:使用方案2+整個(gè)邏輯的匿名函數(shù)自調(diào)用包裹,使arr也為局部變量不會(huì)被污染
    (function(){
      var arr = [
        [0,0,0,0],
        [0,0,0,0],
        [0,0,0,0],
        [0,0,0,0]
      ]
      for(var r =0;r<4;r++) { // 行循環(huán)生成
        for(var c = 0;c<4;c++) { // 列循環(huán)生成
          var cell = document.createElement('div')
          cell.innerText = `${r},${c}`
          div.appendChild(cell);
          // 只保存自己的行號(hào)列號(hào)
          cell.onclick = (function(r,c) {
            return function() {
              arr[r][c]++
              alert(`點(diǎn)擊了${arr[r][c]}次`)
            }
          })(r, c)
        }
      }
    })()
  </script>
</body>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容