目录
引言
一个简短的的问题
几个更具体的例子
jQuery中的闭包
深入理解闭包
模块与闭包
性能考量
闭包的使用场景:
闭包的优缺点
结语
参考资料
首页 web前端 js教程 闭包有话说 - 大前端

闭包有话说 - 大前端

Feb 08, 2017 pm 05:57 PM
javascript 闭包

引言

刚学习前端的时候,看到闭包这个词,总是一脸懵逼,面试的时候,问到这个问题,也是回答的含含糊糊,总感觉有层隔膜,觉得这个概念很神奇,如果能掌握,必将功力大涨。其实,闭包没有这么神秘,它无处不在。

一个简短的的问题

首先,来看一个问题。

请用一句话描述什么是闭包,并写出代码进行说明。

如果能毫不犹豫的说出来,并能给出解释,那下面文字对你来说就没有往下看的必要了。
就这个问题,结合我查阅的资料和经验,在这里简单的说一下,如果哪里有不对的,欢迎指正。

先回答上面的问题,什么是闭包。

闭包是一个概念,它描述了函数执行完毕后,依然驻留内存的现象。

代码描述:

function foo() {

    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

var test = foo();
test(); //2
登录后复制
登录后复制

上面这段代码,清晰的展示了闭包。

函数 bar() 的词法作用域能够访问 foo()的内部作用域。然后我们将bar()函数本身当作一个值类型进行传递。上面这个例子,我们将bar() 所引用的函数对象本身作为返回值。

foo() 执行完毕之后, 其内部作用域并没有被销毁,因为bar()依然保持着对内部作用域的引用,拜bar()的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后的任何时间进行引用。这个引用,其实就是闭包。
也正是这个原因,test被实际调用的时候,它可以访问定义时的词法作用域,所以,才能访问到a.

函数传递也可以是间接的:

    var fn;
    function foo(){

        var a = 2;

        function baz() {
            console.log( a );
        }
        fn = baz; //将baz 分配给全局变量
    }

    function bar(){
        fn();
    }
    foo();
    bar(); //2
登录后复制
登录后复制

所以,无论通过何种手段将内部函数传递到其所在的词法作用域外,它都会持有对原始定义作用域的引用。也就是说,无论在什么地方执行这个函数,都会使用闭包。也是这个原因,我们才可以很方便的使用回调函数而不用关心其具体细节。

其实,在定时器,事件监听器,ajax请求, 跨窗口通信,Web Workers 或者任何其他的同步 或 异步任务中,只要使用了回调函数,实际上就是在使用闭包。

到这里,或许你已经对闭包有个大概的了解,下面我再举几个例子来帮你加深对闭包的认识。

几个更具体的例子

首先,就先看一下所谓的立即执行函数.

var a = 2;

(function IIFE() { 
   console.log(a); 
 })();

//2
登录后复制
登录后复制

这个立即执行函数通常被认为是经典的闭包例子,它可以正常工作,但严格意义上讲,它并不是闭包。
为什么呢?

因为这个IIFE函数并不是在它本身的词法作用域之外执行的。它在定义时所在的作用域中执行了。而且,变量a 是通过普通的词法作用域查找的,而不是通过闭包。

另一个用来说明闭包的例子是循环。

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
登录后复制
登录后复制
var handler = function(nodes) {

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = function(){

            console.log(i);

        }
    }
}

var tabs = document.querySelectorAll('.tabs .tab');
    handler(tabs);
登录后复制
登录后复制

我们预期的结果是log  0 ,1,2;

执行之后的结果却是是三个3;

这是为什么呢?

首先解释下这个3是怎么来的,

看一下循环体,循环的终止条件是  i < l ,  首次条件成立时 i 的值是3 。
因此 ,输出显示的是循环结束时 i 的最终值。 根据作用域的工作原理,尽管循环中的函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上是只有一个i.

handler 函数的本意是想把唯一的i传递给事件处理器,但是失败了。
因为事件处理器函数绑定了 i本身 ,而不是函数在构造时的i的值.

知道了这个之后,我们可以做出相应的调整:

var handler = function(nodes) {

    var helper = function(i){
        return function(e){
            console.log(i); // 0 1 2
        }
    }

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = helper(i);
    }
}
登录后复制
登录后复制

在循环外创建一个辅助函数,让这个辅助函数在返回一个绑定了当前i的值的函数,这样就不会混淆了。

明白了这点,就会发现,上面的处理就是为了创建一个新的作用域,换句话说,每次迭代我们都需要一个块作用域.

说到块作用域,就不得不提一个词,那就是let.

所以,如果你不想过多的使用闭包,就可以使用let:

var handler = function(nodes) {

    for(let i = 0, l = nodes.length; i < l ; i++) {
        
        //nodes[i].index = i;

        nodes[i].onclick = function(){

            console.log(i); // 0 1 2


        }
    }
}
登录后复制
登录后复制

jQuery中的闭包

先来看个例子

     var sel = $("#con"); 
     setTimeout( function (){ 
         sel.css({background:"gray"}); 
     }, 2000);
登录后复制
登录后复制

上边的代码使用了 jQuery 的选择器,找到 id 为 con 的元素,注册计时器,两秒之后,将背景色设置为灰色。

这个代码片段的神奇之处在于,在调用了 setTimeout 函数之后,con 依旧被保持在函数内部,当两秒钟之后,id 为 con 的 p 元素的背景色确实得到了改变。应该注意的是,setTimeout 在调用之后已经返回了,但是 con 没有被释放,这是因为 con 引用了全局作用域里的变量 con。

以上的例子帮助我们了解了更多关于闭包的细节,下面我们就深入闭包世界探寻一番。

深入理解闭包

首先看一个概念-执行上下文(Execution Context)。

执行上下文是一个抽象的概念,ECMAScript 规范使用它来追踪代码的执行。它可能是你的代码第一次执行或执行的流程进入函数主体时所在的全局上下文。

闭包有话说 - 大前端

在任意一个时间点,只能有唯一一个执行上下文在运行之中。

这就是为什么 JavaScript 是“单线程”的原因,意思就是一次只能处理一个请求。

一般来说,浏览器会用栈来保存这个执行上下文。

栈是一种“后进先出” (Last In First Out) 的数据结构,即最后插入该栈的元素会最先从栈中被弹出(这是因为我们只能从栈的顶部插入或删除元素)。

当前的执行上下文,或者说正在运行中的执行上下文永远在栈顶。

当运行中的上下文被完全执行以后,它会由栈顶弹出,使得下一个栈顶的项接替它成为正在运行的执行上下文。

除此之外,一个执行上下文正在运行并不代表另一个执行上下文需要等待它完成运行之后才可以开始运行。

有时会出现这样的情况,一个正在运行中的上下文暂停或中止,另外一个上下文开始执行。暂停的上下文可能在稍后某一时间点从它中止的位置继续执行。

一个新的执行上下文被创建并推入栈顶,成为当前的执行上下文,这就是执行上下文替代的机制。

闭包有话说 - 大前端

当我们有很多执行上下文一个接一个地运行时——通常情况下会在中间暂停然后再恢复运行——为了能很好地管理这些上下文的顺序和执行情况,我们需要用一些方法来对其状态进行追踪。而实际上也是如此,根据ECMAScript的规范,每个执行上下文都有用于跟踪代码执行进程的各种状态的组件。包括:

  • 代码执行状态:任何需要开始运行,暂停和恢复执行上下文相关代码执行的状态
     函数:上下文中正在执行的函数对象(正在执行的上下文是脚本或模块的情况下可能是null)

  • Realm:一系列内部对象,一个ECMAScript全局环境,所有在全局环境的作用域内加载的ECMAScript代码,和其他相关的状态及资源。

  • 词法环境:用于解决此执行上下文内代码所做的标识符引用。

  • 变量环境:一种词法环境,该词法环境的环境记录保留了变量声明时在执行上下文中创建的绑定关系。

模块与闭包

现在的开发都离不开模块化,下面说说模块是如何利用闭包的。

先看一个实际中的例子。
这是一个统计模块,看一下代码:

    define("components/webTrends", ["webTrendCore"], function(require,exports, module) {
    
    
        var webTrendCore = require("webTrendCore");  
        var webTrends = {
             init:function (obj) {
                 var self = this;
                self.dcsGetId();
                self.dcsCollect();
            },
    
             dcsGetId:function(){
                if (typeof(_tag) != "undefined") {
                 _tag.dcsid="dcs5w0txb10000wocrvqy1nqm_6n1p";
                 _tag.dcsGetId();
                }
            },
    
            dcsCollect:function(){
                 if (typeof(_tag) != "undefined") {
                    _tag.DCSext.platform="weimendian";
                    if(document.readyState!="complete"){
                    document.onreadystatechange = function(){
                        if(document.readyState=="complete") _tag.dcsCollect()
                        }
                    }
                    else _tag.dcsCollect()
                }
            }
    
        };
    
      module.exports = webTrends;
    
    })
