JavaScript閉包使用場景

JavaScript閉包使用場景

閉包就是外層函數將內層函數返回出去,並且內層函數執行時帶着外層函數的作用域,可以使用外層函數內部的變量,這些變量始終保存在內存中
本質:閉包相當於橋樑,連接函數內核函數外。
特點:保存函數的誕生環境
使用原因:函數外想要獲取函數內部的變量,通過閉包形式
注意事項:閉包會將作用域保存在內存中,不用時需要將變量設置爲null,防止內存泄漏。

閉包的形式

返回值形式

函數中返回一個函數

// 返回值:直接在函數中返回外層函數的變量
function fn() {
    var age = 18;
    return function () {
        return age;
    }
}
var func = fn();
console.log(func()); // 18

函數賦值形式

將內部函數賦值給外部變量

// 函數賦值
var fn2;
function fn() {
    var age = 18;
    fn2 = function () {
        console.log(age);
    }
};
fn2();  // 18

函數參數形式

將內層函數當做參數傳入全局定義的函數中

function fnc(fn) {
    age = fn();
    console.log("已經"+age);
}
function fn() {
    var age = 18;
    fn2 = function () {
        return age;
    }
    fnc(fn2);
};
fn(); // 已經18

IIFE自執行函數

使用自執行函數,省去調用過程
上文自執行函數介紹

function fnc(fn) {
    age = fn();
    console.log("已經"+age);
}
(function fn() {
    var age = 18;
    fn2 = function () {
        return age;
    }
    fnc(fn2);
})();

相關代碼:

循環賦值問題:查看代碼
封裝私有方法:查看代碼
計數器和迭代器:查看代碼
實現緩存機制:查看代碼

閉包用途

計數器

計數器
通過閉包製作計數器
作用:讀取函數內部的變量,這些變量始終保存在內存中

function fn() {
    count = 0;
    return function () {
        ++count;
        console.log("這是第"+count+"個函數;");
    }
}
var ins = fn();
ins();  // 這是第1個函數;
ins();  // 這是第2個函數;
ins = null;  // 防止內存泄漏

迭代器

類似於計數器

// 傳入一個數組,返回一個迭代器
function initIter(iter) {
    arr = iter;
    count = 0;
    return {
        next:function () {
        if(count < arr.length){
            return {
                value:arr[count++],
                done:false
            }

        }
        return {
                value:undefined,
                done:true
            }
    }
    }
}
var arr = ["apple","banana","orange"];
var it = initIter(arr);
console.log(it.next());  // {done: false,value: "apple"}
console.log(it.next());  // {done: false,value: "banana"}
console.log(it.next());  // {done: false,value: "orange"}
console.log(it.next());  // {done: true,value: undefined}

自定義屬性

通過設置函數屬性,來保存信息

function add() {
    return add.count++;
}
add.count = 0
console.log(add());  // 0
console.log(add());  // 1

封裝私有屬性和方法

封裝私有方法

// 閉包存儲私有屬性和方法
function Person() {
    var age;
    var setAge = function (n) {
        if(Number(n)){
            age = n;
        }
        return age;
    };
    var getAge = function () {
        return age;
    };
    return {
        setAge:setAge,
        getAge:getAge
    }
}
// 函數只能使用其提供的兩個接口來操作age的值
var p1 = Person();
p1.setAge(18);
console.log(p1.getAge());
p1 = null;

閉包條件:函數嵌套、訪問所在的作用域、在所在的作用域外被調用

自執行函數IIFE

自執行函數介紹
自執行函數,通過自執行函數,可以省去閉包初始化過程。
同樣,自執行函數內部作用域和外部隔開的,不屬於全局作用域,而是函數作用域

// 自執行函數 IIFE
// 方法一:作爲函數表達式
(function () {

})();
// 方法二:作爲匿名函數  
(function () { 
    
 }()); 
//注意加分號

例如:封裝私有屬性和方法

// 閉包存儲私有屬性和方法
var p1 = (function Person() {
    var age;
    var setAge = function (n) {
        if(Number(n)){
            age = n;
        }
        return age;
    };
    var getAge = function () {
        return age;
    };
    return {
        setAge:setAge,
        getAge:getAge
    }
})();
p1.setAge(18);
console.log(p1.getAge());
p1 = null;

緩存機制

緩存機制介紹
沒有緩存機制時,每次使用都會重複調用函數,影響性能

// 沒有緩存機制時
function factorial(n) {
    if(n < 0){
        throw RangeError("負數沒有階乘");
    }
    var res = 1;
    for(var i = 1;i <= n;i++){
        res *= i;
    }
    return res;
}
console.log(factorial(0));
console.log(factorial(1));
console.log(factorial(2));

添加緩存機制後

