0

0

闭包有话说 - 大前端

高洛峰

高洛峰

发布时间:2017-02-08 17:57:48

|

1174人浏览过

|

来源于php中文网

原创

引言

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

一个简短的的问题

首先,来看一个问题。

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

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

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

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

立即学习前端免费学习笔记(深入)”;

代码描述:

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 是通过普通的词法作用域查找的,而不是通过闭包。

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

    

  • some text1
  • some text2
  • some text3
  • 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 因此 ,输出显示的是循环结束时 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

    宠物商店
    宠物商店

    目前,PetShop已经从最初的2.0、3.0等版本,发展到了最新的4.0版本。PetShop 4.0使用ASP.NET 2.0技术开发,其中加入了众多新增特性,因此,在性能、代码数量、可扩展性等方面有了重大改善。可以说,学习PetShop 4.0是深入掌握ASP.NET 2.0技术的捷径。本节将引领读者逐步了解PetShop 4.0的方方面面,包括应用程序安装、功能和用户界面简介、解决方案和体系

    下载

    引言

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

    一个简短的的问题

    首先,来看一个问题。

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

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

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

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

    立即学习前端免费学习笔记(深入)”;

    代码描述:

    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 是通过普通的词法作用域查找的,而不是通过闭包。

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

        

  • some text1
  • some text2
  • some text3
  • 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 因此 ,输出显示的是循环结束时 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中文网!

    热门AI工具

    更多
    DeepSeek
    DeepSeek

    幻方量化公司旗下的开源大模型平台

    豆包大模型
    豆包大模型

    字节跳动自主研发的一系列大型语言模型

    通义千问
    通义千问

    阿里巴巴推出的全能AI助手

    腾讯元宝
    腾讯元宝

    腾讯混元平台推出的AI助手

    文心一言
    文心一言

    文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

    讯飞写作
    讯飞写作

    基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

    即梦AI
    即梦AI

    一站式AI创作平台,免费AI图片和视频生成。

    ChatGPT
    ChatGPT

    最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

    相关专题

    更多
    Kotlin协程编程与Spring Boot集成实践
    Kotlin协程编程与Spring Boot集成实践

    本专题围绕 Kotlin 协程机制展开,深入讲解挂起函数、协程作用域、结构化并发与异常处理机制,并结合 Spring Boot 展示协程在后端开发中的实际应用。内容涵盖异步接口设计、数据库调用优化、线程资源管理以及性能调优策略,帮助开发者构建更加简洁高效的 Kotlin 后端服务架构。

    2

    2026.02.12

    2026春节习俗大全
    2026春节习俗大全

    本专题整合了2026春节习俗大全,阅读专题下面的文章了解更多详细内容。

    191

    2026.02.11

    Yandex网页版官方入口使用指南_国际版与俄罗斯版访问方法解析
    Yandex网页版官方入口使用指南_国际版与俄罗斯版访问方法解析

    本专题全面整理了Yandex搜索引擎的官方入口信息,涵盖国际版与俄罗斯版官网访问方式、网页版直达入口及免登录使用说明,帮助用户快速、安全地进入Yandex官网,高效使用其搜索与相关服务。

    594

    2026.02.11

    虫虫漫画网页版入口与免费阅读指南_正版漫画全集在线查看方法
    虫虫漫画网页版入口与免费阅读指南_正版漫画全集在线查看方法

    本专题系统整理了虫虫漫画官网及网页版最新入口,涵盖免登录观看、正版漫画全集在线阅读方式,并汇总稳定可用的访问渠道,帮助用户快速找到虫虫漫画官方页面,轻松在线阅读各类热门漫画内容。

    91

    2026.02.11

    Docker容器化部署与DevOps实践
    Docker容器化部署与DevOps实践

    本专题面向后端与运维开发者,系统讲解 Docker 容器化技术在实际项目中的应用。内容涵盖 Docker 镜像构建、容器运行机制、Docker Compose 多服务编排,以及在 DevOps 流程中的持续集成与持续部署实践。通过真实场景演示,帮助开发者实现应用的快速部署、环境一致性与运维自动化。

    7

    2026.02.11

    Rust异步编程与Tokio运行时实战
    Rust异步编程与Tokio运行时实战

    本专题聚焦 Rust 语言的异步编程模型,深入讲解 async/await 机制与 Tokio 运行时的核心原理。内容包括异步任务调度、Future 执行模型、并发安全、网络 IO 编程以及高并发场景下的性能优化。通过实战示例,帮助开发者使用 Rust 构建高性能、低延迟的后端服务与网络应用。

    1

    2026.02.11

    Spring Boot企业级开发与MyBatis Plus实战
    Spring Boot企业级开发与MyBatis Plus实战

    本专题面向 Java 后端开发者,系统讲解如何基于 Spring Boot 与 MyBatis Plus 构建高效、规范的企业级应用。内容涵盖项目架构设计、数据访问层封装、通用 CRUD 实现、分页与条件查询、代码生成器以及常见性能优化方案。通过完整实战案例,帮助开发者提升后端开发效率,减少重复代码,快速交付稳定可维护的业务系统。

    8

    2026.02.11

    包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法
    包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法

    本专题汇总了包子漫画官网和网页版入口,提供最新章节抢先看方法、正版免费阅读指南,以及稳定访问方式,帮助用户快速直达包子漫画页面,无广告畅享全集漫画内容。

    210

    2026.02.10

    MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法
    MC.JS网页版快速畅玩指南_MC.JS官网在线入口及免安装体验方法

    本专题汇总了MC.JS官网入口和网页版快速畅玩方法,提供免安装访问、不同版本(1.8.8、1.12.8)在线体验指南,以及正版网页端操作说明,帮助玩家轻松进入MC.JS世界,实现即时畅玩与高效体验。

    108

    2026.02.10

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    CSS3 教程
    CSS3 教程

    共18课时 | 5.4万人学习

    PostgreSQL 教程
    PostgreSQL 教程

    共48课时 | 8.9万人学习

    NumPy 教程
    NumPy 教程

    共44课时 | 3.3万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号