博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Js 之 递归,闭包
阅读量:5158 次
发布时间:2019-06-13

本文共 5647 字,大约阅读时间需要 18 分钟。

下面创建一个经典 的 阶乘的递归函数:

     

arguments.callee()  是一个指向正在执行的函数的指针。

     

 
function
factorial(num) {
if
(num
<=
1
) {
return
num; }
else
{
return
num
*
arguments.callee(num
-
1
); } } alert(factorial(
3
));
//
6

     但是这样在严格模式下是不能用的,在严格模式下,如下调用:

    

 
var
factorial
=
(
function
f(num) {
//
命名函数表达式
if
(num
<=
1
) {
return
num; }
else
{
return
num
*
f(num
-
1
); } }); alert(factorial(
3
));

闭包

闭包 是指 有权访问另一个函数作用域中的变量的函数。下面看个例子:

 
function
compareAge(age) {
return
function
(object1, object2) {
var
v1
=
object1.age;
var
v2
=
object2.age;
if
(v1
<
v2) {
return
-
1
; }
else
if
(v1
>
v2) {
return
1
}
else
{
return
0
; } } }

变量v1,v2的赋值,调用了外部函数的参数。即使这个内部函数被反回了,而且是被其他地方调用了,

它仍然可以访问变量age, 之所以能够访问,是因为内部函数的作用域中包含外部函数的作用域。

当某个函数 第一次被调用,会创建一个 执行环境 及相应的作用域链,并把作用域链赋值给一个特殊的内部属性

[[Scope]] .然后,使用this,arguments和其他命名参数的值 来初始化函数的 活动对象 但在作用域链中,外部函数的活动对象

始终处于第二位,外部函数的外部函数处于第三位,一次类推。。。

在函数执行时,为读取和写入变量的值,就需要在 作用域链 中查找变量,看下面例子:

上述代码创建一个比较函数,之后再全局作用域中 调用它。

 
function
compare(v1, v2) {
if
(v1
>
v2) {
return
1
; }
else
{
return
0
; } }
var
result
=
compare(
1
,
2
);
//
0

第一次调用 compare()时,会创建一个包含 this,arguments,v1,v2的活动对象。

全局执行环境的变量对象(包含 this,result,compare) 在compare() 执行环境的作用域链中则处于第二位。关系如下图:

                         

每个执行环境都有 一个  变量对象 ,全局环境的变量对象 始终存在.而像 compare()函数这样的 局部环境的变量对象 ,则只在

函数执行过程中存在. 

那么在创建 compare()函数时,会创建一个 包含全局变量对象的作用域链, 这个作用域链 被保存在 [[scope]]属性内.

当调用 compare() 函数时,会为函数创建一个执行环境,然后通过复制 函数的 [[Scope]]属性中的对象 构建起执行环境的作用域链.

此后,又有一个活动对象 被创建,并推入执行环境作用域链的前端.

对于这个例子, compare()函数的执行环境而言,其作用域链中包含两个变量对象: 本地活动对象和全局变量对象.

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

 

无论什么时候在函数中访问一个变量时,就会从作用域中搜索具有相应名字的变量.一般来讲,当函数执行完毕后,局部活动 变量就会被销毁

内存中仅保存全局作用域.但是闭包 有所不同:

看下面函数:

 
//
闭包函数
function
compare(propertyName) {
return
function
(object1, object2) {
var
v1
=
object1[propertyName];
var
v2
=
object2[propertyName];
if
(v1
>
v2) {
return
1
; }
else
if
(v1
<
v2) {
return
2
; }
else
{
return
0
; } } }
var
person1
=
{ name:
"
li
"
, age:
12
};
var
person2
=
{ name:
"
ming
"
, age:
14
};
var
f
=
compare(
"
age
"
);
var
result
=
f(person1, person2); alert(result);
//
2
alert(f);
//
函数f的全部代码
//
普通函数
function
asd() { document.getElementById(
"
asd
"
); };
var
s
=
asd(); alert(s);
//
undefined

 

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

因此 compare()内部的构造函数 的作用域链将包含 compare()函数的活动对象.

在匿名函数从compare()中被返回后,它的作用域链就被初始化为 包含compare()函数的活动对象和全局变量对象.这样匿名函数就可

访问在 compare()中的所有变量. 更为重要的是,compare()函数在执行完毕后,其活动对象不会被销毁,因为匿名函数作用域仍然在引用这个活动对象.

换句话说,当compare()函数返回后,其执行环境的作用域链被销毁了,但他的活动对象 仍然保存在内存中,直到匿名函数被销毁,compare()函数的活动对象才被销毁.

看如下代码  (基于compare()函数的):

var person1 = { name: "li", age: 12 };        var person2 = { name: "ming", age: 14 };        var f = compare("age");        var result = f(person1, person2);        alert(result);  //2        f = null;        alert(f);  //undefined

 

上述代码 通过将f设置为 null,解除 该函数的引用,就等于通知垃圾回收历程,将其清除.随着匿名函数的作用域链被销毁,其他的作用域链也可以安全的销毁了(除了全局作用域). 

