javascript闭包

2018-06-14 admin

闭包的定义:

闭包是函数和声明该函数的词法作用域的组合。

先看如下例子:

function makeFn(){
    var name = "Mirror";
    function showName(){
        alert(name)
    }

    return showName;
}

var myFn = makeFn();
myFn();  // "Mirror"

javascript 中的函数会形成闭包。闭包是由函数以及创建该函数的词法环境组合组成。这个环境包含了这个闭包创建时所能访问的所有局部变量。在以上的例子中,myFn是执行makeFn时创建的showName函数实例的引用,而showName实例仍可访问其词法作用域中的变量,既可以访问到name。 由此,当myFn 被调用时,name仍可被访问。

再看一个更有意思的例子:

function makeAdder(x){
    return function( y ){
        return x + y 
    }
}

var add5 = makeAdder( 5 );    
var add10 = makeAdder( 10 );
console.log( add5(2) )  // 7 
console.log( add10(2) )   // 12

以上示例中,我们定义了makeAdder(x) 函数,它接收一个参数x ,并返回一个新的函数。返回的函数接受一个参数y,并返回 x+y的值。

本质上讲,makeAdder 是一个工厂函数 – 它创建了将指定的值和它的参数相加求和的函数。上面的add5add10都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。在add5的环境中,x为5,而在add10中,x则是10。

实用的闭包:

闭包允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

通常,使用只有一个方法的对象的地方都可以使用闭包。

假如,我们想在页面上添加一些可以调整字号的按钮。一种方法是以像素为单位指定 body 元素的 font-size,然后通过相对的 em 单位设置页面中其它元素(例如header)的字号:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <style>
        body { font-size: 12px;}
        h1 {font-size: 1.5em;}
        h2 {font-size: 1.2em;}
    </style>
    <script>
        function SetFs( size ){
            return function(){
                document.body.style.fontSize = size + 'px'
            }
        }
    </script>
</head>
<body>
    <h1> h1 标签</h1>
    <h2> h2 标签</h2>
    <button id="size_22" onclick="SetFs(22)()" >22px</button>
    <button id="size_32" onclick="SetFs(32)()" >32px</button>
</body>
</html>

用闭包模拟私有方法:

私有方法不仅仅有利于限制对代码的访问,还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。

var Counter = ( function(){
    var privateCounter = 0;
    function changeBy(val){
        privateCounter += val
    };

    return {
        add:function(){
            changeBy(1);
            return privateCounter;
        },
        decrease:function(){
            changeBy(-1);
            return privateCounter;
        },
        value:function(){
            return privateCounter;
        }
    }

})()

以上示例中我们只创建了一个词法环境,为三个函数所共享:Counter.addCounter.decreaseCounter.value

该共享环境创建一个立即执行的匿名函数体内。这个环境中包含两个私有项:privateCounter的变量和changeBy的函数。这两项都无法在这个匿名函数外直接访问。必须通过匿名函数返回的三个公共函数访问。

这三个公共函数是共享同一个环境的闭包。多亏javascript的词法作用域,它们都可以访问privateCounter变量和changeBy函数。

应该注意到我们定义了一个匿名函数,用来创建计算器。立即执行函数将他的值赋给了变量Counter。我们也可以将这个函数存储在另一个变量makeCounter中,并用它来创建多个计数器。

var makeCounter = function(){
    var privateCounter = 0;
    var changeBy = function(val){
        privateCounter += val
    };
    return {
        add:function(){
            changeBy(1)
            return privateCounter
        },
        decrease:function(){
             changeBy(-1)
             return privateCounter
        },
        value:function(){
            return privateCounter
        }
    }
}

var Counter1 = makeCounter();
var Counter2 = makeCounter();
Counter1.add() // 1
Counter2.decrease() // -1

两个计数器Counter1Counter2都引用自己词法作用域内的变量privateCounter。每次调用其中一个计数器时,通过改变这个变量的值,会改变这个闭包的词法环境。然而在一个闭包内对变量的修改,不会影响到另一个闭包中的变量。

以这种方式使用闭包,提供了许多面向对象编程相关的好处——特别是数据隐藏和封装。

在循环中创建闭包:一个常见的错误

在ECMAScript2015引入let关键字以前,在循环中有一个常见的闭包创建问题。如下:

<button>1</button>
<button>2</button>
<button>3</button>

<script>
    var els = document.querySelectorAll('button');
    for(var i=0; i<els.length ; i++){
        els[i].onclick = function(){
            alert( '第'+ i +`按钮` )
        }
    }
</script>

运行以上代码,会发现没有达到想要的结果。无论点击哪个按钮,弹窗提示的都是第3个按钮

原因是赋值给onclick的是闭包。三个闭包在循环中被创建,但是他们共享同一个词法作用域,在这个作用域中存在一个变量i。当onclick的回调执行时,i的值被决定。由于循环在事件触发之前早已执行完毕,变量i(被三个闭包共享)已经变成了’3’。

解决这个问题的一种方案是使用跟多的闭包:特别是使用前面所述的函数工厂:

<button>1</button>
<button>2</button>
<button>3</button>

