第十五章 閉包

匿名函數(shù)這里就不做介紹了

閉包

什么是閉包,可以把閉包理解為,一個函數(shù)可以訪問另外一個函數(shù)中的變量。閉包中的變量會被一直保存在內(nèi)存中,可以避免使用全局變量。(全局變量污染導(dǎo)致應(yīng)用不可預(yù)測,所以推薦使用私有的,封裝的局部變量)

一個栗子,做一個累加器:

var num = 100;

function sum() {
    num++;
}

sum();
console.log(num);   // 101
sum();
console.log(num);   // 102
sum();
console.log(num);   // 103

在全局環(huán)境下的變量實現(xiàn)累加很容易,但是如果要局部變量實現(xiàn)累加呢?

function sum() {
    var num = 100;
    num++;
    return num;
}


console.log(sum());   // 101
console.log(sum());   // 101
console.log(sum());   // 101

每一次函數(shù)執(zhí)行,num都被初始化。所以無法完成累加。正確寫法如下:

function foo() {
    var age = 100;
    return function () {
        age++;
        return age;
    };
}

var f = foo();

console.log(f());
// 101
console.log(f());
// 102
console.log(f());
// 103

以上方法實現(xiàn)了局部變量駐留在內(nèi)存中而實現(xiàn)累加。以上方法也就是閉包,閉包作用域返回的局部變量資源不會被立刻銷毀,所以非必要情況不要使用閉包,會造成資源問題。如果閉包使用結(jié)束,可以用f=null可以解除引用,等待垃圾回收。

循環(huán)里的匿名函數(shù)取值問題

function foo () {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = function () {
            return i;
        };
    }
    return arr;
}

var f = foo();

console.log(f[0]);
// demo.js:15 ? () { return i;} 返回了是個匿名函數(shù)
for(var i = 0; i < 10; i++) {
    console.log(f[i]());
}
// 打印出10個10

打印出10個10,可是我們想要的是0-9十個數(shù)字。實際上,函數(shù)執(zhí)行結(jié)束的時候,循環(huán)已經(jīng)執(zhí)行完畢了。所以,i最終的值為i++,也就是9++,是10。那么怎么解決這個問題呢

方法1(去掉匿名函數(shù)):

function foo () {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = i;
    }
    return arr;
}

var f = foo();
console.log(f);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

方法2:

function foo () {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = (function (num) {      //通過自我及時執(zhí)行
            return num;
        })(i);
    }
    return arr;
}

var f = foo();

console.log(f);
// [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

方法3(常用):

function foo () {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr[i] = function (num) {
            return function () {
                return num;     //內(nèi)存中常駐一個變量
            };
        }(i);
    }
    return arr;
}

var f = foo();

for(var i = 0; i < 10; i++) {
    console.log(f[i]());
}
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9

this對象

閉包在運行的時候this指向window

var obj = {
    getThis: function () {
        return function () {
            return this;
        };
    }
};

console.log(obj.getThis()());
// window 函數(shù)返回的是一個函數(shù),函數(shù)再括號執(zhí)行就是window打點調(diào)用。
var user = 'the window';
var obj = {
    user: 'the obj',
    getUser: function () {
        return function () {
            return this.user;
        };
    }
};


console.log(obj.getUser()());
// the window

如果想改變閉包種this指向,有兩種方法:

1. call/apply

var user = 'the window';
var obj = {
    user: 'the obj',
    getUser: function () {
        return function () {
            return this.user;
        };
    }
};


console.log(obj.getUser().call(obj));
// the obj

2. 作用域

var user = 'the window';
var obj = {
    user: 'the obj',
    getUser: function () {
        var that = this;
        return function () {
            return that.user;
        };
    }
};


console.log(obj.getUser()());
// the obj

內(nèi)存泄露

閉包會導(dǎo)致內(nèi)存泄露問題,也就是無法銷毀駐留在內(nèi)存中的元素。

window.onload = function () {
    function box() {
        var div = document.getElementById('box');
        div.onclick = function () {
            console.log(div.innerHTML); // 123
        };
        console.log(div);   // <div id="box">123</div>
    }
    box();
};

解除引用

window.onload = function () {
    function box() {
        var div = document.getElementById('box');
        var text = div.innerHTML;
        div.onclick = function () {
            console.log(text); // 123
        };
        div = null;
        console.log(div);   // null
    }
    box();
};

模仿塊級作用域

js沒有塊級作用域概念

function f1() {
    for (var i = 0; i < 5; i++) {

    }
    console.log(i); //5 for循環(huán)外面依然可以調(diào)用到i變量
    var i;
    console.log(i); // 5 依然是5,再次聲明不賦值無效
}

f1(); // 5

使用塊級作用域

function f1() {
    (function(){    // 立即執(zhí)行函數(shù)構(gòu)成私有作用域
        for (var i = 0; i < 5; i++) {
            console.log(i);
        }
    })();   // 出去這個作用域,變量立刻被銷毀。
    console.log(i);
}

f1(); 
// 0 1 2 3 4  demo.js:7 Uncaught ReferenceError: i is not defined

使用了塊級作用域,匿名函數(shù)中定義的任何變量,都會在執(zhí)行結(jié)束的時候被銷毀。這種技術(shù)經(jīng)常在全局作用域中被用在函數(shù)外部。從而限制向全局作用域中添加過多的變量和函數(shù)。一般來說,我們都應(yīng)該盡可能少向全局作用局添加函數(shù)和變量。在大型項目中,多人開發(fā),過多的全局變量和函數(shù)很容易命名沖突,引起災(zāi)難性后果。如果采用塊級作用域,每個開發(fā)者使用自己的變量,不必?fù)?dān)心影響全局作用域。

私有作用域演示:

(function () {
    // 全局私有作用域
    var age = 100;
    alert(age);
})();

alert(age); //a ge is not defined

相當(dāng)于:

var age = 100;
alert(age); //100
age = null;
alert(age); //null

私有變量

js沒有私有屬性概念,所有的屬性都是公有的。但是,又一個私有變量概念。任何函數(shù)在函數(shù)中定義的變量,都可以認(rèn)為是私有變量,因為不能再函數(shù)的外部訪問這些變量。

function f1() {
    var age = 100;  // 私有變量 外部無法訪問
}
function Fn() {
    this.age = 100;     // 公有屬性
    this.run = function () {        //公有方法
        return 'runing';
    };
}

var f = new Fn();
console.log(f.age);
// 100
console.log(f.run());
// runing
function Fn() {
    var age = 100;  //私有屬性
    function run() {    //私有方法
        return 'runing';
    };
}

var f = new Fn();
console.log(f.age);
// undefined
console.log(f.run());
// f.run is not a function

訪問方法

function Fn() {
    var age = 100;
    function run() {
        return 'runing';
    }
    this.publicGo = function () {   //對外可見的公共接口
        return age + run();
    };
    this.getAge = function () {   //對外可見的公共接口
        return age;
    };
}

var f = new Fn();
console.log(f.publicGo());
// 100runing
console.log(f.getAge());
// 100

靜態(tài)私有變量

共享于不同對象的屬性:

(function () {
    var user = '';  //私有變量
    Box = function (value) {
        user = value;
    };
    Box.prototype.getUser = function () {
        return user;
    }
})();

var xiaoming = new Box('xiaoming');
console.log(xiaoming.getUser());    //xiaoming
var xiaobai = new Box('xiaobai');
console.log(xiaobai.getUser());     //xiaobai
console.log(xiaoming.getUser());    //xiaobai

模塊模式

之前采用的都是構(gòu)造函數(shù)方式來創(chuàng)建私有變量和特權(quán)方法,那么對象字面量方式采用模塊方式來創(chuàng)建。

// 單例對象   就是永遠(yuǎn)只實例化一次,就是字面量方式聲明對象
var obj = {     // 第一次實例化,無法第二次實例化,那么就是單例
    name: 'xiaoming',
    run: function () {
        return 'is runing..';
    }
}

字面量方式私有化變量函數(shù)

var obj = function () {
    var name = 'xiaoming';
    function run() {
        return 'is runing';
    }
    return {
        getName: function () {
            return name + ' ' + run();
        }
    };
}();

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

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

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