登录后复制
登录后复制

在主页面使用的时候,调用一下就可以了:

var webTrends = require("webTrends");
webTrends.init();
登录后复制
登录后复制

在定义的模块中,我们暴露了webTrends对象,在外面调用返回对象中的方法就形成了闭包。

模块的两个必要条件:

  • 必须有外部的封闭函数,该函数必须至少被调用一次

  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

性能考量

如果一个任务不需要使用闭包,那最好不要在函数内创建函数。
原因很明显,这会 拖慢脚本的处理速度,加大内存消耗 。

举个例子,当需要创建一个对象时,方法通常应该和对象的原型关联,而不是定义到对象的构造函数中。 原因是 每次构造函数被调用, 方法都会被重新赋值 (即 对于每个对象创建),这显然是一种不好的做法。

看一个能说明问题,但是不推荐的做法:

    function MyObject(name, message) {
    
      this.name = name.toString();
      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;
};
登录后复制
登录后复制

很显然,在现有的原型上添加方法是一种更好的做法。

上面的代码还可以写的更简练:

    function MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
    }
    
    (function() {
        this.getName = function() {
            return this.name;
        };
        this.getMessage = function() {
            return this.message;
        };
    }).call(MyObject.prototype);
登录后复制
登录后复制

在前面的三个示例中,继承的原型可以由所有对象共享,并且在每个对象创建时不需要定义方法定义。如果想看更多细节,可以参考对象模型。

闭包的使用场景:

  • 使用闭包可以在JavaScript中模拟块级作用域;

  • 闭包可以用于在对象中创建私有变量。

闭包的优缺点

优点:

  • 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。

  • 方便调用上下文的局部变量。

  • 加强封装性,第2点的延伸,可以达到对变量的保护作用。

缺点:

  • 内存浪费。这个内存浪费不仅仅因为它常驻内存,对闭包的使用不当会造成无效内存的产生。

结语

前面对闭包做了一些简单的解释,最后再总结下,其实闭包没什么特别的,其特点是:

  • 函数嵌套函数

  • 函数内部可以访问到外部的变量或者对象

  • 避免了垃圾回收

欢迎交流,以上 ;-)

参考资料

让我们一起学习JavaScript闭包吧

弄懂JavaScript的作用域和闭包

Closures

引言

刚学习前端的时候,看到闭包这个词,总是一脸懵逼,面试的时候,问到这个问题,也是回答的含含糊糊,总感觉有层隔膜,觉得这个概念很神奇,如果能掌握,必将功力大涨。其实,闭包没有这么神秘,它无处不在。

一个简短的的问题

首先,来看一个问题。

请用一句话描述什么是闭包,并写出代码进行说明。

如果能毫不犹豫的说出来,并能给出解释,那下面文字对你来说就没有往下看的必要了。
就这个问题,结合我查阅的资料和经验,在这里简单的说一下,如果哪里有不对的,欢迎指正。

先回答上面的问题,什么是闭包。

闭包是一个概念,它描述了函数执行完毕后,依然驻留内存的现象。

代码描述:

function foo() {

    var a = 2;

    function bar(){
        console.log(a);
    }

    return bar;
}

var test = foo();
test(); //2
登录后复制
登录后复制

上面这段代码,清晰的展示了闭包。

函数 bar() 的词法作用域能够访问 foo()的内部作用域。然后我们将bar()函数本身当作一个值类型进行传递。上面这个例子,我们将bar() 所引用的函数对象本身作为返回值。

foo() 执行完毕之后, 其内部作用域并没有被销毁,因为bar()依然保持着对内部作用域的引用,拜bar()的位置所赐,它拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以供bar()在之后的任何时间进行引用。这个引用,其实就是闭包。
也正是这个原因,test被实际调用的时候,它可以访问定义时的词法作用域,所以,才能访问到a.

函数传递也可以是间接的:

    var fn;
    function foo(){

        var a = 2;

        function baz() {
            console.log( a );
        }
        fn = baz; //将baz 分配给全局变量
    }

    function bar(){
        fn();
    }
    foo();
    bar(); //2
登录后复制
登录后复制

