js閉包及閉包的經典使用場景

时光独醒發表於2024-06-25

一、閉包的定義

1、什麼是閉包

閉包是指一個函式可以訪問它定義時所在的詞法作用域以及全域性作用域中的變數

2、閉包的特點:

(1)讓外部訪問函式內部變數變成可能
(2)變數會常駐在記憶體中
(3)可以避免使用全域性變數,防止全域性變數汙染;

3、閉包的好處和壞處

閉包的好處是:

(1)延伸變數的使用範圍

(2)避免全域性變數汙染

(3)私有成員的錯在。

缺點是:

(1)由於變數無法回收,使用頻繁,記憶體損耗太大

(2)記憶體洩漏的風險

二、閉包的使用場景有:

1.返回值(最常用)

//1.返回值 最常用的
function fn(){
    var name="hello";
    return function(){
        return name;
    }
}
var fnc = fn();
console.log(fnc())//hello

這個很好理解就是以閉包的形式將 name 返回。

2.函式賦值

var fn2;
function fn(){
    var name="hello";
    //將函式賦值給fn2
    fn2 = function(){
        return name;
    }
}
fn()//要先執行進行賦值,
console.log(fn2())//執行輸出fn2

在閉包裡面給fn2函式設定值,閉包的形式把name屬性記憶下來,執行會輸出 hello。

3.函式引數

function fn(){
    var name="hello";
    return function callback(){
        return name;
    }
}
var fn1 = fn()//執行函式將返回值(callback函式)賦值給fn1,
 
function fn2(f){
    //將函式作為引數傳入
    console.log(f());//執行函式,並輸出
}
fn2(fn1)//執行輸出fn2

用閉包返回一個函式,把此函式作為另一個函式的引數,在另一個函式里面執行這個函式,最終輸出 hello

4.IIFE(自執行函式)

(function(){
    var name="hello";
    var fn1= function(){
        return name;
    }
    //直接在自執行函式里面呼叫fn2,將fn1作為引數傳入
    fn2(fn1);
})()
function fn2(f){
    //將函式作為引數傳入
    console.log(f());//執行函式,並輸出
}

直接在自執行函式里面將封裝的函式fn1傳給fn2,作為引數呼叫同樣可以獲得結果 hello

5.迴圈賦值

//每秒執行1次,分別輸出1-10
for(var i=1;i<=10;i++){
    (function(j){
        //j來接收
        setTimeout(function(){
            console.log(j);
        },j*1000);
    })(i)//i作為實參傳入
}

如果不採用閉包的話,會有不一樣的情況。

6.getter和setter

function fn(){
    var name='hello'
    setName=function(n){
        name = n;
    }
    getName=function(){
        return name;
    }

    //將setName,getName作為物件的屬性返回
    return {
        setName:setName,
        getName:getName
    }
}
var fn1 = fn();//返回物件,屬性setName和getName是兩個函式
console.log(fn1.getName());//getter
fn1.setName('world');//setter修改閉包裡面的name
console.log(fn1.getName());//getter

第一次輸出 hello 用setter以後再輸出 world ,這樣做可以封裝成公共方法,防止不想暴露的屬性和函式暴露在外部。

7.迭代器(執行一次函式往下取一個值)

var arr =['aa','bb','cc'];
function incre(arr){
    var i=0;
    return function(){
        //這個函式每次被執行都返回陣列arr中 i下標對應的元素
         return arr[i++] || '陣列值已經遍歷完';
    }
}
var next = incre(arr);
console.log(next());//aa
console.log(next());//bb
console.log(next());//cc
console.log(next());//陣列值已經遍歷完

8.首次區分(相同的引數,函式不會重複執行)

var fn = (function(){
 var arr=[];//用來快取的陣列
 return function(val){
     if(arr.indexOf(val)==-1){//快取中沒有則表示需要執行
         arr.push(val);//將引數push到快取陣列中
         console.log('函式被執行了',arr);
         //這裡寫想要執行的函式
     }else{
         console.log('此次函式不需要執行');
     }
     console.log('函式呼叫完列印一下,方便檢視已快取的陣列:',arr);
 }
})();

fn(10);
fn(10);
fn(1000);
fn(200);
fn(1000);

執行結果如下:

      

可以明顯的看到首次執行的會被存起來,再次執行直接取。

9.快取

/比如求和操作,如果沒有快取,每次呼叫都要重複計算,採用快取已經執行過的去查詢,查詢到了就直接返回,不需要重新計算    
var fn=(function(){
  var cache={};//快取物件
  var calc=function(arr){//計算函式
      var sum=0;
      //求和
      for(var i=0;i<arr.length;i++){
          sum+=arr[i];
      }
      return sum;
  }

  return function(){
      var args = Array.prototype.slice.call(arguments,0);//arguments轉換成陣列
      var key=args.join(",");//將args用逗號連線成字串
      var result , tSum = cache[key];
      if(tSum){//如果快取有   
          console.log('從快取中取:',cache)//列印方便檢視
          result = tSum;
      }else{
          //重新計算,並存入快取同時賦值給result
          result = cache[key]=calc(args);
          console.log('存入快取:',cache)//列印方便檢視
      }
      return result;
  }
})();
fn(1,2,3,4,5);
fn(1,2,3,4,5);
fn(1,2,3,4,5,6);
fn(1,2,3,4,5,8);
fn(1,2,3,4,5,6);

輸出結果:

    

10.事件防抖

/*
* fn [function] 需要防抖的函式
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null    //藉助閉包
    return function() {
        if(timer){
            clearTimeout(timer) //進入該分支語句,說明當前正在一個計時過程中,並且又觸發了相同事件。所以要取消當前的計時,重新開始計時
            timer = setTimeOut(fn,delay) 
        }else{
            timer = setTimeOut(fn,delay) // 進入該分支說明當前並沒有在計時,那麼就開始一個計時
        }
    }
}