<script>
    var els = document.querySelectorAll('button'); 

    function clickCallback(x){
        return function(){
            alert(x)
        }
    }

    for(var i=0; i<els.length ; i++){
        els[i].onclick = clickCallback(i)
    }
</script>

这段代码可以如我们所期望的那样工作。所有的回调不再共享同一个环境,clickCallback函数为每一个回调创建一个新的词法环境。在这些环境中。x指向循环中的i

另一种方法实现了匿名闭包:

<button>1</button>
<button>2</button>
<button>3</button>

<script>
    var els = document.querySelectorAll('button');

    for(var i=0; i<els.length ; i++){
        els[i].onclick = (function (i) {
            return function(){alert(i)}
        })(i)
    }
</script>

避免使用过多的闭包,可以用let关键词:

<button>1</button>
<button>2</button>
<button>3</button>

<script>
    var els = document.querySelectorAll('button'); 

    for(let i=0; i<els.length ; i++){
        els[i].onclick = function(){
            alert( i );
        }
    }
</script> 

这个例子使用的是let而不是var,因此每个闭包都绑定了块作用域的变量,这意味着不再需要额外的闭包。

性能考量

如果不是某些特定的任务需要使用闭包,在其他函数中创建函数是不明智的,因为闭包在处理速度和内存消耗方面对脚本性能具有负面影响。

例如,在创建新的对象或者类时,方法通常应该关联于对象的原型,而不是定义到对象的构造器中。原因是这将导致每次构造器被调用时,方法都会被重新赋值一次(也就是,每个对象的创建)。

考虑一下示例:

function MyObject( name , message ){
    this.name = name;
    this.message = message.toString();
    this.getName = function(){
        return this.name;
    };
    this.getMessage = function(){
        return this.message
    };
}

上面的代码并未利用到闭包的好处,可以修改成如下:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

不建议重新定义原型。可改成如下例子:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype.getName = function() {
  return this.name;
};
MyObject.prototype.getMessage = function() {
  return this.message;
};

原文链接:https://segmentfault.com/a/1190000015289375

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-34070.html

文章标题:javascript闭包

相关文章
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
javascript是什么意思
avaScript是Netscape开发的一个对象脚本语言,它使用在世界各地数以百万计的网页和服务器应用程序上。 网景的JavaScript是ecma - 262版的标准脚本语言,和公布的标准只有轻微的差异。 与广为流行的错误理解相反,Ja...
2015-11-12
21天学通javascript
简介: 本书是Javascript入门教程。Javascript是Web开发中应用最早、发展最成熟、用户最多的脚本语言。其语法简洁,代码可读性在众多脚本语言中最好,它在使用时不用考虑数据类型,是真正意义上的动态语言。本书总分为四篇,共21章...
2015-11-16
JavaScript的组成
一个完整的JavaScript由3个部分组成:核心(ECMAScript) 文档对象模型(DOM) 浏览器对象模型(BOM) ECMAScript 描述了该语言的语法和基本对象 ; DOM 描述了处理网页内容的方法和接口 ; BOM 描...
2015-11-12
JavaScript 事件流、事件处理程序及事件对象总结
JS与HTML之间的交互通过事件实现。事件就是文档或浏览器窗口中发生的一些特定的交互瞬间。可以使用监听器(或处理程序)来预定事件,以便事件发生时执行相应的代码。这种在传统软件工程中被称为观察员模式,支持页面的行为与页面的外观之间的松散耦合。...
2017-04-05
JavaScript变量的声明
声明变量 变量在脚本中的首次亮相是在其声明中。 在变量首次出现时将会在内存中设置它,因此您稍后可在脚本中引用它。 应在使用变量之前先声明变量。 可以使用 var 关键字实现此目的。 &lt;span id=“mt9” class=“sent...
2015-11-12
7个提高效率的JavaScript调试工具
鐜板湪鐨凧avaScript浜嬪疄涓婂凡鐒舵垚涓轰簡娴佽�岀殑web璇�瑷€锛屽嵆浣垮畠骞朵笉瀹岀編銆傚緢澶氱▼搴忓憳涓嶅枩娆㈢敤JavaScript鍐欎唬鐮侊紝鏄�鍥犱负鍐欏埌鍚庢潵鎬讳細鍑虹幇鍚勭�嶈帿鍚嶅叾濡欑殑bug锛岃€屼笖鍦ㄥ紑...
2015-11-11
7个让JavaScript变得更好的注意事项
随着浏览器性能提高,伴随着新的HTML5的编程接口的稳步采用,网页上的JavaScript的音量在逐渐增加。然而,一个写得不好的程序编码却拥有着打破整个网站,让用户为之沮丧和驱赶潜在客户的潜力。 开发人员必须使用所有供他们任意使用的工具和技...
2015-11-12
JavaScript游戏之连连看源码分享
JavaScript游戏之连连看源码 下载地址:JavaScript游戏之连连看源码 解压密码:www.javascriptcn.com ...
2015-11-12
JavaScript短路原理精简代码
js中||和&amp;&amp;的特性帮我们精简了代码的同时,也带来了代码可读性的降低,虽然高效,但请灵活使用。 在js逻辑运算中,0、&quot;&quot;、null、false、undefined、NaN都会判为false,其他都为t...
2015-11-12
回到顶部