所以,无论通过何种手段将内部函数传递到其所在的词法作用域外,它都会持有对原始定义作用域的引用。也就是说,无论在什么地方执行这个函数,都会使用闭包。也是这个原因,我们才可以很方便的使用回调函数而不用关心其具体细节。

其实,在定时器,事件监听器,ajax请求, 跨窗口通信,Web Workers 或者任何其他的同步 或 异步任务中,只要使用了回调函数,实际上就是在使用闭包。

到这里,或许你已经对闭包有个大概的了解,下面我再举几个例子来帮你加深对闭包的认识。

几个更具体的例子

首先,就先看一下所谓的立即执行函数.

var a = 2;

(function IIFE() { 
   console.log(a); 
 })();

//2
登录后复制
登录后复制

这个立即执行函数通常被认为是经典的闭包例子,它可以正常工作,但严格意义上讲,它并不是闭包。
为什么呢?

因为这个IIFE函数并不是在它本身的词法作用域之外执行的。它在定义时所在的作用域中执行了。而且,变量a 是通过普通的词法作用域查找的,而不是通过闭包。

另一个用来说明闭包的例子是循环。

    <p class="tabs">
        <li class="tab">some text1</li>
        <li class="tab">some text2</li>
        <li class="tab">some text3</li>
    </p>
登录后复制
登录后复制
var handler = function(nodes) {

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = function(){

            console.log(i);

        }
    }
}

var tabs = document.querySelectorAll('.tabs .tab');
    handler(tabs);
登录后复制
登录后复制

我们预期的结果是log  0 ,1,2;

执行之后的结果却是是三个3;

这是为什么呢?

首先解释下这个3是怎么来的,

看一下循环体,循环的终止条件是  i < l ,  首次条件成立时 i 的值是3 。
因此 ,输出显示的是循环结束时 i 的最终值。 根据作用域的工作原理,尽管循环中的函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上是只有一个i.

handler 函数的本意是想把唯一的i传递给事件处理器,但是失败了。
因为事件处理器函数绑定了 i本身 ,而不是函数在构造时的i的值.

知道了这个之后,我们可以做出相应的调整:

var handler = function(nodes) {

    var helper = function(i){
        return function(e){
            console.log(i); // 0 1 2
        }
    }

    for(var i = 0, l = nodes.length; i < l ; i++) {
        
        nodes[i].onclick = helper(i);
    }
}
登录后复制
登录后复制

在循环外创建一个辅助函数,让这个辅助函数在返回一个绑定了当前i的值的函数,这样就不会混淆了。

明白了这点,就会发现,上面的处理就是为了创建一个新的作用域,换句话说,每次迭代我们都需要一个块作用域.

说到块作用域,就不得不提一个词,那就是let.

所以,如果你不想过多的使用闭包,就可以使用let:

var handler = function(nodes) {

    for(let i = 0, l = nodes.length; i < l ; i++) {
        
        //nodes[i].index = i;

        nodes[i].onclick = function(){

            console.log(i); // 0 1 2


        }
    }
}
登录后复制
登录后复制

jQuery中的闭包

先来看个例子

     var sel = $("#con"); 
     setTimeout( function (){ 
         sel.css({background:"gray"}); 
     }, 2000);
登录后复制
登录后复制

上边的代码使用了 jQuery 的选择器,找到 id 为 con 的元素,注册计时器,两秒之后,将背景色设置为灰色。

这个代码片段的神奇之处在于,在调用了 setTimeout 函数之后,con 依旧被保持在函数内部,当两秒钟之后,id 为 con 的 p 元素的背景色确实得到了改变。应该注意的是,setTimeout 在调用之后已经返回了,但是 con 没有被释放,这是因为 con 引用了全局作用域里的变量 con。

以上的例子帮助我们了解了更多关于闭包的细节,下面我们就深入闭包世界探寻一番。

深入理解闭包

首先看一个概念-执行上下文(Execution Context)。

执行上下文是一个抽象的概念,ECMAScript 规范使用它来追踪代码的执行。它可能是你的代码第一次执行或执行的流程进入函数主体时所在的全局上下文。

闭包有话说 - 大前端

在任意一个时间点,只能有唯一一个执行上下文在运行之中。

这就是为什么 JavaScript 是“单线程”的原因,意思就是一次只能处理一个请求。

一般来说,浏览器会用栈来保存这个执行上下文。

栈是一种“后进先出” (Last In First Out) 的数据结构,即最后插入该栈的元素会最先从栈中被弹出(这是因为我们只能从栈的顶部插入或删除元素)。

