javascript中的函数(进阶3)

101 阅读3分钟

深入理解闭包

什么是闭包?

闭包就是能读取其他函数内部变量的函数。闭包的作用是为了使函数部作用域能够访问到函数部变量。

本质上:闭包就是函数内部和函数外部链接的一座桥梁

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中可能会导致内存的泄露。

  • 每个父函数调用完成后,都会形成新的闭包,父函数中的变量始终会在内存中,相当于缓存起来了,小心内存的消耗。

总结

闭包需要三个条件

  1. 函数有嵌套
  2. 访问所在的作用域
  3. 在所在的作用域被调用

立即执行函数(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');