理解闭包的

我是怎样理解闭包的

渐入闭包

假设有这个需求,写个函数,动态生成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
标签: 闭包
原文地址:https://www.cnblogs.com/Leo_wl/p/3329442.html