当前的执行上下文,或者说正在运行中的执行上下文永远在栈顶。

当运行中的上下文被完全执行以后,它会由栈顶弹出,使得下一个栈顶的项接替它成为正在运行的执行上下文。

除此之外,一个执行上下文正在运行并不代表另一个执行上下文需要等待它完成运行之后才可以开始运行。

有时会出现这样的情况,一个正在运行中的上下文暂停或中止,另外一个上下文开始执行。暂停的上下文可能在稍后某一时间点从它中止的位置继续执行。

一个新的执行上下文被创建并推入栈顶,成为当前的执行上下文,这就是执行上下文替代的机制。

闭包有话说 - 大前端

当我们有很多执行上下文一个接一个地运行时——通常情况下会在中间暂停然后再恢复运行——为了能很好地管理这些上下文的顺序和执行情况,我们需要用一些方法来对其状态进行追踪。而实际上也是如此,根据ECMAScript的规范,每个执行上下文都有用于跟踪代码执行进程的各种状态的组件。包括:

  • 代码执行状态:任何需要开始运行,暂停和恢复执行上下文相关代码执行的状态
     函数:上下文中正在执行的函数对象(正在执行的上下文是脚本或模块的情况下可能是null)

  • Realm:一系列内部对象,一个ECMAScript全局环境,所有在全局环境的作用域内加载的ECMAScript代码,和其他相关的状态及资源。

  • 词法环境:用于解决此执行上下文内代码所做的标识符引用。

  • 变量环境:一种词法环境,该词法环境的环境记录保留了变量声明时在执行上下文中创建的绑定关系。

模块与闭包

现在的开发都离不开模块化,下面说说模块是如何利用闭包的。

先看一个实际中的例子。
这是一个统计模块,看一下代码:

    define("components/webTrends", ["webTrendCore"], function(require,exports, module) {
    
    
        var webTrendCore = require("webTrendCore");  
        var webTrends = {
             init:function (obj) {
                 var self = this;
                self.dcsGetId();
                self.dcsCollect();
            },
    
             dcsGetId:function(){
                if (typeof(_tag) != "undefined") {
                 _tag.dcsid="dcs5w0txb10000wocrvqy1nqm_6n1p";
                 _tag.dcsGetId();
                }
            },
    
            dcsCollect:function(){
                 if (typeof(_tag) != "undefined") {
                    _tag.DCSext.platform="weimendian";
                    if(document.readyState!="complete"){
                    document.onreadystatechange = function(){
                        if(document.readyState=="complete") _tag.dcsCollect()
                        }
                    }
                    else _tag.dcsCollect()
                }
            }
    
        };
    
      module.exports = webTrends;
    
    })
登录后复制
登录后复制

在主页面使用的时候,调用一下就可以了:

var webTrends = require("webTrends");
webTrends.init();
登录后复制
登录后复制

在定义的模块中,我们暴露了webTrends对象,在外面调用返回对象中的方法就形成了闭包。

模块的两个必要条件:

  • 必须有外部的封闭函数,该函数必须至少被调用一次

  • 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。

性能考量

如果一个任务不需要使用闭包,那最好不要在函数内创建函数。
原因很明显,这会 拖慢脚本的处理速度,加大内存消耗 。

举个例子,当需要创建一个对象时,方法通常应该和对象的原型关联,而不是定义到对象的构造函数中。 原因是 每次构造函数被调用, 方法都会被重新赋值 (即 对于每个对象创建),这显然是一种不好的做法。

看一个能说明问题,但是不推荐的做法:

    function MyObject(name, message) {
    
      this.name = name.toString();
      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;
};
登录后复制
登录后复制

很显然,在现有的原型上添加方法是一种更好的做法。

上面的代码还可以写的更简练:

    function MyObject(name, message) {
        this.name = name.toString();
        this.message = message.toString();
    }
    
    (function() {
        this.getName = function() {
            return this.name;
        };
        this.getMessage = function() {
            return this.message;
        };
    }).call(MyObject.prototype);
登录后复制
登录后复制

在前面的三个示例中,继承的原型可以由所有对象共享,并且在每个对象创建时不需要定义方法定义。如果想看更多细节,可以参考对象模型。

闭包的使用场景:

  • 使用闭包可以在JavaScript中模拟块级作用域;

  • 闭包可以用于在对象中创建私有变量。

闭包的优缺点

