资讯专栏INFORMATION COLUMN

javascript闭包

binaryTree / 802人阅读

摘要:闭包的定义闭包是函数和声明该函数的词法作用域的组合。上面的和都是闭包。然而在一个闭包内对变量的修改,不会影响到另一个闭包中的变量。原因是赋值给的是闭包。由于循环在事件触发之前早已执行完毕,变量被三个闭包共享已经变成了。

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

先看如下例子:

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)的字号:




    
    
    


    

h1 标签

h2 标签

用闭包模拟私有方法:

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

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关键字以前,在循环中有一个常见的闭包创建问题。如下:





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

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

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





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

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






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





 

这个例子使用的是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://www.ucloud.cn/yun/95458.html

相关文章

  • Javascript闭包入门(译文)

    摘要:也许最好的理解是闭包总是在进入某个函数的时候被创建,而局部变量是被加入到这个闭包中。在函数内部的函数的内部声明函数是可以的可以获得不止一个层级的闭包。 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者 :文章写在2006年,可直到翻译的21小时之前作者还在完善这篇文章,在Stackoverflow的How do Java...

    Fourierr 评论0 收藏0
  • 理解Javascript闭包

    摘要:但是闭包也不是什么复杂到不可理解的东西,简而言之,闭包就是闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在。可惜的是,并没有提供相关的成员和方法来访问闭包中的局部变量。 (收藏自 技术狂) 前言:还是一篇入门文章。Javascript中有几个非常重要的语言特性——对象、原型继承、闭包。其中闭包 对于那些使用传统静态语言C/C++的程序员来说是一个新的语言特性。本文将...

    dayday_up 评论0 收藏0
  • JavaScript闭包,只学这篇就够了

    摘要:当在中调用匿名函数时,它们用的都是同一个闭包,而且在这个闭包中使用了和的当前值的值为因为循环已经结束,的值为。最好将闭包当作是一个函数的入口创建的,而局部变量是被添加进这个闭包的。 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。 其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于...

    CoderBear 评论0 收藏0
  • JavaScript深入之闭包

    摘要:深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。定义对闭包的定义为闭包是指那些能够访问自由变量的函数。 JavaScript深入系列第八篇,介绍理论上的闭包和实践上的闭包,以及从作用域链的角度解析经典的闭包题。 定义 MDN 对闭包的定义为: 闭包是指那些能够访问自由变量的函数。 那什么是自由变量呢? 自由变量是指在函数中使用的,但既不是函数参数也...

    caige 评论0 收藏0
  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • [学习笔记] JavaScript 闭包

    摘要:但是,必须强调,闭包是一个运行期概念。通过原型链可以实现继承,而与闭包相关的就是作用域链。常理来说,一个函数执行完毕,其执行环境的作用域链会被销毁。所以此时,的作用域链虽然销毁了,但是其活动对象仍在内存中。 学习Javascript闭包(Closure)javascript的闭包JavaScript 闭包深入理解(closure)理解 Javascript 的闭包JavaScript ...

    sunsmell 评论0 收藏0

发表评论

0条评论

binaryTree

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<