您的位置:首页 > Web前端 > JavaScript

js学习笔记-第五章函数表达式-闭包和模仿块级作用域

2019-01-11 15:22 585 查看

 

//闭包的概念
            //闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,
            //示例
            function createComparisonFunction(propertyName) {

                return function(object1, object2) {
                    var value1 = object1[propertyName];
                    var value2 = object2[propertyName];

                    if(value1 < value2) {
                        return -1;
                    } else if(value1 > value2) {
                        return 1;
                    } else {
                        return 0;
                    }
                };
            }
            //这两行代码访问了外部函数中的变量 propertyName。

            function compare(value1, value2) {
                if(value1 < value2) {
                    return -1;
                } else if(value1 > value2) {
                    return 1;
                } else {
                    return 0;
                }
            }
            var result = compare(5, 10);
            //先定义了 compare()函数
            //又在全局作用域中调用了它
            //当调用 compare()时,会创建一个包含 arguments、value1 和 value2 的活动对象。全局执行环境的变量对象(包含 result和 compare)在 compare()执行环境的作用域链中则处于第二位

 

 

//闭包的基本使用:一是可以读取函数内部的 变量,二是让变量的值始终保持在内存中

            function test() {
                var name = "zhangsan";
            }
            //如何获取函数内部的name
            /*function test() {
                var name = "zhangsan";
                //通过返回一个函数
                return function() {
                    return name;
                    //console.log(name);
                }
            }

            var fun = test();
            fun(); //zhangsan
            console.log(fun()); //zhangsan;
*/
            /*由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过
度使用闭包可能会导致内存占用过多,我们建议读者只在绝对必要时再考虑使用闭
包。虽然像 V8 等优化后的 JavaScript 引擎会尝试回收被闭包占用的内存,但请大家
还是要慎重使用闭包*/
            /*作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最
            后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。下面这个例子可以清晰地说
            明这个问题
                    */
            function createFunctions() {
                var result = new Array();
                for(var i = 0; i < 10; i++) {
                    result[i] = function() {
                        return i;
                    };
                }
                return result;
            }
            /*因为每个函数的作用域链中
            都保存着 createFunctions() 函数的活动对象,所以它们引用的都是同一个变量 i 。 当
            createFunctions()函数返回后,变量 i 的值是 10,此时每个函数都引用着保存变量 i 的同一个变量
            对象,所以在每个函数内部 i 的值都是 10。
            */
            /*但是,我们可以通过创建另一个匿名函数强制让闭包的行为
            符合预期,如下所示。*/

            function createFunctions() {
                var result = new Array();
                for(var i = 0; i < 10; i++) {
                    result[i] = function(num) {
                        return function() {
                            return num;
                        };
                    }(i);
                }
                return result;
            }

            //this对象:this 对象是在运行时基于函数的执行环境绑定的:在全局函数中,this 等于 window,而当函数被作为某个对象的方法调用时,this 等于那个对象
            //匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window①。
            var name = "The Window";
            var object = {
                name: "My Object",
                getNameFunc: function() {
                    return function() {
                        return this.name;
                    };
                }
            };
            console.log(object.getNameFunc()()); //"The Window"(在非严格模式下)

            //如何让它取到object中的name呢?

            var name = "The Window";
            var object = {
                name: "My Object",
                getNameFunc: function() {
                    var that = this; //保存该对象的引用
                    return function() {
                        //输出该对象name
                        return that.name;
                    };
                }
            };
            console.log(object.getNameFunc()()); //"My Object"(在非严格模式下)

            //内存泄漏
            //如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁。来看下面的例子

            function assignHandler() {
                var element = document.getElementById("someElement");
                element.onclick = function() {
                    alert(element.id);
                };
            }

            /*由于匿名函数保存了一个对 assignHandler()的活动对象的引用,因此
就会导致无法减少 element 的引用数。只要匿名函数存在,element 的引用数至少也是 1,因此它所
占用的内存就永远不会被回收。*/
            //这个问题可以通过稍微改写一下代码来解决

            function assignHandler() {
                var element = document.getElementById("someElement");
                var id = element.id;

                element.onclick = function() {
                    alert(id);
                };

                element = null;
            }
            /*必须要记住:闭包会引用包含函数
            的整个活动对象,而其中包含着 element。即使闭包不直接引用 element,包含函数的活动对象中也
            仍然会保存一个引用。因此,有必要把 element 变量设置为 null。这样就能够解除对 DOM 对象的引
            用,顺利地减少其引用数,确保正常回收其占用的内存*/

            //模仿块级作用域
            //JavaScript 没有块级作用域的概念。这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的

            /*function outputNumbers(count) {
                for(var i = 0; i < count; i++) {
                    console.log(i);
                }
                //循环结束后也可取到i的值
                console.log(i); //计数
            }
            outputNumbers(2) //2*/

            //即使像下面这样错误地重新声明同一个变量,也不会改变它的值
            function outputNumbers(count) {
                for(var i = 0; i < count; i++) {
                    console.log(i);
                }

                var i; //重新声明变量
                console.log(i); //计数:count
            }
            //outputNumbers(2)

            /*JavaScript 从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不
见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题。
用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示。*/

            /*(function() {
                //这里是块级作用域
                console.log(11);
            })();*/

            /*以上代码定义并立即调用了一个匿名函数。将函数声明包含在一对圆括号中,表示它实际上是一个
函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。*/
            //理解匿名函数的调用
            //一下示例
            /*var count = 5;
            outputNumbers(count);*/
            /*里初始化了变量 count,将其值设置为 5。当然,这里的变量是没有必要的,因为可以把值直接
传给函数。为了让代码更简洁,我们在调用函数时用 5 来代替变量 count,如下所示。*/
            //outputNumbers(5);
            //变量只不过是值的另一种表现形式
            /*var someFunction = function() {
                //这里是块级作用域
            };
            someFunction();*/
            /*定义了一个函数,然后立即调用了它。定义函数的方式是创建一个匿名函数,并把匿名
函数赋值给变量 someFunction。而调用函数的方式是在函数名称后面添加一对圆括号,即
someFunction()*/
            //那在这里是不是也可以用函数的值直接取代函数名呢?

            /*function() {
                //这里是块级作用域
            }(); //出错!;
            */
            /*是因为 JavaScript 将 function 关键字当作一个函数声明的开始,而函
            数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式,
            只要像下面这样给它加上一对圆括号即可*/
            //一是作为私有作用域(由于JavaScript中没有块级作用域的概念),而是匿名函数的自我调用
            (function() {
                //这里是块级作用域
            })();

            //无论在什么地方,只要临时需要一些变量,就可以使用私有作用域,例如:
            function outputNumbers(count) {
                (function() {
                    for(var i = 0; i < count; i++) {
                        console.log(i);
                    }
                })();

                console.log(i); //导致一个错误!
            }
            //因此,变量 i 只能在循环中使用,使用后即被销毁
            //而在私有作用域中能够访问变量 count,是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量
            /*在一个由很多开发人员共同参与的大型
应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可
以使用自己的变量,又不必担心搞乱全局作用域。*/
            //outputNumbers(2)

            (function() {

                var now = new Date();
                if(now.getMonth() == 0 && now.getDate() == 1) {
                    alert("Happy new year!");
                }
            })();
            //这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
        </script>
    </head>

    <body>
        <div id="someElement">someElement</div>
    </body>
 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: