iT邦幫忙

2023 iThome 鐵人賽

DAY 29
0
SideProject30

往後端邁進的菜前端系列 第 29

倒數一天要來回顧一下閉包

  • 分享至 

  • xImage
  •  

因為這幾天剛好朋友來公司面試,我們討論著一些前端面試必考題,想說也工作半年了,可以來恢復一下失去的記憶…

尤其是必考題 閉包 啊!

  1. 變數作用域 (全域性變數 global variants) 區域性變數 (local)

變數根據作用域的不同分為兩種:全域性變數和區域性變數。

**「作用域」一詞,指的正是作用域環境在程式碼指定變數時,使用 location 來決定該變數用在哪裡的事情。

  • 函式內部可以使用全域性變數。
  • 函式外部不可以使用區域性變數。
  • 當函式執行完畢,本作用域內的區域性變數會銷毀。
  1. 記憶體空間存放
    每當我們新增一個變數就會產生一個記憶體位置來存放值
    https://ithelp.ithome.com.tw/upload/images/20231014/201626391IUvrY1S1l.png
    https://ithelp.ithome.com.tw/upload/images/20231014/20162639233JAZMBOE.png
  • 若變數在函式 : 變數在函式執行完後,沒有被任何方法參照到時,變數的記憶體空間就會被釋放掉。
  • 若變數在函式 (全域環境) : 變數的記憶體空間不會被釋放掉。
function randomString(length) {
  var result = ''
  var characters =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  var charactersLength = characters.length
  for (var i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength))
  }
  return result
}

var demoData = [] //全域變數

function getData() {
  var demoData = [] //區域變數
  for (let i = 0; i < 1000; i++) {
    demoData.push(randomString(1000))
  }
}
getData()

閉包的出現

—>延伸變數作用域範圍,讀取函式內部的變數
—>讓這些變數的值始終保持在記憶體中不被釋放,讓外部可以執行。
影響 —> 讓 function 可以重複被使用

做法 (Scopes nesting)—> 在子函式部分使用父函式所宣告的變數,就可避免父函式所宣告的變數在沒有參照的情況下被釋放掉。儲存內層 Function 變數不被釋放,重複使用。

function storeMoney() {
  // 宣告父函數
  var money = 1000 // 外層 money 此時的變數可以被內層的 function 存取
  return function (price) {
    // (閉包)子函數引用父函數
    money = money + price // 不斷取用外層所宣告的變數(範圍鍊)
    return money // 內層的 money 後來是私有的變數,外層無法讀取
  }
}
console.log(storeMoney()) // 返回子函數
//1. 直接呼叫內層函式
console.log(storeMoney()(100)) // 1100 (存取內部函式的變數)
//呼叫一次外層,再呼叫一次內層,price的值為100

//2.外層函式賦予到另一個變數上
var debbyMoney = storeMoney()
console.log(debbyMoney)
//storMoney是一個表達式,所以會回傳一段函式,建立一個變數(debbyMoney)儲存,如此storeMoney的記憶體就不會被釋放
console.log(debbyMoney(1000)) //2000
console.log(debbyMoney(1000)) //3000
console.log(debbyMoney(1000)) //4000

//3. function重複使用
var riverMoney = storeMoney()
console.log(riverMoney)
//storMoney是一個表達式,所以會回傳一段函式,建立一個變數(riverMoney)儲存,如此storeMoney的記憶體就不會被釋放
console.log(riverMoney(1)) //2000
console.log(riverMoney(10)) //3000
console.log(riverMoney(100)) //4000

//他們共享函式的定義,卻保有不同的環境

https://ithelp.ithome.com.tw/upload/images/20231014/20162639dlW5nvpPsP.jpg
https://ithelp.ithome.com.tw/upload/images/20231014/20162639pNsX6AjYS1.jpg

QA1

var msg = "global."

function outer() {
  var msg = "local."

  function inner() {
    return msg;
  }

  return inner;
}

var innerFunc = outer();
var result = innerFunc();

console.log( result );    //  ?
//程式碼在撰寫完成的同時,就已經先確立了作用域,運行的過程中都不會改變其作用域

QA: 我想要每過一秒印出 1 2 3 4 5


for (var i = 0; i < 3; i++) {
  setTimeout(function log() {
    console.log(i) // What is logged?
  }, 1000)

  console.log('var i', i)
}
console.log('var i', i)

// var i = 0 的作用域範圍是 function,for 在1000毫秒前就跑了三次,最後一次 i 停留在3
// 這時內層的 log()要執行console.log(i),變數 i 會往作用域的外層去尋找,就找到上面迴圈的那個變數 i,也就是 3

for (let i = 0; i < 3; i++) {
  setTimeout(function log() {
    console.log(i) // What is logged?
  }, 1000)
  console.log('let i', i)
}
console.log('let i', i) //i is not defined

有閉包跟無閉包的差異

var count = 0;

function counter(){
  return ++count;
}

console.log( counter() );   // 1
console.log( counter() );   // 2
console.log( counter() );   // 3
function counter(){
  var count = 0;

  return function(){
    return ++count;
  }
}

var countFunc = counter();

console.log( countFunc() );   // 1
console.log( countFunc() );   // 2
console.log( countFunc() );   // 3

//甚至可以使用同一個方向再次複製出獨立的環境
var countFunc2 = counter();
console.log( countFunc2() );   // 1
console.log( countFunc2() );   // 2

透過閉包,可以同時執行多種方法

function storeMoney(initValue) {
  var money = initValue || 1000 // 若 initValue 有值就套用,或給 1000
  return {
    // 使用 return 回傳一個物件,每個物件裡都是一段函式
    increase: function (price) {
      money += price
    },
    decrease: function (price) {
      money -= price
    },
    value: function () {
      return money
    },
  }
}
var MingMoney = storeMoney(100)
MingMoney.increase(100)
MingMoney.increase(100)
MingMoney.decrease(25)
MingMoney.decrease(96)
console.log(MingMoney.value()) // 179

var timMoney = storeMoney(1000)
timMoney.increase(1000)
console.log(timMoney.value()) // 2000

你已經用過的 JS closure

  1. event handler
let countClicked = 0;
myButton.addEventListener('click', function handleClick() {
  countClicked++;
  myText.innerText = `You clicked ${countClicked} times`;
});
  1. callback function
const message = 'Hello, World!';
setTimeout(function callback() {
  console.log(message); // logs "Hello, World!"
}, 1000);

總結

為何需要閉包

  1. 解決過多的全域變數會造成不可預期的錯誤(變數名稱衝突、沒用到的變數無法回收)
  2. 增加函式的可用性,執行屬於自己的函式跟變數

明天就要結賽了,心情也真是有種高興但又有種壓力的感覺,即使到現在每天都還是一堆聽不懂的東西,學不完的東西,牙一咬還是要繼續走下去呢!


上一篇
來安裝 Docker 吧
下一篇
第一次的完賽
系列文
往後端邁進的菜前端30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言