最简单易懂的闭包

来源:互联网 发布:sas编程与数据挖掘商业案例 编辑:程序博客网 时间:2024/05/22 11:34
一、作用域
     js特有的作用域,内部函数可以访问外部函数的参数和变量,这个很美妙哦!
     但是有时我们需要验证一个变量或属性的类型时,他就会自动向上查找直到原型,如果有则返回,如果没有则返回undefined。typedef、for in循环都会出现该问题;hasOwnPrototype就可以很好的避免这个问题,如果对象独有这个属性,则返回true否则false。
     但是外部的函数是不能访问到内部函数里面的参数和变量的。有时候我们需要内部变量,因此此处可以借助闭包。
二、闭包
     其实每一个函数都是一个闭包,都可以访问到函数外部的局部变量。函数里面嵌套函数只不过是更高级的闭包,有权访问另一个函数作用域中的变量。
     作用:1)可以帮助我们访问到内部的变量;2)让有些变量始终保存在内存中。
     例子详解:
     1.function create(){
var result = new Array();
for(var i = 0; i < 10; i++){
     result[i] = function(){
     return i;
          };
     }
     return result;
}
alert(create());
这个例子误以为是返回0到9,其实都是10。为什么呢?首先闭包只能取得函数中任何变量的最后一个值,还有我觉得最好理解的就是,var result[i]=function(){...};是一个函数表达式,函数()才能执行。因此i自增,每次返回都是function(){return i;}。解决办法如下:
function create(){
var result = new Array();
for(var i = 0; i < 10; i++){
result[i] = (function(num){
     return num;
})(i);
}
return result;
}
alert(create());
就相当于每次进行函数调用,自动传参。
      2.var name = "$$$";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
     return this.name;
};
}
};
console.log(object.getNameFunc()());//"$$$"
这种模式调用函数会自动将this绑定到全局对象,这是js语言设计错误。解决办法:给该方法定义一个变量(一般规范命名为that),并给其复制为this,内部函数通过变量访问到this,就可以绑定到对象上
var name = "T$$$";
var object = {
name : "My Object",
 getNameFunc : function(){
var alin = this;
return function(){
     return alin.name;
};
}
};
console.log(object.getNameFunc()());//My Object
      3function cnt(){
     var i = 0;
     function inner(){
console.log(i);
i++;
     }
     return inner;
}
var result = cnt();
result();//0  result();//1result();//2
该例子和第一个例子很像,但是这个更加清晰的解释了函数里面嵌套函数,即为闭包。return就相当于链接内部函数与外部函数的桥梁。cnt()依赖于inner(),inner()又依赖于cnt(),所以i始终存在于内存中。
3.js内存机制
     js闭包代码不出错是不会有内存泄露的,这种情况值存在于IE6/7里,是浏览器的问题而不是代码本身的问题,因为他们用的引用计数机制。
     所有语言内存生命周期都是相似的:1)当需要时候分配内存;2)对内存进行读写操作;3)当不在需要时释放内存。
     标记清除法:“可回收”即“对象不可达”,即访问不到。这种算法,会定义一个“根”,定期从“根”出发,找出“根”下的所有对象,看是否能找到一条路径找到该对象。从不同的“根”出发,若对象不可达,则被回收。
     例如在循环中,没循环一次计数机制便加一,当循环结束,计数并不能为0,但不再引用,就回收不了。如果标记清楚发,顺着根找,函数调用完后就没有任何,循环中的对象不再被任何人引用,所以回收。




后续:(知识都是相通的,这些可以更好的理解闭包)
1.函数声明和函数表达式的不同点:
1>函数声明会提升当前执行环境(作用域)上的函数声明,而函数表达式必须等到js执行到它所在行时,才会由上往下解析函数表达式;
2>函数表达式后面可以加括号调用该函数,函数声明不行。如下例子:
fnName();
function fnName(){
...
}
//正常,提升了函数声明,调用可在声明之前
faName();
var fnName = function(){
...
}
//报错,变量还未保存对函数的引用,调用必须在表达式之后
var fnName = function(){
...
}();
//函数表达式后面加括号,js引擎解析到此就可以立即调用函数
function fnName(){
...
}();
//不会报错,js解析会自动忽略后面的括号,函数声明但不会被调用
function(){
...
}();
//语法错误,虽然匿名函数属于函数表达式,但没有进行赋值操作
3>(function(){...})();
     (function(){...}());
     要在函数后面加括号立即被调用。这个函数必须是函数表达式,不是函数声明。如下例子:
(function(a){
     console.log(a);
})(123); //123,使用()运算符

(function(a){
     console.log(a); //firebug输出1234,使用()运算符
}(1234));

!function(a){
        console.log(a); //firebug输出12345,使用!运算符
}(12345);

+function(a){
        console.log(a); //firebug输出123456,使用+运算符
}(123456);

-function(a){
        console.log(a); //firebug输出1234567,使用-运算符
}(1234567);

var fn=function(a){
        console.log(a); //firebug输出12345678,使用=运算符
}(12345678);
     所加符号都是将函数声明变为函数表达式。
     !、+、-、=、()会和函数的返回值进行运算,造成不必要的麻烦。
     这样写的用处:js没有私有作用域,很多人开发难免造成变量冲突。根据js作用域链的特征,可模仿私有作用域,匿名函数当做一个容器,容器内可以访问外部变量,而外部环境不能访问容器内的变量。可把这个容器当做一个命名空间,外部的不能访问里面的,不会发生命名冲突。
     function a(){
var i = 0;
function b(){
alert(++i);
}
return b;
     }
     var c = a();
     alert(c());
  1. 定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。
  2. 执行函数a的时候,a会进入相应的执行环境(excution context)
  3. 在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。
  4. 然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。
  5. 下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。
  6. 最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。
1 0