LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

理解JavaScript閉包及其使用場景

admin
2024年11月5日 17:22 本文熱度 568

閉包

閉包的官方定義是:一個表達式(通常是一個函數(shù)),它具有多個變量,并綁定到一個包含這些變量的環(huán)境。

在 JavaScript 中,閉包指的是函數(shù)即使在執(zhí)行并離開其定義的詞法作用域后,仍能夠訪問該作用域的能力。這是因為當(dāng)函數(shù)被創(chuàng)建時,它會生成一個閉包,其中包含對當(dāng)前函數(shù)定義環(huán)境的引用,從而使函數(shù)能夠繼續(xù)訪問該環(huán)境中的變量。

以下是一個閉包的示例:

function makeCounter({
  var count = 0;
  return function({
    return ++count;
  };
}

var counter = makeCounter();

console.log(counter()); // 輸出:1
console.log(counter()); // 輸出:2
console.log(counter()); // 輸出:3

在這個示例中,makeCounter函數(shù)返回一個匿名函數(shù),并在makeCounter函數(shù)的作用域內(nèi)定義了一個count變量。當(dāng)makeCounter函數(shù)執(zhí)行時,它返回匿名函數(shù)并將其賦值給變量counter。每次調(diào)用counter函數(shù)時,它都會訪問makeCounter函數(shù)作用域內(nèi)的count變量并遞增它。由于匿名函數(shù)在創(chuàng)建時形成了一個閉包,所以即使makeCounter函數(shù)執(zhí)行完畢,counter函數(shù)仍然可以訪問makeCounter函數(shù)作用域內(nèi)的count變量,從而實現(xiàn)了計數(shù)器的功能。

閉包具有以下特點:

  • 閉包可以訪問外部函數(shù)作用域中的變量,即使外部函數(shù)已經(jīng)返回。
  • 閉包持有對外部函數(shù)作用域的引用,這可能導(dǎo)致內(nèi)存泄漏。
  • 閉包可以在多個函數(shù)之間共享狀態(tài),但需要注意避免意外修改該狀態(tài)。

由于閉包的特殊性質(zhì),它們在 JavaScript 中被廣泛用于實現(xiàn)模塊化、封裝私有變量等。然而,由于閉包可能導(dǎo)致內(nèi)存泄漏等問題,使用時應(yīng)謹慎。

閉包的實現(xiàn)原理

閉包的實現(xiàn)原理基于 JavaScript 的函數(shù)作用域和作用域鏈機制。當(dāng)函數(shù)被定義時,它會創(chuàng)建一個新的作用域,并在該作用域中保存當(dāng)前的變量環(huán)境。當(dāng)函數(shù)執(zhí)行時,它會創(chuàng)建一個新的執(zhí)行環(huán)境,并在該執(zhí)行環(huán)境中保存當(dāng)前的作用域鏈。函數(shù)執(zhí)行完畢后,它會銷毀執(zhí)行環(huán)境和作用域鏈,但作用域中的變量仍然保存在內(nèi)存中。

當(dāng)函數(shù)返回一個內(nèi)部函數(shù)時,內(nèi)部函數(shù)仍然可以訪問外部函數(shù)的作用域和變量,因為其作用域鏈包含外部函數(shù)的作用域鏈。這就創(chuàng)建了一個閉包,使得內(nèi)部函數(shù)可以訪問外部函數(shù)的變量,并且這些變量直到內(nèi)部函數(shù)被銷毀時才會被釋放。

以下是一個演示閉包實現(xiàn)原理的示例:

function outer({
  let x = 10;
  return function inner({
    console.log(x);
  };
}

const innerFn = outer();
innerFn(); // 輸出 10

在這個示例中,outer函數(shù)返回一個內(nèi)部函數(shù)inner,它可以訪問outer函數(shù)中的變量x。在outer函數(shù)執(zhí)行完畢后,變量x仍然保存在內(nèi)存中,因為inner函數(shù)形成了一個閉包,并且可以訪問outer函數(shù)的變量和作用域。

閉包的使用場景

實現(xiàn)私有變量、方法和模塊化

// 模塊化計數(shù)器
const counterModule = (function({
  let count = 0// 私有變量

  function increment(// 私有方法
    count++;
    console.log(`counter value: ${count}`);
  }

  function reset(// 私有方法
    count = 0;
    console.log('counter is reset');
  }

  return { // 暴露公共方法
    increment,
    reset
  }
})();

// 使用模塊
counterModule.increment(); // counter value: 1
counterModule.increment(); // counter value: 2
counterModule.reset(); // counter is reset

在上述代碼中,我們使用立即調(diào)用函數(shù)表達式(IIFE)返回一個包含兩個公共方法incrementreset的對象,這些方法可以從外部訪問。count變量以及incrementreset方法是私有的。這樣,我們可以以模塊化的方式組織代碼,避免全局變量污染,同時保護私有變量和方法免受外部干擾。

實現(xiàn)函數(shù)記憶化

function memoize(fn{
  const cache = {}; // 緩存計算結(jié)果

  return function(...args{
    const key = JSON.stringify(args); // 將參數(shù)轉(zhuǎn)換為緩存鍵

    if (cache[key] === undefined) { // 如果結(jié)果不存在,則計算并緩存
      cache[key] = fn.apply(this, args);
    }

    return cache[key]; // 返回緩存的計算結(jié)果
  };
}

function factorial(n{
  console.log(`calculating ${n} factorial`);
  if (n === 0) {
    return 1;
  }
  return n * factorial(n - 1);
}

const memoizedFactorial = memoize(factorial);

console.log(memoizedFactorial(5)); // calculating 5 factorial 120
console.log(memoizedFactorial(5)); // 120 
console.log(memoizedFactorial(3)); // calculating 3 factorial 6
console.log(memoizedFactorial(3)); // 6

在上述代碼中,我們定義了一個memoize函數(shù),它接受一個函數(shù)fn作為參數(shù),并返回一個新函數(shù),該新函數(shù)緩存fn的計算結(jié)果以避免重復(fù)計算。具體來說,我們使用一個名為cache的對象來存儲計算結(jié)果,并返回一個在外部memoize函數(shù)中引用cache對象和fn函數(shù)的閉包。在閉包中,我們使用JSON.stringify將輸入?yún)?shù)轉(zhuǎn)換為字符串作為緩存鍵,然后檢查結(jié)果是否已在緩存中。如果是,則直接返回;否則,計算結(jié)果并將其保存到緩存中。最后,我們可以使用memoize函數(shù)包裝任何需要記憶化的函數(shù)以避免重復(fù)計算。在上述代碼中,我們使用memoize函數(shù)包裝了一個計算階乘的函數(shù)factorial,并將其命名為memoizedFactorial。我們可以看到,第一次計算一個數(shù)的階乘時,它會輸出正在計算的消息。然而,當(dāng)我們再次計算相同的數(shù)時,它不會輸出消息,因為結(jié)果已經(jīng)被緩存。

避免循環(huán)中的作用域問題

此技術(shù)還可用于避免循環(huán)中的作用域問題。當(dāng)我們在循環(huán)中定義一個函數(shù)時,它可能會引用循環(huán)變量,但由于 JavaScript 的作用域規(guī)則,當(dāng)函數(shù)被調(diào)用時,循環(huán)變量的值可能不是我們期望的。通過使用閉包為循環(huán)的每次迭代創(chuàng)建一個新的作用域,我們可以避免這個問題。

function createFunctions({
  const result = [];

  for (var i = 0; i < 5; i++) {
    result[i] = function(num{
      return function({
        return num;
      };
    }(i);
  }

  return result;
}

const funcs = createFunctions();
console.log(funcs[0]()); // 0
console.log(funcs[1]()); // 1
console.log(funcs[2]()); // 2
console.log(funcs[3]()); // 3
console.log(funcs[4]()); // 4

在上述代碼中,我們定義了一個createFunctions函數(shù),它返回一個包含五個函數(shù)的數(shù)組。這些函數(shù)的目的是返回它們在數(shù)組中的索引。我們使用閉包來避免循環(huán)中的作用域問題。具體來說,我們在循環(huán)中定義一個立即調(diào)用的匿名函數(shù),它接受一個參數(shù)num并返回一個新函數(shù),該新函數(shù)始終返回num。然后,我們立即調(diào)用這個匿名函數(shù),將i作為參數(shù)傳遞,并將返回的函數(shù)保存到數(shù)組result的相應(yīng)位置。由于匿名函數(shù)返回一個新函數(shù),并且這個新函數(shù)引用外部函數(shù)createFunctions中的變量num,每個函數(shù)都會記錄其在數(shù)組中的索引。因此,當(dāng)我們調(diào)用這些函數(shù)時,它們將返回它們在數(shù)組中的索引,而不是循環(huán)變量i的值。最后,我們使用createFunctions函數(shù)創(chuàng)建一個包含五個返回自身索引的函數(shù)的數(shù)組,并分別調(diào)用這些函數(shù),輸出它們的返回值。

在異步編程中保存狀態(tài)

function createIncrementer({
  let count = 0;

  function increment({
    count++;
    console.log(`Count: ${count}`);
  }

  return {
    incrementAsync() {
      setTimeout(() => {
        increment();
      }, 1000);
    }
  };
}

const incrementer = createIncrementer();

incrementer.incrementAsync(); // Count: 1
incrementer.incrementAsync(); // Count: 2
incrementer.incrementAsync(); // Count: 3

在上述代碼中,我們定義了一個createIncrementer函數(shù),它返回一個包含incrementAsync方法的對象。該方法將在 1 秒后調(diào)用內(nèi)部的increment函數(shù)。increment函數(shù)通過閉包訪問在外部函數(shù)createIncrementer中定義的變量count,因此它可以在多次調(diào)用incrementAsync方法之間持續(xù)跟蹤計數(shù)。我們創(chuàng)建了一個incrementer對象,并多次調(diào)用其incrementAsync方法,每次都會在 1 秒后輸出當(dāng)前的計數(shù)值。請注意,在此過程中我們沒有顯式傳遞任何參數(shù),而是使用閉包來維護計數(shù)狀態(tài),從而避免了在異步編程中手動傳遞狀態(tài)的麻煩。

實現(xiàn)函數(shù)柯里化

實現(xiàn)以下代碼的柯里化函數(shù)。

function sum(a, b, c{
  return a + b + c;
}

const curriedSum = curry(sum);

curriedSum(123); // 6
curriedSum(1)(23); // 6
curriedSum(12)(3); // 6
curriedSum(1)(2)(3); // 6

實現(xiàn)高階函數(shù)

計算執(zhí)行時間的高階函數(shù)

function timingDecorator(fn{
  return function({
    console.time("timing");
    const result = fn.apply(thisarguments);
    console.timeEnd("timing");
    return result;
  };
}

const add = function(x, y{
  return x + y;
};

const timingAdd = timingDecorator(add);
console.log(timingAdd(12));

緩存返回結(jié)果的高階函數(shù)

function memoizeDecorator(fn{
  const cache = new Map();
  return function(...args{
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      return cache.get(key);
    }
    const result = fn.apply(this, args);
    cache.set(key, result);
    return result;
  };
}

const fibonacci = function(n{
  if (n < 2return n;
  return fibonacci(n - 1) + fibonacci(n - 2);
};

const memoizeFibonacci = memoizeDecorator(fibonacci);
console.log(memoizeFibonacci(10));

實現(xiàn)延遲執(zhí)行函數(shù)

function delayDecorator(fn, delay{
  return function({
    const args = arguments;
    setTimeout(function({
      fn.apply(this, args);
    }, delay);
  };
}

const sayHello = function(name{
  console.log(`Hello, ${name}!`);
};

const delayedHello = delayDecorator(sayHello, 1000);
delayedHello("John");

實現(xiàn)生成器

function makeGenerator(array{
  let index = 0;
  return function({
    if (index < array.length) {
      return { value: array[index++], donefalse };
    } else {
      return { donetrue };
    }
  };
}

const generator = makeGenerator([123]);

let result = generator();
while (!result.done) {
  console.log(result.value);
  result = generator();
}

在這個示例中,我們定義了一個名為makeGenerator的函數(shù),它接受一個數(shù)組作為參數(shù)并返回一個新函數(shù)。這個新函數(shù)使用閉包在內(nèi)部保存數(shù)組索引,并根據(jù)索引依次返回數(shù)組中的每個元素,直到所有元素都被返回。我們將數(shù)組[1, 2, 3]傳遞給makeGenerator函數(shù),并將返回的函數(shù)賦值給變量generator。然后,我們調(diào)用generator函數(shù)逐個檢索數(shù)組中的元素并將它們輸出到控制臺。這樣,我們可以輕松地使用閉包實現(xiàn)生成器,并以惰性方式逐個生成值,避免了一次性計算所有值所帶來的性能和內(nèi)存消耗問題。同時,使用閉包可以維護函數(shù)的狀態(tài)和作用域,避免全局變量污染和變量沖突等問題。

實現(xiàn)事件監(jiān)聽器

function createEventListener(element, eventName, handler{
  element.addEventListener(eventName, handler);
  return function({
    element.removeEventListener(eventName, handler);
  };
}

const button = document.getElementById("myButton");
const onClick = function({
  console.log("Button clicked!");
};

const removeEventListener = createEventListener(button, "click", onClick);

setTimeout(function({
  removeEventListener();
}, 5000);

在這個示例中,我們定義了一個createEventListener函數(shù),它接受一個 DOM 元素、一個事件名稱和一個事件處理函數(shù)作為參數(shù),并返回一個新函數(shù)。這個新函數(shù)使用閉包在內(nèi)部保存 DOM 元素、事件名稱和事件處理函數(shù),并在執(zhí)行時向 DOM 元素添加一個事件監(jiān)聽器。我們將一個按鈕元素、一個點擊事件處理函數(shù)和事件名稱“click”傳遞給createEventListener函數(shù),并將返回的函數(shù)賦值給removeEventListener。然后,我們在一段時間后通過調(diào)用removeEventListener函數(shù)手動移除事件監(jiān)聽器,以停止響應(yīng)按鈕點擊事件。這樣,我們可以使用閉包輕松實現(xiàn)事件監(jiān)聽器,并靈活控制其生命周期,避免內(nèi)存泄漏和性能問題。使用閉包還允許我們維護函數(shù)的狀態(tài)和作用域,避免全局變量污染和變量沖突。


該文章在 2024/11/6 10:33:26 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

黄频国产免费高清视频,久久不卡精品中文字幕一区,激情五月天AV电影在线观看,欧美国产韩国日本一区二区
中文字幕在线流畅不卡高清 | 曰韩第一页综合久久道 | 亚洲人成禁漫在线观看 | 宅男噜噜69国产精品观看 | 亚洲日韩欧美一区 | 亚洲第一区欧美国产综合86 |