优点:

  • 逻辑连续,当闭包作为另一个函数调用的参数时,避免你脱离当前逻辑而单独编写额外逻辑。

  • 方便调用上下文的局部变量。

  • 加强封装性,第2点的延伸,可以达到对变量的保护作用。

缺点:

  • 内存浪费。这个内存浪费不仅仅因为它常驻内存,对闭包的使用不当会造成无效内存的产生。

结语

前面对闭包做了一些简单的解释,最后再总结下,其实闭包没什么特别的,其特点是:

  • 函数嵌套函数

  • 函数内部可以访问到外部的变量或者对象

  • 避免了垃圾回收

更多闭包有话说 - 大前端相关文章请关注PHP中文网!




本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1664
14
CakePHP 教程
1423
52
Laravel 教程
1317
25
PHP教程
1268
29
C# 教程
1243
24
C++ lambda 表达式中闭包的含义是什么? C++ lambda 表达式中闭包的含义是什么? Apr 17, 2024 pm 06:15 PM

在C++中,闭包是能够访问外部变量的lambda表达式。要创建闭包,请捕获lambda表达式中的外部变量。闭包提供可复用性、信息隐藏和延迟求值等优势。它们在事件处理程序等实际情况中很有用,其中即使外部变量被销毁,闭包仍然可以访问它们。

C++ 函数中闭包的优点和缺点是什么? C++ 函数中闭包的优点和缺点是什么? Apr 25, 2024 pm 01:33 PM

闭包是一种嵌套函数,它能访问外层函数作用域的变量,优点包括数据封装、状态保持和灵活性。缺点包括内存消耗、性能影响和调试复杂性。此外,闭包还可以创建匿名函数,并将其作为回调或参数传递给其他函数。

解决闭包导致的内存泄漏问题 解决闭包导致的内存泄漏问题 Feb 18, 2024 pm 03:20 PM

标题:闭包引起的内存泄漏及解决方法引言:闭包是JavaScript中一个非常常见的概念,它可以让内部函数访问外部函数的变量。然而,闭包在使用不当的情况下可能导致内存泄漏。本文将探讨闭包引起的内存泄漏问题,并提供解决方法及具体代码示例。一、闭包引起的内存泄漏问题闭包的特性是内部函数可以访问外部函数的变量,这意味着在闭包中引用的变量不会被垃圾回收。如果使用不当,

C++ Lambda 表达式如何实现闭包? C++ Lambda 表达式如何实现闭包? Jun 01, 2024 pm 05:50 PM

C++Lambda表达式支持闭包,即保存函数作用域变量并供函数访问。语法为[capture-list](parameters)->return-type{function-body}。capture-list定义要捕获的变量,可以使用[=]按值捕获所有局部变量,[&]按引用捕获所有局部变量,或[variable1,variable2,...]捕获特定变量。Lambda表达式只能访问捕获的变量,但无法修改原始值。

PHP 函数的链式调用和闭包 PHP 函数的链式调用和闭包 Apr 13, 2024 am 11:18 AM

是的,可以通过链式调用和闭包优化代码简洁性和可读性:链式调用可将函数调用链接为一个流畅接口。闭包可创建可重用代码块,并在函数外部访问变量。

函数指针和闭包对Golang性能的影响 函数指针和闭包对Golang性能的影响 Apr 15, 2024 am 10:36 AM

函数指针和闭包对Go性能的影响如下:函数指针:稍慢于直接调用,但可提高可读性和可复用性。闭包:通常更慢,但可封装数据和行为。实战案例:函数指针可优化排序算法,闭包可创建事件处理程序,但会带来性能损失。

golang函数闭包在测试中的作用 golang函数闭包在测试中的作用 Apr 24, 2024 am 08:54 AM

Go语言函数闭包在单元测试中发挥着至关重要的作用:捕获值:闭包可以访问外部作用域的变量,允许在嵌套函数中捕获和重用测试参数。简化测试代码:通过捕获值,闭包消除了对每个循环重复设置参数的需求,从而简化了测试代码。提高可读性:使用闭包可以组织测试逻辑,使测试代码更清晰、更易于阅读。

golang匿名函数和闭包的优缺点总结 golang匿名函数和闭包的优缺点总结 May 05, 2024 am 09:54 AM

匿名函数简洁、匿名,但可读性差、调试困难;闭包能封装数据、管理状态,但可能导致内存消耗和循环引用。实战案例:匿名函数可用于简单数值处理,闭包可实现状态管理。

See all articles