学习闭包前请先了解
闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。所谓的闭包就是一个具有封闭的对外不公开的、包裹结构,或空间。
在js 中函数可以构成闭包,一般函数是一个代码结构的封闭结构,即包裹的特性,同时根据作用域规则,只允许函数访问外部的数据,外部无法访问函数内部的数据,即封闭的对外不公开的特性,因此说函数可以构成闭包。
概括:闭包就是一个具有封闭与包裹功能的结构。函数可以构成闭包。函数内部定义的数据函数外部无法访问,即函数具有封闭性;函数可以封装代码即具有包裹性,所以函数可以构成闭包。
函数就可以构成闭包,要解决的问题就是访问到函数内部的数据
// 最简单的方法使用return,但是不可第二次使用。
function foo(){
var num=123;
return num;
}
var res = foo();
console.log(res);// => 123;
function foo(){
var num=Math.random();
return num;
}
var res1=foo();
var res2=foo();
console.log(res1+”\n”+res2);
在函数内的数据,不能直接在函数外被访问,那么在函数内部如果定义一个函数,那么再这个内部函数中是可以直接访问的
function foo(){
var num=Math.random();
function func(){
return num;
}
return func;
}
var f=foo();//f 可以直接访问这个num
var res1=f();
var res2=f();
console.log(res1+”\n”+res2);
我们使用前面学习的绘制作用域链结构图的方法来绘制闭包的作用域链结构图,如下
//0级链无法访问1级链的数据,所以间接的0级链操作 2级链的函数,来访问1级链的数据。
例子:
要求通过外部访问到 name
原代码
function foo(){
var o={name:’jim’};
}
实现:
function foo(){
var o={name:’jim’};
return function(){
return o;
}
}
1.如何获得超过一个数据(要求在外面访问这俩个,想访问那个就可访问那个)
functin foo(){
var num1=Math.random();
var num2=Math.random();
}
解决方法:
functin foo(){
var num1=Math.random();
var num2=Math.random();
return {
Num1:function(){
return num1
},
Num2:function(){
return num2
}
}
}
2.如何完成读取一个数据和修改这个数据
functin foo(){
var num=Math.random();
}
var res=foo();
res.set_num();
res.get_num();
解决方法:
functin foo(){
var num=Math.random();
return {
get_num:function(){
return num;
},
set_num:function(value){
num=value;
}
};
}
var res=foo();
var num=res.get_num();
console.log(num);
res.set_num(123);
var num=res.get_num();
console.log(num);
一般闭包的问题就是要想办法间接的获得函数的使用权,那我们可以总结出一个基本的使用模型。
闭包是为了实现具有私有访问的空间函数的
所谓的私有数据,就是说只有函数内部可以访问的数据,或对象内部的方法可以访问的数据
1.带有私有访问数据的对象
function Person(){
this.name='张三';
//setName('');
}
最简单的实现方式:
function createPerson(){
var __name__ = '';//内部使用可以这样写"__name__";
return {//return 返回是一个对象,var p=createPerson();中加new 或不加new是一个效果
get_name:function(){
return __name__;
},
set_name:function(value){ //如果不姓张就报错
if(value.charAt(0)==="张"){
__name__=value;
}else{
throw new Error('姓氏不对不能取名');
}
}
}
}
var p=new createPerson();
console.log(p.get_name());//""
p.set_name("张三");
console.log(p.get_name());// "张三"
p.set_name("王六");
console.log(p.get_name()); // Uncaught Error: '姓氏不对不能取名'
2.带有私有访问数据的函数
//传统
var func=function(){}
function func(){}
//
var foo=(function(){
//私有数据
return function(){
// 可以使用私有数据
};
}())
对象模型
function foo(){
//私有数据
return {
method:function(){
//操作私有数据
}
}
}
函数模型
function foo(){
//私有数据
return function(){
//可以操作私有数据
}
}
1.沙箱的概念
沙盘与盒子,就可以在一个小小的空间内模拟现实世界,特点是执行效果与现实世界一模一样,但是沙箱中模拟与现实无关。
2.沙箱模式
沙箱模式就是一个自调函数,代码写到函数中一样会执行,但是不会与外界有任何影响。
例如,在jQuery 中
(function(){
var jQuery =function(){
//所有的算法
}
// .... // .... jQuery.each=function(){}
window.jQuery = window.$=jQuery;
})();
$.each(....)
以 Fibonacci 数列为例,改进传统计算斐波那契数列方法,我们来回顾一下传统递归方式求斐波那契数列方法,我们定义一个count变量来查看递归了多少次:
var count=0;
function fibo(n){
count++;
if(n==0 || n==1) return 1;
return fibo(n-1) + fibo(n-2);
}
fibi(20);
console.log(count1);
//5:15
//6:25
//...
//20:21891
当 n = 5 式,count = 15,当时当 n = 20 的时候,count就达到惊人的21891次,性能太低了
性能低的原因是 重复计算。如果每次将计算的结果存起来
改进版:
var data = [ 1, 1 ];
var count = 0;
function fibo( n ) {
count++;
var v = data[ n ];
if( v === undefined ){
v = fibo( n - 1 ) + fibo( n - 2 );
data[ n ] = v;
}
return v;
}
fibo( 100 );
console.log( count ); // 199
改进之后,n=100的时候也才199次,大大提高了性能。
函数执行需要内存,那么函数中定义的变量,会在函数执行结束后自动回收,凡是因为闭包结构,被引出的数据,如果还有变量引用这些数据的话,那么这些数据就不会被回收。因此在使用闭包的时候,如果不使用某些数据了一定要赋值为null。
var f=(function(){
var num=123;
return function(){
return num;
};
})();
//f引用这函数,函数引用这变量 num;
//因此在不使用该数据的时候,最好写上 f=null;
//嵌入到其他函数时用f=null;(ie)
()()写法是怎么回事?
答:这个是函数颗粒化,又叫高级函数,常用(nodejs中);意思:定义一个函数,该函数返回一个函数,那么在调用的时候
function foo(){
function func(){}
return func;
};
foo()()
JavaScript 模式
function color( r, g, b ) { // ... } =>
color( 255, 0, 0 ) function color ( r ) {
return function color( g ) {
return color( b ) { }
}
} color( 255 )( 0 )( 0 )
函数的两种传递方式:函数作为返回值和函数作为参数
1.函数作为返回值
function fn(){
var max=10;
return function bar(x){
if(x>max){
console.log(x);//15
}
}
}
var f1=fn();
f1(15);
bar函数作为返回值,赋值给f1变量。执行f1(15)时,用到了fn作用域下的max变量的值。
2.函数作为参数被传递
例子:
var max=10;
fn=function (x){
if(x>max){
console.log(x);
}
}
(function show(f){
var max=100;
f(15);
})(fn);
fn函数作为一个参数被传递进入另一个函数,赋值给f参数。执行f(15)时,max变量的取值是10,而不是100。
变量跨作用域是怎么取值?
变量跨作用域取值时,要到创建这个函数的作用域取值,而不是“父作用域”。另外,除了结合着作用域之外,还需要结合着执行上下文栈来说一下。
最后总结一下闭包的好处与坏处
好处
①保护函数内的变量安全 ,实现封装,防止变量流入其他环境发生命名冲突
②在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
③匿名自执行函数可以减少内存消耗
坏处
①其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可以在使用完变量后手动为它赋值为null;
②其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响
参考