JS 中闭包的变量 闭包与this

Posted 暗夜独狼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JS 中闭包的变量 闭包与this相关的知识,希望对你有一定的参考价值。

闭包与变量:

作用域链的一个副作用,闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。

    function fn1(){
        //创建一个数组
        var arr = new Array();
        //为数组赋值,此时i是保存在fn1 这个作用域中
        for (var i = 0; i <10; i++) {
            arr[i] = function(){
                return i
            }
        }
    return arr;
   }

   var fs = fn1();
   for (var i = 0; i < fs.length; i++) {
       fs[i]();   //此时通过闭包来调用函数,会去上一级作用域中找,这是i的值已经是10,所以会连续输出10个  10;
   };

解决方法:通过创建另一个匿名函数强制让闭包的行为符合预期,

 function fn1(){       
        var arr = new Array();     
        for (var i = 0; i <10; i++) {
            arr[i] = (function(num){
                return function(){
                    return num;
                }
            })(i);  //此时 有大量的作用域
        }
    return arr;
   }

   var fs = fn1();
   for (var i = 0; i < fs.length; i++) {
       fs[i]();    //此时就是 0,1,2,3,4,5,6,7,8,9  ,
   };

消耗大量的内存,

闭包的this问题:

在闭包中使用 this 对象也可能会导致一些问题,this 对象是在运行时基于函数的执行环境绑定的:
在全局函数中, this 等于 window,当函数作为某个对象的方法调用时, this 等于那个对象。不过,匿名函数的执行环境具有全局性,因此其 this 对象通常指向 window。

var name ="A";
   var object = {
        name: "obj",
        say: function(){
            return function(){
                return this.name;
            }
        }
   }
   object.say()();  //此时,输出的是A,
当调用object.say()后,object的函数 内存空间已经释放,但是里面的闭包 依然存在,此时这个this就是指向window

解决方法:

 var name ="A";
   var object = {
        name: "obj",
        say: function(){
            //此时 that 就指向了 object 
            var that = this;
            return function(){
                return that.name;
            }
        }
   }
 object.say()();  //此时,输出的是obj,

内存的泄露:

如果闭包的作用域链中保存着一个html 元素,那么就意味着该元素将无法被销毁。

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

创建了一个作为 element 元素事件处理程序的闭包,匿名函数保存了一个对 assignHandler()的活动对象的引用,只要匿名函数存在, element 的引用数至少也是 1,因此它所
占用的内存就永远不会被回收。可以通过稍微改写一下代码来解决,

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

把 element.id 的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。仅仅做到这一步,还是不能解决内存泄漏。闭包会引用包含函数
的整个活动对象,而其中包含着 element。即使闭包不直接引用 element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把 element 变量设置为 null。

块级作用域:

javascript 没有块级作用域的概念,不管是使用循环还是判断之后,这个变量会一直存在,所以当全局中使用了 循环 或者判断后,这个变量可能会影响到函数的变量,所以除非特殊情况,不要使用全局变量,且 全局变量在作用域链最顶部,访问最慢,

for (var i = 0; i < 10; i++) {
      
  };
  alert(i);   //此时i就是 10;

解决的方式是匿名函数,用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示。

 (function(){
    //这里是块级作用域
    })();

定义并立即调用了一个匿名函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可
以使用自己的变量,又不必担心搞乱全局作用域。
这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。

私有变量:

私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。

function add(num1, num2){
    var sum = num1 + num2;
    return sum;
}这个函数内部,有 3 个私有变量: num1、 num2 和 sum。在函数内部可以访问这几个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访
问这些变量。

有权访问私有变量和私有函数的公有方法称为特权方法(privileged method)。

function Person(name){
    //特权方法
    this.setName = function(value){
        name = value;
    }
    //特权方法
    this.getName = function(){
        return name;
    }
 }
 var p = new Person("Linda");   p.setName("Joke");   p.getName(); //Joke

两个特权方法: getName()和 setName()。这两个方法都可以在构造函数外部使用,而且都有权访问私有变量 name。但在 Person 构造函数外部,没有任何办法访问 name。
它们作为闭包能够通过作用域链访问 name。私有变量 name在 Person 的每一个实例中都不相同,因为每次调用构造函数都会重新创建这两个方法。
构造函数模式的缺点是针对每个实例都会创建同样一组新方法,而使用静态私有变量来实现特权方法就可以避免这个问题。

静态私有变量:

多查找作用域链中的一个层次,就会在一定程度上影响查找速度。而这正是使用闭包和私有变量的一个显明的不足之处

模块模式

模块模式通过为单例添加私有变量和特权方法能够使其得到增强,

var singleton = (function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }

    //特权/公有 方法和属性
    return {
        publicProperty: true,
        publicMethod : function(){
                privateVariable++;
                return privateFunction();
        }
    }
 })();
这个模块模式使用了一个返回对象的匿名函数。匿名函数内部,定义了私有变量和函数。返回的对象 字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法 有权 访问私有变量和函数。这个
对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的:
 var application = (function(){
    //私有变量和函数
    var components = new Array();
    components.push(new BaseComponent());
    //公有
    return{
        getComponent:function(){
            return components.length;
        },
        registerComponent:function(component){
            if(typeof component == "object")
            {
                components.push(component);
            }
        }
    }
 })();

如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是 Object 的实例,

增强的模块模式:

这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。

var singleton = (function(){
    //私有变量和私有函数
    var privateVariable = 10;
    function privateFunction(){
        return false;
    }
    //创建对象
    var object = new CustomType();
    //添加特权/公有属性和方法
    object.publicProperty = true;
    object.publicMethod = function(){
        privateVariable++;
        return privateFunction();
    };
    //返回这个对象
    return object;
})();

总结:

函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
递归函数应该始终使用 arguments.callee 来递归地调用自身,不要使用函数名——函数名可能会发生变化。


当在函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理:

闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域。

函数的作用域及其所有变量都会在函数执行结束后被销毁。

当函数返回了一个闭包时,这个函数的作用域将会一直在内存中保存到闭包不存在为止。使用匿名函数可以在 JavaScript 中模仿块级作用域(JavaScript 本身没有块级作用域的概念),要点如下。

? 创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。
? 结果就是函数内部的所有变量都会被立即销毁—

闭包还可以用于在对象中创建私有变量,相关概念和要点如下。
? 即使 JavaScript 中没有正式的私有对象属性的概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
? 有权访问私有变量的公有方法叫做特权方法。
? 可以使用构造函数模式、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。

JavaScript 中的函数表达式和闭包都是极其有用的特性,利用它们可以实现很多功能。因为创建闭包必须维护额外的作用域,所以过度使用它们可能会占用大量内存。

 

 

 

 

 


 

以上是关于JS 中闭包的变量 闭包与this的主要内容,如果未能解决你的问题,请参考以下文章

JS基础 作用域与闭包

JavaScript——闭包与this对象以及window对象

1--面试总结-js深入理解,对象,原型链,构造函数,执行上下文堆栈,执行上下文,变量对象,活动对象,作用域链,闭包,This

js内存空间 执行上下文 变量对象详解 作用域链与闭包 全方位解读this

js 闭包(面试题)

JS闭包总结