zoukankan      html  css  js  c++  java
  • 理解闭包的

    我是怎样理解闭包的

    渐入闭包

    假设有这个需求,写个函数,动态生成HTML,每次生成的 HTML 有一部分的是固定不变的。

    于是
    复制代码
    function buildHtml() {
        var template = ['<table><tr><td>',
            '',
            '</td><td>',
            '',
            '<td></td></tr></table>'];
        template[1] = args[0];
        template[3] = args[1];
        return template.join('');
    }
    复制代码

     分析下,每次执行函数时,都会重复定义个template,执行完函数后,这个变量就被销毁;这样重复定义销毁,肯定对性能不好,考虑把template这个变量内容提取到外面,

    比如改造后成了这样
     
    这时候,你又有个需求,要把这个函数移到其它页面,移动的时候,你必须保证两件事,一个是template和函数名不能和已有命名空间的变量重复,并且这样的结构显然不方面移动,于是乎你考虑这样实现
    复制代码
    $(function () {
        //...
        var template = ['<table><tr><td>',
            '',
            '</td><td>',
            '',
            '<td></td></tr></table>'];
        function buildHtml(args) {
            template[1] = args[0];
            template[3] = args[1];
            return template.join('');
    
        }
    
        //...这里可以调用上面的代码
    })
    复制代码

     这样移动代码时,貌似就不要考虑两个变量名重复,貌似也解决了一部分问题。可是还有一种更好的方案,比如

    复制代码
    var buildHtml = (function () {
        var template = ['<table><tr><td>',
            '',
            '</td><td>',
            '',
            '<td></td></tr></table>'];
        return function (args) {
            template[1] = args[0];
            template[3] = args[1];
            return template.join('');
    
        }
    
    })();
    复制代码

      

    这样结构上更紧凑,移动这段代码更方便,template也没暴露出来,这里就用到了闭包。如果对(function(){...})()用法不熟悉,最好先google下。实在一时理解不了,我这里还将上面例子变通下。
    复制代码
    function xx() {
        var template = ['<table><tr><td>',
            '',
            '</td><td>',
            '',
            '<td></td></tr></table>'];
        return function (args) {
            template[1] = args[0];
            template[3] = args[1];
            return template.join('');
    
        }
    };
    var buildHtml = xx();
    复制代码

     然后就可以这样调用了buildHtml(/*参数*/);

     
    闭包就像提供了接口或者公用方法,来访问和修改私有属性。这样的结构,还常见于定义一个对象的私有属性和私有方法。
    比如 
    复制代码
    var win = (function () {
        var fn1 = function () {}, //私有方法
        pro = 1; //私有属性
        return {
            outFns1 : function () {
                //调用fn1或pro
            },
            outFns2 : function () {
                //调用fn1或pro
    
            }
        }
    })();
    复制代码

     上面的例子,貌似都用到return,不用return呢?实际上面第二个例子,稍微改造下,也是闭包

    复制代码
    $(function () {
        //...
        var template = ['<table><tr><td>',
            '',
            '</td><td>',
            '',
            '<td></td></tr></table>'];
        buildHtml = function (args) {
            template[1] = args[0];
            template[3] = args[1];
            return template.join('');
    
        }
    
        //...这里可以调用上面的代码
    })
    复制代码

     buildHtml因为没有var声明,所以是全局变量,这样在$的回调函数外面,也可以通过buildHtml访问template。

    其它形式,诸如将内部函数赋给对象的属性也可以,此变量可以是全局的。
     
    闭包概念
    至此,来总结下什么是闭包?
    在一个函数(这里称为父函数)内部定义一个函数(内部函数通过作用域链访问父函数的局部变量),结束时父函数返回内部函数的引用,或者在定义的时候将内部函数引用暴露出来。执行父函数时,我们取得了内部函数的引用,执行完父函数后,父函数相关变量所占据的内存仍保留着,仍可以通过闭包访问和修改,也就是说父函数的当前活动对象被维持了(这里涉及作用域链知识,下面有介绍),直到引用消失。
    闭包作用看起来,就像提供了活的键值对,然后你在内部函数里面可以访问。闭包是定义时孕育的,在执行时形成。
    举这个例子,只是加深对闭包的理解。为什么是“活”的。
     
    复制代码
    function wrapFns() {
        var arr = [];
        for (var i = 10; i--; ) {
            arr[i] = function () {
                return i;
            }
        }
        return arr;
    }
    var fns = wrapFns();
    console.log(fns[10]()); // 值是多少?
    复制代码
    值为0并不是10。当然这里你要它为10,也有方法,这里就不说了。
     
    闭包的应用
    1.正如开始所举的例子,闭包用来封装插件
    你在网络上download一个js插件,基本上所有插件都用到下面类似结构来实现封装,这样实现变量私有化,且还可以在外面调用或改变私有变量值
    (function (window, undefined) {
        window.ymPrompt = {}; //可以全局访问了
    })(window);

     2.闭包来实现记忆功能

    function aa(num) {
        if (num == 0) {
            return 1;
        }
        return num * aa(num - 1);
    }
    改造后
    复制代码
    var aa = (function () {
        var cache = [];
        return function (num) {
            if (!cache[num]) {
                if (num == 0) {
                    cache[num] = 1;
                }
                cache[num] = num * aa(num - 1);
            }
            return cache[num];
        }
    })();
    复制代码
    类似的,可考虑用闭包实现一种类似情形,“传入一个函数,返回一个带记忆功能函数”,可参考《权威指南》P.199
     
    3.其它等等
    如上面例子中的对象的私有属性方法,毕竟在标识符前''_"仍是可以被访问的。
     
     
    闭包及作用域的内部机制
    关于闭包,涉及的概念有作用域链,函数一层层执行时,产生的执行环境栈(IE调试器中的调用堆栈)、标识符的解析,有关作用域链又包含函数内部隐含属性[[scope]](只能通过JavaScript引擎访问),[[scope]]引用的的作用域链又是如何产生的?
    这里来探讨下,如果描述的不清楚,勿见怪。
    ①每个函数都有个[[scope]]属性,指向函数定义时的执行环境上下文。--闭包就在这孕育的。
    ②假如A是全局函数,B是A的内部函数。执行A函数时,执行环境的上下文指向一个作用域链,链的第一个对象是当前函数的活动对象(this、参数、局部变量),第二个对象是全局window。
    ③当执行代码运行到B定义地方, 设置 B的[[scope]]属性为当前执行环境的上下文。
    ④执行B函数时,JavaScript引擎将上一执行环境入栈,并产生新的执行环境,这时的执行环境的上下文指向一个作用域链,链的第一个对象是当前函数的活动对象(this、参数、局部变量),第二个活动对象是A函数产生的,第三个window。
    ⑤B函数里面访问一个变量,要进行标志符解析(JavaScript原型也有标识符解析),它从当前上下文指向的作用域链的第一个对象开始查找,找不到就查找第二个对象,直到找到相关值就立即返回,如果没找到,报undefined错误。
    ⑥如果A函数仅定义了B函数,然后执行它,并没有返回给外部。当A函数执行完毕,A所占据的内存就此回收。
    如果B函数引用给外部,同时它的[[scope]]([[scope]]中的活动对象,比如父函数A的活动对象)会被维持,当B函数再次调用时,仍然可以范围。--闭包就在这产生的。
     
    作用域链临时变更情况
    a、如使用with(obj){},try{}catch(obj){},会将obj添加到作用域链的首部,其它活动对象相应后移一位,直到with结束。
    b、使用Function定义的函数对象,函数对象的[[scope]]属性置为window。
     
    由于我看的大多是翻译的外文,一些概念变来变去的,不像教科书那样死板明确,理解时要变通,本文中借用这些概念时,也不知道准不准确。
     
    this
    其实很简单,傻子般记住一点就行。
    执行时,看所属函数是对象方法还是一个单独的函数?单独的函数this===window;对象方法,this == 对象。
    复制代码
    function A() {
        function B() {
            console.log(this === window); //true 单独的函数
        }
        this.a = 1;
    }
    A.method = function () {
        console.log(this.a); //1 对象方法,this===A
    }
    var fn = A.method;
    fn(); //undefined 单独函数
    复制代码

      

    闭包注意 
    就如最开始举的例子,不小心忘了给函数加上var ,就产生了闭包,外部函数里面的局部变量得不到释放,如果是富客户端应用,比如你点击了页面的一个控件,通过一系列程序逻辑,调用了嵌套很深的函数,在一层层调用时,产生了一系列闭包,长期积累,内存消耗越来越大,页面反应越来越慢。
    总之,如果你对JavaScript语法没有深入了解很容易产生闭包,导致性能问题。比如我们平时的项目开发中,基本还是基于面向过程的编程,每个功能定义个函数,然后按大功能组装在一起,大功能再按大功能组装在一起,编码没遵循常用JavaScript规范。
     
    1、由于闭包使变量长期占据内存,所以不要随便使用闭包,注意意外引入闭包。
    一个这样的构造函数
    复制代码
    function Construtor() {
        var aa;
        this.aa = '1';
        this.fun1 = function () {};
        this.fun2 = function () {};
        this.fun3...
    }
    复制代码

     在fun1 fun2 fun3中没有使用aa,没有必要用闭包,Construtor.prototype.fun1 = function(){}

    退一步讲,即使要使用aa,可将其命名为this.aaa,然后this.aaa访问。
     
     
    2、我们使用闭包时,可以将不需继续调用局部变量在父函数末尾置为null。
     
    总结几点
    1、一个函数能访问什么变量,取决于它定义时所处的代码位置或上下文环境,而不是执行时上下文环境。
    2、闭包就像提供了接口或者公用方法,来访问和修改私有属性,并且保持私有变量不销毁。
    3、this很简单。
     
    边写边想,例子也是随手写的没测试,不过应该没问题,有错误、语言不顺地方,望指出;以后还想到什么,再补充吧。
    有任何问题,欢迎留言交流。 注意:已解决的问题,会在整理后删除掉。 *******站在巨人的肩膀上
     

    据说每个大牛、小牛都应该有自己的库——Ajax

     

    蹉跎到今天终于要写Ajax部分了,平时工作中除了选择器我用jQuery的最多的就是ajax,所以这部分在自己的框架中必不可少。

    XMLHttpRequest

    我以为对每个使用过Ajax的人来说XMLHttpRequest对象肯定是如雷贯耳,可是在和公司小伙伴儿的讨论中我意识到,这个对象对有些已经使用Ajax很久的人来说仍然很陌生,jQuery等类库把XMLHttpRequest对象封装的太好了,以至于我们都不知道自己在使用它。关于JavaScript原生的Ajax之前写过一篇Ajax初步理解的博客,为了方便下面书写,在这里再介绍一下几个重点概念。

    Ajax的核心是JavaScript对象XmlHttpRequest,这个对象为向服务器发送请求和解析服务器响应提供了流畅的接口。XmlHttpRequest可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。该对象有两个重要方法open与send

    调用send()方法,请求被发往服务器,服务器根据请求生成响应(Response),传回给XHR对象,在收到响应后相应数据会填充到XHR对象的属性,有四个相关属性会被填充:

    1. responseText:作为响应主体被返回的文本

    2. responseXML:如果响应内容的类型是”text/xml”或”application/xml”,这个属性将保存包含着相应数据的XML文档

    3. status:响应的HTTP状态(200,404,500等)

    4. statusText:HTTP状态说明

    XHR对象有一个readyState属性,该属性表示请求/响应过程中的当前活动阶段,每当readyState值改变的时候都会触发一次onreadystatechange事件。

    浏览器兼容的创建XHR对象方法

    大家都懂得,IE总会制造些麻烦,看看浏览器兼容的创建XHR对象方法,具体过程可以看看Ajax初步理解中的说明

    复制代码
    function __createXHR(){
                    var xhr = null;
                    try {
                        // Firefox, Opera 8.0+, Safari,IE7+
                        xhr = new XMLHttpRequest();
                    }
                    catch (e) {
                        // Internet Explorer 
                        try {
                            xhr = new ActiveXObject("Msxml2.XMLHTTP");
                        }
                        catch (e) {
                            try {
                                xhr = new ActiveXObject("Microsoft.XMLHTTP");
                            }
                            catch (e) {
                                xhr = null;
                            }
                        }
                    }
                    return xhr;
                }
    复制代码

    封装

    其实看看第一段关于XHR对象的说明就明白为什么jQuery等类库会对XHR做如此彻底的封装了,这个对象实在是太复杂了,参数的设置都要有时机问题,所以我也走上了封装之路。

    复制代码
    ajax: function(configs) { 
                        var settings = { 
                            "url": "",  //请求地址
                            "method": "post", //请求使用方法
                            "user": "", //用户名
                            "password": "", //密码
                            "data": null, //参数(text/json)
                            "responseType": "text", //返回值获取方式 text/xml 
                            "headers": {}, //自定义的HttpHeader
                            "enableCache":true, //是否使用缓存
                            "onSucceed": null, //成功句柄
                            "onClientError": null, //客户端错误句柄
                            "onServerError": null //服务器端错误句柄
                        }; 
                        for (s in settings) { 
                            settings[s] = configs[s] ? configs[s] : settings[s];  //应用自定义配置
                        } 
                        var xhr = _createXHR();  //创建XHR对象
                        xhr.onreadystatechange = function() { 
                            if (xhr.readyState == 4) {  //请求完成,响应就绪
                                var result = settings["responseType"] == "text" ? xhr.responseText : xhr.responseXML; //返回值类型
                                if (((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) && typeof settings['onSucceed'] == 'function') { //成功
                                    settings['onSucceed'](result, xhr.status); 
                                } else if (xhr.status >= 400 && xhr.status < 500) { //客户端出错,404啊神马的
                                    settings['onClientError'](result, xhr.status); 
                                } else if (xhr.status >= 500) { //服务器端出错
                                    settings['onServerError'](result, xhr.status); 
                                } 
                            } 
                        } 
                        xhr.open(settings['method'], settings['url'], settings['user'], settings['password']); //发送请求
                      
                        if (typeof settings['headers'] == 'object') { //设置自定义headers
                            var headers = settings['headers']; 
                            for (h in headers) { 
                                xhr.setRequestHeader(h, headers[h]); 
                            } 
                        } 
                        if(!settings['enableCache']){ //禁用缓存
                            xhr.setRequestHeader("If-Modified-Since","0");
                            }
                        
                        if (settings["method"].toLowerCase() == "post") { //post请求
                            xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); 
                            var data="";
                            if(typeof settings["data"]=='object')
                            {
                                for(d in settings["data"]){
                                    data+=(d+'='+settings["data"][d]);
                                    }
                              }else{
                                data=settings["data"];
                                  }
                            xhr.send(data); //传递参数
                        } else { 
                            xhr.send(); //get请求
                        } 
                    }
    复制代码

    最后

    这样一个简单的Ajax封装就完成了,使用的时候和jQuery类似

    复制代码
    ssLib.ajax({ 
                "url": "testajax.php",
                "data":{"name":"Byron"},
                "onSucceed": function(result) {
                    alert(result);
                } 
            });
    复制代码

    看起类不错,不过没实际使用过呢还,希望大家多给意见。

     
     
    分类: JavaScript
    标签: 闭包
  • 相关阅读:
    WPF 柱状图显示数据
    WPF 寻找控件模板中的元素
    WPF 寻找数据模板中的元素
    WPF VisualTreeHelper的使用
    WPF依赖项属性不需要包装属性也可以工作
    WPF依赖属性对内存的使用方式
    WPF Binding Path妙用
    WPF Binding Path妙用代码实现
    WPF Binding妙处-既无Path也无Source
    WPF ListView的使用
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3329442.html
Copyright © 2011-2022 走看看