# 闭包
参考资料 视频1 (opens new window) 视频2 (opens new window)
# 1.什么是闭包
先看一段代码
function bag(){
var book = '背包里的书';
}
console.log(book);
// book is not defined
执行上下文:全局环境、函数环境、Eval环境
关于执行上下文,详细情况请看 作用域 文档
现在想要在全局环境下调用book,该怎么做?
当想要在全局环境下调用函数环境的内部变量的想法,就叫做闭包
(全局作用域下调用函数作用域的内部变量或函数)
function bag(){
var book = '背包里的书';
function bagTag(){
// 在这里,是能够输出book的值的
console.log(book);
}
return bagTag;
}
var result = bag();
result(); // 成功输出了book
闭包就是bagTag,能够读取其他函数内部变量的函数
闭包最大的特点:可以记住诞生的环境,比如bagTag记住了其诞生的环境是bag,所以在bagTag中可以得到bag中的内部变量
# 2.闭包的用途
# 2.1 量封装在函数中,不暴露在全局作用域中
读取函数内部的变量,这些变量始终在内存中
// 不使用闭包的计数器
var start = 0; // 变量暴露在全局作用域下
function count(){
return start++;
}
for(var i = 0; i < 10; i++){
console.log(count());
}
// 使用闭包的计数器
function count(){
var start = 0; // 变量在函数作用域内部
function rt() {
return start++;
}
return rt; // 返回闭包函数
}
var result = count();
for(var i = 0; i < 10; i++){
console.log(result());
}
// 因为返回的闭包函数是存储在 result 这个对象之中
// 所以会存在内存泄漏、占用的问题,可以在调取完变量之后,将其置空
result = null;
# 2.2 闭包能够封装对象的私有属性和方法
function Person(name){
var age; //私有属性
// 私有方法——闭包,
function setAge(n){
age = n;
}
function getAge(){
return age;
}
return {
name: name,
setAge: setAge,
getAge: getAge
}
}
var p1 = Person('张三');
p1.setAge(18);
console.log(p1.getAge());
p1 = null;
# 3.闭包注意点
- 使用闭包使得函数中的变量始终在内存中,内存消耗很大,所以不能滥用闭包,否则会造成页面的性能问题,在IE中可能导致内存泄漏
# 4.闭包总结
闭包需要三个条件:
- 函数嵌套
- 访问所在的作用域
- 在所在作用域外被调用
每个父函数调用完成,都会形成新的闭包,父函数中的变量会始终在内存中,相当于缓存,小心内存的消耗问题
# 5.立即执行函数 IIFE
在JavaScript中 () 跟在函数后面,表示调用函数 fn()
立即执行函数: 需要定义函数之后,立即调用该函数
如果function关键字出现在行首,一律解释成函数声明语句,函数声明语句需要函数名,为了匿名函数没有函数名,所以可以使用()将行首取代掉
for(var i = 0; i < 5; i++) {
setTimeout(function(){
console.log(i++);
},4000);
}
// 因为 setTimeout 是异步操作,所以会被列入任务队列中等待同步操作执行完毕再执行
// 输出结果是 5 6 7 8 9
// 但是 如果要输出 0 1 2 3 4,该怎么修改?
// 那就是要让setTimeout操作立即执行
for(var i = 0; i < 5; i++) {
(function(x){
setTimeout(function(){
console.log(x++);
},4000);
})(i);
}
// (function(x){})(i)
// (function(x){}(i)) W3C建议该种
# 6.闭包的十种场景
# 6.1 返回值
最常见
var fn = function(){
var name = '张三';
return function(){
return name;
}
}
var fnname = fn();
console.log(fnname());
# 6.2 函数赋值
一种变形的形式是将内部函数赋值给一个外部的变量
var fn2;
var fn = function(){
var name = '张三';
var a = function () {
return name;
}
fn2 = a;
}
fn();
console.log(fn2());
# 6.3 函数参数
var fn2 = function (f) {
console.log(f());
}
function fn(){
var name = '张三';
var a = function () {
return name;
}
fn2(a);
}
fn();
# 6.4 IIFE
function fn2(f){
console.log(f());
}
(function () {
var name = '张三';
var a = function () {
return name;
}
fn2(a);
})();
# 6.5 循环赋值
function foo(){
var arr = [];
for(var i = 0; i < 10; i++){
(function(i){
arr[i] = function(){
return i;
}
})(i);
}
}
var bar = foo();
# 6.6 getter 和 setter
var getValue,setValue;
(function(){
var num = 0;
getValue = function(){
return num;
}
setValue = function(v){
if(typeof v === 'number'){
num = v;
}
}
})();
setValue(233);
console.log(getValue());
# 6.7 迭代器
计数器
var add = function () {
var num = 0;
var name = ['张三', '李四', '王五']
return function () {
return name[num++];
}
}();
console.log(add());
console.log(add());
console.log(add());
# 6.8 区分首次
var firstLoad = (function(){
var list = [];
return function(id){
if(list.indexOf(id) >= 0){
return false;
} else {
list.push(id);
return true;
}
}
})();
console.log(firstLoad(10);)
console.log(firstLoad(10);)
# 6.9 缓存机制
var mult = function () {
//缓存对象
var cache = {};
var calculate = function () {
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum = sum + i;
}
return sum;
}
return function () {
var args = Array.prototype.join.call(arguments,',');
if(args in cache){
return chche[args];
}
return cache[args] = calculate.apply(null,arugments);
}
}();
console.log(mult(1,2,3,1,1,2,3));