前端开发那些事儿JavaScript入门教程前端入门教程

JavaScript函数之闭包

2021-07-31  本文已影响0人  微语博客

什么是闭包

闭包是JavaScript的难点,闭包产生的原因也是因为函数作用域的特性,函数作用域的内容可以回顾上一篇文章JavaScript函数之作用域。闭包是什么呢,很多文档资料对闭包的讲解也是非常的抽象,比如引用Mozilla对闭包的解释:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。简单的理解是:闭包其实是一个被引用的函数作用域。这个作用域是函数的一个私有空间,由于私有空间的变量被引用,从而形成闭包不让它销毁。

回顾作用域

在JavaScript中,作用域无非两种:局部和全局作用域。作用域的特点也很明显,内层可以访问外层的作用域。比如下面的示例:

function myFun() {
    var num = 10;
    function inFun(){
        console.log(num);
    }
    inFun();//10
}
myFun();

当执行外层函数myFun时,该函数产生了一个内部变量num和一个内部函数inFun,所以在myFun函数外部无法访问到内部的作用域,原因是作用域创建的顺序有先后。同理,嵌套函数也是一样。

闭包

上面的示例中,myFun的嵌套函数inFun执行后成功输出了num的值,这是我们意料之中的事,因为嵌套函数可以访问外部函数的作用域,但是如果我们要在myFun外面执行inFun函数呢,看下面的例子

function myFun() {
    var num = 10;
    function inFun(){
        console.log(num);
    }
    return inFun;
}
var pubFun = myFun();
pubFun();//10

该示例中我们没有直接调用inFun函数,而是将它作为返回值返回了。在外层定义了一个全局变量pubFun,其值为myFun函数的调用,注意这里在声明变量pubFun时已经调用了myFun函数,所以再执行pubFun的时候输出了num的值。

在本示例中,由于myFun执行完毕,num变量可能会被回收销毁,但是由于内部函数对其有引用,所以myFun函数形成了一个闭包,阻止局部变量被销毁,所以闭包可以理解为是一个作用域,包括该作用域的所有引用。

计数器

再看一个闭包的示例:

function add() {
    var num = 0;
    function addx(x){
        return num+=x;
    }
    return addx;
}
var myAdd = add();
console.log(myAdd(1));//1
console.log(myAdd(1));//2
console.log(myAdd(1));//3

由于闭包,num变量不会被销毁或重置,所以每次执行都会累加,这样就形成了计数器。我们也可以让它的计数梯度为其它的值,比如下面:

function add() {
    var num = 0;
    function addx(x){
        return num+=x;
    }
    return addx;
}
var myAdd = add();
console.log(myAdd(2));//2
console.log(myAdd(2));//4
console.log(myAdd(2));//6

当然,这样的应用就有很多了,其核心就是JS函数形成的闭包。

闭包模拟私有方法

学过Java的同学都知道,Java可以用private关键字来修饰私有方法,而原生的js并没有提供支持,我们可以使用闭包来模拟私有方法,私有方法只能被同一个类所调用。

function math(x) {
    var num = 0;
    return {
        add : function (y) {
            return x + y;
        },
        multi : function (y) {
            return x * y;
        }
    };
}
var myMath = math(5);
console.log(myMath.add(5));//10
console.log(myMath.multi(5));//25

例子简单,够用就好,不过这个示例还就可以使用立即执行函数优化,下一篇文章讲解立即执行函数

闭包引发的性能问题

因为闭包中的作用域引用无法释放,大量的使用闭包会严重的消耗内存,如果不是特定任务需要使用闭包,在其它函数中创建函数不是一个明智选择。比如下面的例子:

function MyObj(name){
    this.name = name.toString();
    this.getName = function(){
        return this.name;
    }
}

类似这种示例,每次构造器被调用时,方法都会被重新赋值一次。应该关联于对象的原型,而不是定义到对象的构造器中。

function MyFun(name){
    this.name = name.toString();
}
MyFun.protoype = {
    getName : function(){
        return this.name;
    }
}

但是也不建议重新定义原型,而是推荐在原型对象上添加原型方法。

function MyFun(name){
    this.name = name.toString();
}
MyFun.protoype.getName = function(){
    return this.name;
}

这里就涉及到构造函数和原型的知识点了,先混个眼熟吧。

上一篇下一篇

猜你喜欢

热点阅读