深入理解闭包
什么是闭包?
闭包就是能读取其他函数内部变量的函数。闭包的作用是为了使函数外部作用域能够访问到函数内部变量。
本质上:闭包就是函数内部和函数外部链接的一座桥梁
var a = 123;
function fn1(){
console.log(a);
var b = 234;
function fn2(){//fn2就是一个闭包
console.log(b);
}
retutn fn2;
}
var res = fn1();//fn2
console.log(b);//error
res();//234
闭包的特点
就是他可以记住诞生的环境,比如fn2记住了他诞生的环境是fn1,所以fn2可以得到fn1中的内部变量。
闭包的用途
1.计数器
作用:读取函数内部的变量,这些变量始终在内存中
function a(){
var start = 0;
function b(){
return start++;
}
return b;
}
var inc = a();//b
console.log(inc());
console.log(inc());
console.log(inc());
//释放当前变量
inc = null;
注意:在使用闭包时小心内存的泄露。
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('john');
p1.setAge(18);
console.log(p1.getAge());
//置空
p1 = null;
闭包的注意点
-
使用闭包使得函数中的变量始终在内存中,内存消耗巨大,所以我们不能滥用闭包,否则会造成页面的性能问题。在ie中可能会导致内存的泄露。
-
每个父函数调用完成后,都会形成新的闭包,父函数中的变量始终会在内存中,相当于缓存起来了,小心内存的消耗。
总结
闭包需要三个条件
- 函数有嵌套
- 访问所在的作用域
- 在所在的作用域外被调用
立即执行函数(IIFE)
**()**是表达式,跟在函数后面,表示调用函数。
定义函数之后,立即调用,这种函数称为立即执行函数。
**注意:**如果function出现在行首,一律解释成声明语句。
function(){}();//error
常用写法
(function(){
//...
})();
(function(){
//...
}());
通常情况下写自执行函数时,为了避免漏写;可以这样表示。
!(function(){
//...
})()
立即执行函数的应用
可以封装私有属性,同时可以减少对全局变量的污染。
var res=!(function(){
/*私有属性*/
var count = 0;
return function(){
return ++count;
}
})()
console.log(res());
对循环和闭包的错误理解
function foo(){
var arr=[];
for (var i=0;i<10;i++){
//此处的括号并不是作用域
arr[i]=function(){
return i;
}
}
return arr;//arr是一个函数数组
}
var bar = foo();
bar[0]();//10
bar[1]();//10
bar[2]();//10
分析
遍历时没有将i保存在内存中,仅仅将函数赋值给每个元素。
通过for循环,形成了10个函数,但是函数还没有被调用,此时的i为9+1=10,当一一调用这10个函数时,返回的i永远是10。
解决方案1
function foo(){
var arr=[];
for (var i=0;i<10;i++;){
arr[i]=(function(n){
return function(){
return n;
};
})(i);
}
return arr;
}
var bar = foo();
bar[0]();//0
bar[1]();//1
bar[2]();//2
分析
通过for循环,当每一个函数形成时,都完成了自调用(定义函数,立即执行),此时的i作为实参传入,闭包是可以访问到的。
解决方案2
function foo(){
var arr=[];
for (var i=0;i<10;i++;){
(function(n){
arr[n]=function(){
return n;
}
})(i);
}
return arr;
}
var bar = foo();
bar[0]();//0
bar[1]();//1
bar[2]();//2
解决方案3
es6中的let块级作用域
function foo(){
var arr=[];
for (let i=0;i<10;i++;){
//此时为一个块级作用域,每次的i都被保存在内存中
arr[i]=function(){
return i;
}
}
return arr;
}
var bar = foo();
bar[0]();//0
bar[1]();//1
bar[2]();//2
闭包的10种表示形式
返回值
var fn = function(){
var name = 'john';
return function(){
return name;
}
}
var fnc = fn();
fnc();//john
函数赋值
一种变形形式,将内部的函数赋值给了一个外部的变量
var fn2;
var fn = function(){
var name = 'john';
var a = function(){
return name;
};
fn2 = a;
};
fn();
fn2();//john
函数参数
var fn2 = function(f){
console.log(f());
};
function fn(){
var name='john';
var a = function(){
return name;
};
fn2(a);
}
fn();//john
IIFE
function fn2(f){
console.log(f());
}
(function(){
var name='john';
var a = function(){
return name;
};
fn2(a);
})();//john
循环赋值
function foo(){
var arr=[];
for (var i=0;i<10;i++){
(function(n){
arr[n]=function(){
return n;
}
})(i);
}
return arr;//函数数组
}
var bar = foo();
console.log(bar[3]());
getter&setter
getter和setter函数,来将要操作的变量保存在函数内部,防止暴露在外部。
var getValue,setValue;
(function(){
var num = 0;
getValue = function(){
return num;
};
setValue = function(v){
if(typeof v==='number'){
num = v;
}
};
})();
console.log(getValue());
setValue(10);
console.log(getValue());
迭代器
计数器
var add = function(){
var num = 0;
return function(){
return ++num;
}
}();
console.log(add());
console.log(add());
console.log(add());
迭代器
function setUP(arr){
var i = 0;
return function(){
return arr[i++];
}
}
var next = setUP(['john','jack','tom']);
console.log(next());
console.log(next());
console.log(next());
区分首次
var firstLoad = (function(){
var list = [];
return function(id){
if(list.indexOf(id)>=0){
//存在id
return false;
}else{
//首次调用
list.push(id);
return true;
}
}
})();
firstLoad(10);
firstLoad(10);
模拟缓存机制
未产生缓存的
function mult(arguments){
var sum = 0;
for(var i = 0;i<arguments.length;i++){
sum = sum+arguments[i];
}
return sum;
}
console.log(mult(1,2,3,1,1,2,3));//13
console.log(mult(1,2,3,1,1,2,3));//13
有缓存机制的
//模拟一个对象的key 看对象中是否有相同的key,如果有则直接获取value返回
{
key:value
1,2,3,1,1,2,3:13,
}
var mult = function(){
//缓存对象
var cache = {};
var calculate = function(){
var sum = 0;
for(var i = 0;i<arguments.length;i++){
sum = sum+arguments[i];
}
return sum;//获取value
}
return function(){
//对cache对象进行操作
var args=Array.prototype.join.call(arguments,',');//形成数组
if (args in cache){
return cache[args];
}
return cache[args]=calculate.apply(null,arguments);//结果
}
}();
console.log(mult(1,2,3,1,1,2,3));
image图片对象上报
//Image()
//new Image()进行数据上报
var report =function (src){
var img = new Image();
img.src = src;
}
report('http://xxx.com/getUserInfo');
注意:低版本浏览器在进行数据上报会丢失30%左右的数据
使用闭包来做图片的上传
var report =function (src){
var imgs = [];
return function(src){
var img = new Image();
imgs.push(img);
img.src = src;
}
}();
report('http://xxx.com/getUserInfo');