function factorial() {
    var fac_arr = {};  // 緩存:用來存儲計算結果
    // 計算階乘函數
    function calculate(n) {
        if(n < 0){
        throw RangeError("負數沒有階乘");
        }
        var res = 1;
        for(var i = 1;i <= n;i++){
            res *= i;
        }
        return res;
    }
    return function (n) {
        // 判斷之前是否計算過,計算過直接取值,沒有則計算,存儲到緩存中
        if(n in fac_arr){
            return fac_arr[n];
        }else{
            var res = calculate(n);
            fac_arr[n] = res;
            return res;
        }
      }
}
var fac = factorial()
console.log(fac(0));  // 結果:1         fac_arr值{0: 1}
console.log(fac(10)); // 結果:3628800   fac_arr值{0: 1, 10: 3628800}
console.log(fac(10)); // 結果:3628800   fac_arr值{0: 1, 10: 3628800}

這個階乘函數可以,更進一步,每計算一次,緩存一次

function factorial() {
    var fac_arr = [1,];  // 緩存:記錄0-最大階乘之間所有階乘
    var max_fac = 0;  // 記錄最大階乘
    // 計算階乘函數
    function calculate(n) {
        console.log(fac_arr);
        if(n < 0){  
        return RangeError("負數沒有階乘");
        }else if(n <= max_fac){   // 獲取的值在該範圍內,直接返回
            return fac_arr[n];
        }else{
            for(var i = max_fac;i < n;i++){
                fac_arr.push(fac_arr[i] * (i+1));
            }
            max_fac = n;
        }
        return fac_arr[n];
    }
    return calculate
}
var fac = factorial()
console.log(fac(20)); // 將1,2,3.。。20階乘全部計算存儲下來
console.log(fac(10));  // 直接從緩存中獲取
console.log(fac(15));  // 直接從緩存中獲取

優點:計算一次大的階乘之後,0-20之間所有階乘都存儲下來。下次想要0-20之間的階乘直接獲取,不用重複計算。
缺點:當階乘過大時,緩存存儲的階乘太多,消耗容量

循環閉包的錯誤結果

循環賦值
當我們在循環中使用閉包時,注意閉包的作用域

<ul id="myList">
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
    <li>你好</li>
</ul>
<script>
    //點擊li標籤時,獲取li索引
    var myList = document.getElementById("myList");
    var lis = myList.children;  // 獲取所有的li標籤
    // 循環li標籤
    for(var i = 0;i < lis.length;i++){
        lis[i].onclick = function () {
            this.innerHTML = i;
            console.log(i);
        }
    }
    console.log(i); // 5
</script>

運行上述代碼時,會發現無論點擊哪個li標籤,返回的都是索引5.
原因:
1.onclick點擊事件設置後不會立即執行,觸發點擊事件時纔會調用事件
2.在事件中查找i變量,因爲js中{}不爲獨立的作用域,每次循環使用同一個i,所以循環結束後i變量值爲5。 即每次循環的i都是同一個i,相當於

// 循環li標籤
var i;
for(i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        this.innerHTML = i;
        console.log(i);
    }
}
console.log(i); // 5

解決辦法一:let變量

使用es6的let創建變量

//點擊li標籤時,獲取li索引
var myList = document.getElementById("myList");
var lis = myList.children;  // 獲取所有的li標籤
// 循環li標籤
for(let i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        this.innerHTML = i;
        console.log(i);
    }
}
console.log(i); //Uncaught ReferenceError: i is not defined

let變量會讓{}變爲獨立作用域,即每循環一次,生成一個單獨的作用域,每個作用域中都會創建一個i。
查找變量時,每個i的值不同。循環外部也不能訪問i變量。

解決辦法二:閉包

因爲閉包可以保存數值,我們可以通過閉包來保存i的值

//點擊li標籤時,獲取li索引
var myList = document.getElementById("myList");
var lis = myList.children;  // 獲取所有的li標籤
// 循環li標籤
for(let i = 0;i < lis.length;i++){
    lis[i].onclick = function () {
        return (function (n) {
            lis[i].innerHTML = i;
            console.log(n);
        })(i);
    }
}

在js中,{}不能算作獨立作用域,函數內部才能算獨立作用域。(除了es6聲明let、const等)
所以使用自執行函數,並且將i值傳入函數,保存下來。

圖片上報

因爲img圖片對象,在src獲取url後,會下載圖片,但是當前函數執行完畢後,變量會被銷燬,導致丟失圖片數據。
使用閉包解決

function report2() {
    var imgs = [];
    return function (src) {
        var img = new Image();
        img.src = src;
        imgs.push(img);
        return img;
    }
}
var rp = report2();
var img2 = rp("../photo.jpg");
App.appendChild(img2);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章