《javascript设计模式与开发实践》阅读笔记(四)

闭包问题

变量的作用域

变量的生存周期

对于全局变量来说,生存周期是永久的;对于局部变量而言,一旦退出函数,局部变量就会被销毁。
下面看一段代码,代码示例3_1_2.html

<script type="text/javascript">
    var func=function(){
        var a=1;
        return function(){
            a++;
            alert(a);
        }
    }
    var f=func();
    f();//2
    f();//3
    f();//4
</script>

很有意思,a并没有被销毁。原因是f返回了一个匿名函数,而这个匿名函数访问了a,局部变量一直在匿名函数的环境里。这里测试了一下,确实f是一个匿名函数。也就是说f没有被销毁,那么a就不会被销毁咯?

这里写图片描述

闭包的经典应用

先上代码,3_1_3.html

<html>
<head>
    <title></title>
</head>
<body>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<div>5</div>
</body>
<script type="text/javascript">
    var nodes=document.getElementsByTagName('div');
    for (var i = 0 ; i < nodes.length; i++) {
        nodes[i].onclick=function(){
            alert(i);
        }
    }
</script>
</html>

无论点击1、2、3、4、5都是显示5。
这是为什么呢?我们读一下代码,我们是为每一个node添加的onclick事件啊?为什么会这么怪异呢?

原因是onclick事件是异步触发的,当事件被触发时,for循环早就结束。
另外,说一下i的作用域是全局作用域,js里面不存在块级作用域。

解决方案1

做一点小改动,将var 变成let,就可以解决。这里理解一下为什么可以解决:因为let是块级作用域,也就是i的生存周期只在for循环中,就不存在alert(i)的i是全局中的i了。

<script type="text/javascript">
    var nodes=document.getElementsByTagName('div');
    for (let i = 0 ; i < nodes.length; i++) {
        nodes[i].onclick=function(){
            alert(i);
        }
    }
</script>

解决方案2

结合上面的思想,我们只需要把i的作用域包裹在一个局部作用域中,就能解决这个问题。我们采用闭包的方式来看一下呢:

    var nodes=document.getElementsByTagName('div');
    for (var i = 0 ; i < nodes.length; i++) {
        (function(i){
            nodes[i].onclick=function(){
                alert(i);
            }
        })(i)
    }

像这样,我们把i放在一个函数里面,那么在作用域链上面就会先查找该函数内部的i,再找到全局的i,这样的i就不是5了。

解决方案3,通过this

闭包的其他应用

1.封装变量

例如一个mult函数(计算乘积的函数)

var cache = {};
var mult = function() {
    var args = Array.prototype.join.call(arguments, ',');
    if (cache[args]) {
        return cache[args];
    }
    var a = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
        a = a * arguments[i];
    }
    return cache[args] = a;
}
console.log(cache); //{}
console.log(mult(1, 2, 3))//6
console.log(cache);//{ '1,2,3': 6 }

因为担心mult一旦作用结束,那么cache就被销毁了,但是通过闭包可以把cache封装在mult函数中,同时随着mult()函数调用结束,cache也不会消失;

var mult = (function() {
    // body...
    var cache = {};
    return function() {
        var args = Array.prototype.join.call(arguments, ',');
        console.log("cache");
        console.log(cache);

        if (args in cache) {
            return cache[args]
        }
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i];
        }
        return cache[args] = a;
    }
})()
console.log(mult(1, 2, 3));
console.log(mult(1, 2, 3));

这样在外部访问不到cache,但是cache却存在着,真的是太神奇了。mult(1, 2, 3)这个函数返回的是一个匿名函数的引用,而并不是一个简单的对象或者是一个基本类型,这个函数可以访问到mult内的cache。所以cache的生命得以延续下来了。很有意思。
这里写图片描述

再看一下下面这种写法,将计算部分提出去了,使得代码更加优雅。

var mult = (function() {
    // body...
    var cache = {};
    var calculate = function() {
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
            a = a * arguments[i];
        }
        return a;
    }
    return function() {
        var args = Array.prototype.join.call(arguments, ',');
        console.log("cache");
        console.log(cache);
        if (args in cache) {
            return cache[args]
        }
        return cache[args] = calculate.apply(null, arguments);
    }
})()
console.log(mult(1, 2, 3));
console.log(mult(1, 2, 3));

calculate.apply(null, arguments);中的null表示this指向宿主对象。

2.延续局部变量的作用域

闭包和面向对象设计

先看由前面引申而来的一个包含闭包的函数,这个返回的是一个call,call是一个函数,却是在extent内部的函数,因此它可以访问value;

var extent = function() {
    var value = 0;
    return {
        call: function() {
            value++;
            console.log(value);
        }
    }
}
var e = extent();
e.call()
e.call()
e.call()

单例模式的实现:
其实以前都不知道单例模式运用到了闭包,确实,这就是闭包啊。call可以访问extent内部的变量,value可以被call改变。这是一个简单的单例模式,并没有设置私有变量。

var extent = {
    value: 0,
    call: function() {
        this.value++;
        console.log(this.value);
    }
}
console.log(extent.value); //0
extent.call(); //1
extent.call(); //2
extent.call(); //3
console.log(extent.value); //3

一个具有私有属性的单例模式的实现,这里就不能通过extent._value访问到value了,但是通过call可以。

var extent = (function() {
    var _value = 0;
    return {
        call: function() {
            _value++;
            console.log(_value);
        }
    }
}())

extent.call() //1
extent.call() //2
extent.call() //3
console.log(extent._value); //undefined

闭包实现命令模式

原始的例子:

<body>
<button id="execute">点我执行命令</button>
<button id="undo">点我执行命令</button>

</body>
<script type="text/javascript">
    var Tv={
        open:function(){
            console.log('打开电视机');
        },
        close:function () {
            console.log('关闭电视机');
        }
    }
    var OpenTvCommand=function(receiver){
        this.receiver=receiver;
    }
    OpenTvCommand.prototype.execute=function(){
        this.receiver.open();
    }
    OpenTvCommand.prototype.undo=function(){
        this.receiver.close();
    }
    var setCommand=function(command){
        document.getElementById('execute').onclick=function(){
            command.execute();
        }
        document.getElementById('undo').onclick=function(){
            command.undo();
        }
    }
    setCommand(new OpenTvCommand(Tv))
</script>
</html>

采用闭包后

var Tv = {
    open: function() {
        console.log('打开电视机');
    },
    close: function() {
        console.log('关闭电视机');
    }
}
var createCommand = function(receiver) {
    var execute = function() {
        return receiver.open()
    }
    var undo = function() {
        return receiver.close()
    }
    return {
        execute: execute,
        undo: undo
    }
};
var setCommand = function(command) {
    document.getElementById('execute').onclick = function() {
        command.execute();
    }
    document.getElementById('undo').onclick = function() {
        command.undo();
    }
}
setCommand(createCommand(Tv));

说实话我不知道有什么区别,大概就是createCommand采用了一种单例模式的方法?将execute和undo方法定义在了内部。之前是通过原型的方式实现的,就需要new 创建一个对象。

闭包与内存管理

局部变量本来应该在函数退出的时候就解除引用,但是由于被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。闭包会存在一些数据无法被及时销毁的,但是如果将来需要回收这些变量,可以手动设置这些变量为null。
跟闭包和内存泄露有关的是会出现循环引用,如果闭包的作用域链中保存着一些DOM节点,那么就有可能造成内存泄露。但是这不是闭包的问题,也不是javascript的问题,而是由于BOM和DOM的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用技术策略。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值