zoukankan      html  css  js  c++  java
  • JS闭包学习笔记

    什么是闭包

    我们先来看看闭包的定义:

    所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

    上面这句话对于初学者来说毫无意义,要弄懂闭包,首先我们来看看js变量的作用域:

    • 同大多数语言一样,JS的变量分为全局变量和局部变量。
    • 函数内部可以直接调用全局变量
    • 外部无法调用函数内的局部变量
    ps:如果在函数内部使用未被 var 定义的变量,相当于实际声明了一个全局变量!!!

    下面是代码实例:

    //函数内部调用全局变量
    var value = 'abc';
    function show() {
        alert(value);
    }
    
    //外部直接调用局部变量
    function define() {
        var innerValue = 'hello';
    }
    alert(innerValue);//报错 Uncaught ReferenceError: innerValue is not defined
    

    上面关于js的变量作用域的内容应该很容易理解。但似乎对于理解闭包人没有太大的帮助。
    看完上边的内容,我仍然有以下的问题

    • 什么是闭包(毫无帮助)
    • 闭包有什么用?
    • 闭包的写法

    不要放弃,我们继续往下看 》》》

    我们先来看看闭包的写法:

    //type 1:最基本的形式,方便理解
    function a() {
        var value= 'local';
        function b() {
            alert(value);
        }
        return b;
    }
    var c = a();
    c();
    
    //type 2:构造方法赋值,
    function a(r) {
       //do something;
    }
    a.value='value';
    a.prototype.show= function() {
        return a.value ;
    }
    var c = new a(1.0);
    alert(c.show());
    
    //type 3:与type 1类似,不过是将被引用的变量和方法都放入了同一个对象obj中
    var a= function() {
        var obj = new Object();
        obj.value = 'value';
        obj.show = function() {
            return obj.value;
        }
        return obj;
    }
    var c = new a();
    alert(c.show());
    
    //type 4:声明一个对象,定义对象的属性和方法
    var a = new Object();
    a.value = 'value';
    a.show = function() {
        return this.value;
    }
    alert(a.show());
    
    //type 5:对象的另一种形式
    var a = {
        value:'value',
        show: function() {
            return this.value;
        }
    }
    alert(a.show());
    
    //type 6:Function方式
    var a= new Function("this.value = 'value';this.show= function() {return this.value;}");  
    alert((new a()).show()); 
    

    深入理解闭包

    接下来,我们基于type 1的方式,深入的解析一下闭包。首先我们必须掌握以下几个概念:

    • 函数的执行环境(excution context)
    • 活动对象(call object)
    • 作用域(scope)
    • 作用域链(scope chain)

    概念解释:

    执行环境:每调用一个函数(执行函数)时,系统会为该函数创建一个封闭的局部的运行环境,即该函数的执行环境。函数总是在自己的执行环境中执行,如读取局部变量、函数参数、运行内部逻辑。创建执行环境的过程包含了创建函数的 作用域,函数也是在自己的作用域下执行的。从另一个角度说,每个函数执行环境都有一个 作用域链,子函数的 作用域链 包括它的父函数的 作用域链

    函数作用域 分为 词法作用域(lexical scope)动态作用域
    词法作用域:函数定义时的 作用域,即静态作用域。当一个函数定义时,它的 词法作用域 就确定了,词法作用域 说明的是在函数结构的嵌套关系下,函数作用的范围。这个时候也就形成了该函数的 作用域链作用域链 就是把那些具有嵌套层级关系的 作用域 串联起来。函数的内部[[scope]]属性指向了该 作用域链
    动态作用域:函数调用时的 作用域。当一个函数被调用时,首先将函数内部[[scope]]属性指向了函数的 作用域链,然后会创建一个 调用对象,并用 该调用对象 记录 函数参数 和函数的 局部变量,将其至于 作用域链 的顶部。动态作用域 就是通过把该调用对象加到 作用域链 的顶部来创建的,此时的[[scope]]除了具有定义时的 作用域链,还具有了调用时创建的 调用对象。换句话说,执行环境下的 作用域 等于该函数定义时就确定的 作用域链 加上该函数刚刚创建的 调用对象,从而也形成了新的 作用域链。所以说是 动态的作用域,并且 作用域链 也随之发生了变化。再看这里的 作用域,其实是一个对象链,这些对象就是函数调用时创建的 调用对象,以及它上面一层层的 调用对象 直到最上层的全局对象。

    a函数从定义到被调用的过程:

    1.当定义函数a的时候,javascript解释器会将函数a的作用域链(scope chain)设置为 定义a时a所在的环境,如果a是一个全局函数,则a的作用域链中只有window对象;
    2.当执行函数a的时候,a会进入相应的 执行环境(excution context)
    3.在创建 执行环境 的过程中,首先会为a添加一个scope属性,即a的 作用域,这个值为第1步中的 作用域链
    4.接下来 执行环境 会创建一个 活动对象(call object)活动对象 也是一个拥有属性的对象,但它不具有原型而且 不能通过JavaScript代码直接访问 。创建完 活动对象 后,把 活动对象 添加到a的 作用域链 的最顶端。此时a的 作用域链 包含了两个对象:a的 活动对象 和window对象;
    5.接着在 活动对象 上添加一个arguments属性,用来保存调用函数a时所传递的参数;
    6.最后,把所有函数a的形参和内部函数b的引用也添加到a的 活动对象 上。这一步中,完成了函数b的定义。
    ps:当函数b执行的时候过程也如上边一样,因此,执行时b的 作用域链 包含了3个对象:b的 活动对象 、a的 活动对象 和window对象。

    ok,虽然这么两大段东西没有直接讲闭包,不过是不是大家的头脑都清晰了许多?现在再回头看看js里面闭包的定义,是不是也不在一头雾水了?清楚的掌握函数从定义到调用的整个细节,是掌握闭包的基础。所以小伙伴要是还不理解,请认真仔细的将上面的内容多读几遍。

    为什么要使用闭包

    最后我们来看看,为什么要使用闭包?什么情况需要用到闭包呢?
    书面的解释是这样的:在动态执行环境中,数据实时地发生变化,为了保持这些非持久型变量的值,我们用闭包这种载体来存储这些动态数据。(哦,好像很有道理,不过我还是不懂)

    闭包的应用举例,包括但不限于以下场景

    • setTimeout/setInterval
    • 数据缓存(这个有待商榷,看后面的实例,似乎并不需要用到闭包)
    • 对象封装(模拟面向对象的代码风格)
    • 回调函数(callback) //无实例
    • 事件句柄(event handle) //无实例
      ps:无实例表示技师还没理解透彻,,以后再补上。

    代码示例:
    1.setTimeout

    var arr = [4, 5, 6, 8, 7, 9, 3, 2, 1, 0];
    var $ = function(id) {
        return document.getElementById(id);
    }
    var Sort = {
        Insert: function() {
            for (var i = 1; i < arr.length; i++) {
                for (var j = 0; j < i; j++) {
                    if (arr[i] < arr[j]) {
                        arr[i] = [arr[j], arr[j] = arr[i]][0];
                    }
                }
                //非闭包,无法保存排序过程,每次输出都是最终结果
                setTimeout(function() {
                    $("proc").innerHTML += arr + "<br/>";
                }, i * 500);
                //闭包写法
                /* //临时变量方式
                    setTimeout((function() {
                    var m = [];
                    for (var j = 0; j < arr.length; j++) {
                        m[j] = arr[j];
                    }
                    return function() {
                        $("proc").innerHTML += m + "<br>";
                    }
                })(), i * 500);*/
                //or参数方式
                /*
                setTimeout((function(m) {
                    return function() {
                        $("proc").innerHTML += m + "<br>";
                    }
                })(arr.join(",")), i * 500);
    			*/
            }
            return arr;
        }
    }
    
    <!DOCTYPE html> 
    <html> 
        <head> 
            <title></title> 
        </head> 
        <body> 
    <div> 
    v    ar a = [4, 5, 6, 8, 7, 9, 3, 2, 1, 0];</div> 
    <div> 
    <input type="button" value="插入排序" onclick="Sort.Insert();" /> 
    </div> 
    Proc:
    <div id="proc"> 
    </div> 
        </body> 
    </html> 
    

    2.数据缓存

    var CachedBox = (function() {
        var cache = {}
          , catchNameArr = []
          , catchMax = 10000;
        return {
            getCatch: function(name) {
                if (name in cache) {
                    return cache[name];
                }
                var value = name;
                cache[name] = value;
                catchNameArr.push(name);
                this.clearOldCatch();
                return value;
            },
            clearOldCatch: function() {
                if (catchNameArr.length > catchMax) {
                    delete cache[catchNameArr.shift()];
                }
            }
        };
    });
    var box = new CachedBox();
    //字段不在缓存中,需要重新创建value变量
    var name = box.getCatch('cache');
    alert(name);
    //直接读取缓存数据
    var nameAgain = box.getCatch('cache');
    alert(nameAgain);
    

    3.封装与面向对象模拟

    //封装
    var person = function(){    
        //变量作用域为函数内部,外部无法访问    
        var name = "default"; 
        return {    
           getName : function(){    
               return name;    
           },    
           setName : function(newName){    
               name = newName;    
           }    
        }    
    }();    
         
    console.log(person.name);    //直接访问,结果为undefined    
    console.log(person.getName());    
    console.log(person.setName("abruzzi"));    
    console.log(person.getName());   
    //模拟面向对象
    function Person() {
        var name = "default";
        return {
           getName : function(){
               return name;
           },
           setName : function(newName){
               name = newName;
           }
        }
    }
    var jack = new Person();
    //do something;
    

    另外,从阮一峰大神博客看到的一个思考题:

    var name = "The Window";
      var object = {
        name : "My Object",
        getNameFunc : function(){
          return function(){
            return this.name;
          };
        }
      };
      alert(object.getNameFunc()());
    

    大家可以自己思考一下,预测一下结果,自己放到浏览器里运行一下,看是否预测正确,是否真的清楚结果是怎么得来的(没错,这道题集合了this和闭包)。我看阮大神博客下面的评论,貌似没有一个和我预期的一致,告诉我你的答案,欢迎一起交流学习。

    参考:

  • 相关阅读:
    出差归来
    五一假期的开端
    哭。。。五一这就过拉。。。还没什么感觉那。。。呜呜
    爱姬家族新成员。。。
    大道至简读后感
    假期进度报告
    假期报告
    假期进度报告
    假期进度报告
    假期进度报告
  • 原文地址:https://www.cnblogs.com/unck-luck/p/5371559.html
Copyright © 2011-2022 走看看