下图展示了 f()的过程中产生的作用域链之间的关系:

 

由于闭包会携带包含它的函数的作用域,因此会比其他函数更占用内存.

 

闭包与变量

作用域链这种配置机制 引出了一个值得注意的副作用,就是 闭包只能取得包含函数中任何变量的最后一个值,

因为闭包所保存的一个变量对象,而不是某个特殊的变量,看下面的例子说明这个:

function a() {            var array = new Array();            for (var i = 0; i < 6; i++) {                array[i] = function () {                    return i;                };            }            return array;        }        var result;        for (var j = 0; j < a().length; j++) {            result += "\n" + "a()[" + j + "]:" + a()[j]();        }        alert(result);

result结果 如下图所示:

结果并没有和预想一样是 每个函数返回自己的索引.

因为每个函数的作用域中都保存着a()的活动对象,所以他们引用的都是同一个变量i,

当a()函数返回后,变量i的值为6, 此时每个函数都引用 着 保存变量i的同一个变量对象.所以每个函数内部i的值都是6.

但是可以通过 另一个匿名函数强制闭包的行为: 例子如下:

function a() {            var array = new Array();            for (var i = 0; i < 6; i++) {                array[i] = function (num) {                    return function () {                        return num;                    }                };            }            return array;        }        var result;        for (var j = 0; j < a().length; j++) {            result += "\n" + "a()[" + j + "]:" + a()[j](j)();        }        alert(result);

结果如下图:

重写了a()函数,没有直接把闭包的复制给数组,而是定义了一个匿名函数.并将立即执行该匿名函数的结果赋值给数组.

这里的匿名函数有个参数num,也就是最终的函数要返回的值.在调用每个匿名函数时,我们传入了变量i,由于函数的参数是按值传递的,

所以就会将变量i的值复制给 参数num,而在这个匿名函数内部,有创建了并返回一个访问num的闭包.这样 array数组的每个函数都有自己的num变量的一个副本.

关于this对象

在闭包中使用this有可能 会导致一些问题.

this对象 是在运行时基于函数的执行环境绑定的. 在全局函数中, this等于window,而当被作为某个对象的方法调用时,this等于那个对象.

不过,匿名函数的执行环境具有全局性,因此其this 对象通常指向window, 看下面例子:

var name = "window";        var object = {            name: "gao",            getName: function () {                return function () {                    return this.name;                }            }        }        alert(object.getName()());//非严格模式下  window

前面说过,每个函数被调用时,其活动对象都会自动取得两个特殊的变量,this和 arguments.内部函数在搜索这两个变量时,

只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量. 不过可以把外部作用域中的this对象保存在一个闭包

能够访问到的变量(that)里,就可以让闭包访问该对象(object)了:看下面例子:

var name = "window";        var object = {            name: "gao",            getName: function () {                var that = this;                return function () {                    return that.name;                }            }        }        alert(object.getName()()); //gao

提示:this和arguments 页存在同样的问题,如果想访问作用域中的arguments对象,必须将该对象的引用保存到另一个闭包可以访问的变量中.

内存泄露

如果闭包的作用域链中保存着一个html元素,那么就意味着该元素将无法销毁: 看下面例子:

(function () {            var div = document.getElementsByTagName("div");            var className = div[0].className;            if (div.length > 0) {                div[0].onclick = function () {                    alert(className);                };            }            div = null;  //解除对DOM对象的引用        })();

 

在上面代码中,通过把 div className存在一个变量中,并且在 闭包中引用该变量 消除了循环引用.但仅仅这么做还是不能避免内存泄露.

需要把 div变量设置为null,从而解除对DOM对象的引用,减少了其引用数,确保内存能够正常的回收.

转载于:https://www.cnblogs.com/Mr-Joe/archive/2012/06/08/2541627.html

你可能感兴趣的文章
学习什么语言的问题,其实,不是一个问题......
查看>>
MongoRepository动态代理及jpa方法解析源码分析
查看>>
bzoj2015 [Usaco2010 Feb]Chocolate Giving
查看>>
bzoj1651[Usaco2006 Feb]Stall Reservations 专用牛棚
查看>>
spring中InitializingBean接口使用理解
查看>>
团队合作之Scrum
查看>>
关于开发和测试沟通的一些问题
查看>>
Redis教程_2
查看>>
通过java给qq邮箱发送信息
查看>>
style、currentStyle、getComputedStyle区别介绍
查看>>
Python List(列表)使用示例
查看>>
poj-3069-Saruman's Army
查看>>
webstorm的破解
查看>>
C#中创建线程,创建带参数的线程
查看>>
让 VS2010 支持 HTML5 和 CSS3.0
查看>>
eclipse 中过滤空包,目录树中不显示。
查看>>
test
查看>>
从 PHP 到 Java
查看>>
OpenOffice 实现OFFICE在线预览
查看>>
Stardew Valley(星露谷物语)Mod开发之路 写在前面
查看>>