匿名函数 VS 闭包

来源:互联网 发布:可以注销淘宝店铺吗 编辑:程序博客网 时间:2024/06/06 01:47
function fact(n){  if(n<=1){    return 1;  } else {    return fact(n-1)*n;  }}//fact(3)var f = fact;fact = null;f(2)

上述 f(2) 调用将会报错:尽管f此时可以引用fact在变为null之前所指向的函数,但是函数内部的fact已经是null,已经失去原有的引用,所以将会报错。

建议和修改方法:可将fact函数内部对自身引用的句柄fact改为arguments.callee。


闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式, 是在一个函数内部创建另一个函数。

建议非必要时, 不要使用闭包,匿名函数也是差不多。

function outer(propertyName){  var v;  return function(o1,o2){    v = v || 0;    console.info('vInOuter',++v);    return o1[propertyName] == o2[propertyName];  }}var o1 = {  a:1};var o2 = {  a:2};var a = outer('a');a(o1,o2);
上面代码中a(o1,o2)如果多次调用可以发现outer中的局部变量v将会不断增加1。

说明:

内部函数访问了外部函数变量propertyName,即使这个内部函数被返回了,而且是在其他地方被调用了,它仍然可以访问变量 prototyName,内部函数的作用域链包括外部函数。

当某个函数第一次被调用时, 会创建一个执行环境及相应的作用域链, 并把作用域链赋值给一个特殊的内部属性[Scope] , 然后, this, arguments 和其他命名参数的值来初始化函数的活动对象.但在作用域链中, 内部函数-> 外部函数-> 更外部函数->......-> 最外部函数. ( 这种包含关系 )。

function compare(value1, value2){     if(value1 < value2){         return -1;     }else if(value1 > value2){         return 1;     }else{        return 0;     }            }   var result = compare(5,10);


解读:每一个函数调用都会有两个Scope与之对应,一个可以称之为caller,另一个可以称之为callee,也就是调用者与被调用者。

其中callee的Scope中的this会指向caller对象。当然,每一个Scope中都会存在一些局部变量,正是这些对象形成了闭包的概念。

显然,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。


后台的每个执行环境都有一个表示变量的对象-变量对象, 全局环境的变量对象始终存在, 而像compare()函数这样的局部环境变量对象, 则只在函数执行的过程中存在, 在创建compare()函数时, 会创建一个预先包含全局变量对象的作用域链, 这个作用域链保存在内部的[[Scope]]属性中, 当调用 compare()函数时, 会为函数创建一个执行环境, 然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后, 又有一个活动对象(在此作为变量对象使用) 被创建并被推入执行环境作用域链的前端, 对于这个例子 compare() 函数的执行环境而言, 其作用域链中包含两个变量对象, 本地活动对象和全局变量对象, 显然作用域链本质上是一个指向变量对象的指针列表, 它只引用但不实际包含变量对象。


无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存仅保存全局作用域,但是闭包的情况不同

在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。


承接以上代码例子 :
var a = outer('a');  //a指针保存函数地址
var result = a( o1,o2);

在匿名函数从 outer()函数中被返回后,它的作用域链被初始化为包含outer()函数的活动对象和全局对象,这样,匿名函数就可以访问outer()中定义的所有变量, 更为重要的是, outer()函数执行完毕后,其活动对象也不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象。(那么匿名函数为什么没有被释放呢? 因为outer 这个函数本身返回的是一个函数, 所以a=outer 这条语句的本质就是又做了一个指针指向了内存中的匿名函数, 所以在释放空间时, 因为匿名函数在外围函数的外围还有一个指针指向, 所以它不能被释放, 同样它指向的外围函数内部的变量也不能被释放)换句话说,当outer()函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然会留在内存中直到匿名函数被销毁后,outer()的活动对象才会被销毁。所以需要显示解除 :  例如:

// 创建函数
var a = outer('a'); // 这时a已经是指向了函数内部的匿名函数

// 调用函数, // 注意, 这个函数执行完毕后, 函数的内存空间将被释放, 但是... 匿名函数和匿名函数所指向的函数的内部变量不能被释放
var result = outer(o1, o2);

// 解除对匿名函数的引用( 以便释放内存 )
a = null;


注意: ( anonymous ) Scope 中的 this 是 window, 这也就呼应了下边的 this 的问题。



0 0