javascript中闭包与作用域的理解

 

很多js的框架与插件编写都用到了闭包,所以,阅读和掌握闭包很有必要。最近学习vue框架时,经常会猜想很多功能的native js实现,很多都应用到了闭包,闭包除了目前已知的一些特性,如:可以保持局部变量以减少对全局作用域的污染外,一些情况下必须用闭包才能实现。

先贴一个根据自己的理解绘制的闭包原理:

关于闭包的几个重要的知识点需要理解:

1. javascript中,每次运行函数,都会创建一个新的对象,用来保存函数的内部变量,称之为——局部变量保存对象

2. 每次运行外部函数,内部闭包函数都会被重新定义一遍

3. 函数运行时,局部变量保存对象会保存在函数定义时的作用域链中,当函数执行完毕后,没有其他引用指向该作用域链时,会释放上述对象;若有其他引用指向时(如函数运行结束后返回一个闭包的引用),该对象不会被释放。

一些遇到的,必须用闭包的情况:

一、for循环后的外部引用指向同一个作用域,和设计期待背道而驰

在模拟vue的数据监控逻辑时,刚开始写的代码如下:

    function Observe (obj){
        for( prop in obj ){
            var oldval = obj[prop];
            Object.defineProperty( obj,prop,{
                get:function(){
                    return oldval;
                },
                set:function(newval){
                    console.log( prop+'的数据改变了,老的值:'+oldval+"新的值:"+newval );
                    oldval = newval;
                }
            } );
        }
    }

    var data = {
        name:'tester',
        age:16,
        class:'04071135'
    };

Observe( data );

在firefox控制台打印data.name,状况如下:

很明显,虽然这里为data的每个属性(name,age,class)定义了getter和setter存取器——即每个属性的getter/setter外部应用都指向了每次重新定义的闭包,但这些闭包共享了一个父函数作用域,在循环执行完毕后,该作用域内挂在的变量对象信息如下:

prop: 'class';

oldval:'04071135'

所以,当调用虽有的外部引用——属性的存取器getter和setter时,访问的是同一个变量对象,所以,执行结果都是一样的,再多打印一些信息看看:

经上述分析可知,因为这些外部引用都指向了同一个作用域里的变量对象才导致的bug,解决的办法就是,让每个外部引用都能保有自己独立的作用域,这就想到了闭包:

修改上述Observe函数为:

    function Observe( obj ){
        function defineprop( obj,prop ){
            //在没有开始define之前获取属性值,这样就不会陷入死循环
            var oldval = obj[prop];
            Object.defineProperty(obj,prop,{
                get:function(){
                    return oldval;
                },
                set:function(newval){
                    console.log( prop+'的数据改变了,老的值:'+oldval+"新的值:"+newval );
                    oldval = newval;
                }
            });
        }
        var keys = Object.keys( obj );
        for( var i=0;i<keys.length;i++ ){
            defineprop( obj,keys[i] );
        }
    }

Observe( data );

再在控制台打印测试,结果如下:

这里就是运用了闭包的特点,虽然内部定义了defineprop函数后就调用了,如果没有外部引用指向时,就会释放defineprop里的prop,oldval等,但因为data的属性存取器指向了defineprop内的闭包(get和set),所以,defineprop里的局部变量得得以保留。每个属性存取器都有了自己的作用域—持久化的变量保存对象,因此,访问起来就没什么问题了。

再看下面一个小例子,是上面的简化版;

var obj_arr = [{},{},{}];

    for( var i=0;i<obj_arr.length;i++ ){
        obj_arr[i]["getdata"] = function(){
            return i;
        }
    }

 

 

 此时在控制台访问一下对象的getdata看看:

这些引用均访问同一块公用的作用域;

根据上述的闭包思路重新改写代码:

    var obj_arr = [{},{},{}];
    function addData( val ){
        obj_arr[val]["getdata"] = function(){
            return val;
        }
    }
    for( var i=0;i<obj_arr.length;i++ ){
        addData(i);
    }

打印结果如下,正常。

 

二、闭包在作用域绑定方面也很有用

将方法的执行绑定在对象上以后,用户不用再关注方法执行的作用域,直接调用即可,这种思路的实现可以参考:https://www.cnblogs.com/surfer/p/9625725.html

未完待续...

posted @ 2018-09-12 15:49  sophel  阅读(366)  评论(0编辑  收藏  举报