zoukankan      html  css  js  c++  java
  • 14.匿名函数和闭包

    匿名函数和闭包

    学习要点:
    1.匿名函数
    2.闭包


    匿名函数就是没有名字的函数,闭包是可以访问一个函数作用域里变量的函数。声明:本节内容需要有面向对象和
    少量设计模式基础。

    一、匿名函数

    //普通函数
    function box(){ //函数名是box
    return 'Lee';
    }

    //匿名函数
    function(){ //匿名函数,会报错,单独的匿名函数是无法运行的
    return 'Lee';
    }

    //通过表达式自我执行
    (function box(){ //封装成表达式
    alert('Lee');
    })(); //( )表示执行函数,并且传参。第一个圆括号放匿名函数,第二个圆括号执行

    //把匿名函数赋值给变量
    var box = function(){ //将匿名函数赋给变量
    return 'Lee';
    };
    alert(box()); //调用方式和函数调用相似

    //把匿名函数自我执行的返回值赋给变量
    var box =(function(){
    return 'Lee';
    })();
    alert(box); //其中box后面不用加( )

    //自我执行匿名函数的传参
    (function(age){
    alert(age);
    })(100);

    //函数里的匿名函数
    function box(){ //函数里的匿名函数,产生闭包
    return function(){
    return 'journey';
    }
    }
    alert(box()()); //调用匿名函数,如果没有( )打印出来的就是函数的整个内容,而不是执行

    二、闭包

    闭包是指有权访问另一个函数作用域中的变量的函数,创建闭包的常见方式,就是在一个函数内部创建另一个函数
    ,通过另一个函数访问这个函数的局部变量。

    //通过闭包可以返回局部变量
    function box(){
    var user = 'Lee';
    return function(){ //通过匿名函数返回box()局部变量
    return user;
    };
    }
    alert(box()()); //通过box()()来直接调用匿名函数返回值

    var b = box();
    alert(b()); //另一种调用匿名函数返回值

    使用闭包有一个优点,也是它的缺点:就是可以把局部变量驻留在内存中,可以避免使用全局变量。(全局变
    量污染导致应用程序不可预测性,每个模块都可调用必将引来灾难,所以推荐使用私有的,封装的局部变量)。

    //通过全局变量来累加
    var age = 100; //全局变量
    function box(){
    age++; //模块级可以调用全局变量,进行累加
    }
    box(); //执行函数,累加了
    alert(age); //输出全局变量

    //通过局部变量无法实现累加
    function box(){
    var age = 100;
    age ++; //累加
    return age;
    }

    alert(box()); //101
    alert(box()); //101,无法实现,因为又被初始化了

    //使用匿名函数实现局部变量驻留内存中,从而累加
    function box(){
    var age = 100;
    return function (){
    age++;
    return age;
    };
    }
    var b = box;
    alert(b());
    alert(b());
    alert(b());
    alert(b());
    b = null ; //解除引用 等待垃圾回收机制回收

    PS:由于闭包里作用域返回的局部变量资源不会被立刻销毁回收,所以可能会占用更多的内存。过度使用闭包会
    导致性能下降,建议在非常有必要的时候才使用闭包。

    作用域链的机制导致一个问题,在循环中里的匿名函数取得的任何变量都是最后一个值。

    //函数里包含匿名函数
    function box(){
    var arr=[];
    for ( var i = 0 ; i<10 ; i++){
    arr[i] = function(){
    return i;
    };
    }
    //循环已经执行完毕了,最用因为i<4但是还要++,所以最终的i是5
    return arr;
    }

    var b = box(); //得到函数数组,此时赋值给b的其实就是上面函数执行完毕得到的5
    alert(b.length); //得到函数集合长度
    for(var i = 0 ; i<b.length;i++){
    alert(b[i]()); //输出每个函数的值,都是最后一个值5
    }

    上面的例子输出的结果都是5,也就是循环后得到的最大的i值。因为b[i]调用的是匿名函数,匿名函数并没有自我
    执行,等到调用的时候,box()已执行完毕,i早已变成5,所以最终的结果就是5个5。

    //改()
    function box(){
    var arr = [];
    for(var i = 0 ; i<5 ; i++){
    arr[i]=i;
    }
    }
    var b = box();
    for (var i = 0; i<b.length ; i++){
    alert(b[i]);
    }

    //改1
    function box(){
    var arr = [];
    for(var i = 0; i<5;i++){
    arr[i]=(function(num){ //通过自我及时执行匿名函数
    return num;
    })(i);
    }
    return arr;
    }
    var b = box();
    for (var i = 0; i<b.length ; i++){
    alert(b[i]);
    }

    //改2
    function box(){
    var arr = [];
    for(var i = 0; i<5;i++){
    arr[i]=(function(num){
    //num其实在这里
    return function(){ //因为闭包可以将变量驻留在内存中,和上一节课的累加是一个道理
    return num;
    }
    })(i);
    }

    //已经执行完毕了,num为什么可以0,1,2,3,4
    return arr;
    }
    var b = box();
    for (var i = 0; i<b.length ; i++){
    alert(b[i]());
    }


    function box(){
    var arr = [];
    for(var i = 0; i<5;i++){
    arr[i]=function(num){
    //num其实在这里
    return function(){ //因为闭包可以将变量驻留在内存中,和上一节课的累加是一个道理
    return num;
    }
    }(i);
    }

    //已经执行完毕了,num为什么可以0,1,2,3,4
    return arr;
    }
    var b = box();
    for (var i = 0; i<b.length ; i++){
    alert(b[i]());
    }

    我们通过匿名函数自我执行,立即把结果赋值给a[i]。每一个i,是调用方通过按值传递的,所以最终返回的都是指
    定的递增的i。而不是box()函数里的i。


    关于this对象
    在闭包中使用this对象也可能会导致一些问题,this对象是在运行时基于函数的执行环境绑定的,如果this在全局
    范围就是window,如果在对象内部就是指向这个对象。而闭包却在运行时指向window的,因为闭包并不属于这
    个对象的属性或方法。

    var user = 'The Window',
    var obj = {
    user : 'The Object',
    getUserFunction : function(){
    return function(){ //闭包不属于obj,里面的this是指向window的
    return this.user;
    };
    }
    };
    alert(box.getUserFunction()());

    //对象冒充
    alert(box.getUserFunction().call(box));

    //我们也可以这样做
    var user = 'The Window',
    var obj = {
    user : 'The Object',
    getUserFunction : function(){
    //这里作用域的this是Box
    return function(){
    //这里作用域的this是window
    return this.user;
    };
    }
    };
    //我们可以将Box中的this赋值给一个变量var that = this;然后返回return that.user;
    var user = 'The Window',
    var obj = {
    user : 'The Object',
    getUserFunction : function(){
    //这里作用域的this是Box
    var that = this;
    return function(){
    //这里作用域的this是window
    return that.user;
    };
    }
    };
    //这样的话这个闭包中的this就是The Object

    //关于this对象
    var box = {
    getThis : function(){
    return this;
    }
    };
    alert(this); //object Window
    alert(box.getThis()); //object Object

    var box = {
    getThis : function(){
    return function(){
    return this;
    }
    }
    };
    alert(box.getThis()()); //object Window

    内存泄漏
    由于IE的JScript对象和DOM对象使用不同的垃圾收集方式,因此闭包在IE中会导致一些问题。就是内存泄漏的
    问题,也就是无法销毁驻留在内存中的元素。以下代码有两个知识点还没有学习到,一个是DOM,一个是事件。

    function box(){
    var oDiv = document.getElementById('oDiv'); //oDiv用完之后一直驻留在内存中
    oDiv.onclick=function(){
    alert(oDiv.innerHTML); //这里的oDiv导致了内存泄漏
    };
    }
    box();

    那么在最后应该将oDiv解除引用来避免内存泄漏。
    function box(){
    var oDiv=document.getElementById('oDiv');
    var text = oDiv.innerHTML;
    oDiv.onclick = function(){
    alert(text);
    };
    oDiv = null; //解除引用,等待回收
    }

    PS:如果并没有使用解除引用,那么需要等到浏览器关闭才能得以释放

    私有化
    模仿块级作用域(私有作用域)
    Javascript没有块级作用域的概念

    function box(count){
    for(var i = 0 ; i <count ; i++){};
    alert(i);
    }
    box(2);

    function box(count){
    for(var i = 0 ; i<count ; i++){}
    var i; //就算重新声明,也不会影响之前声明初始化的i值
    alert(i);
    }
    box(2);

    以上两个例子,说明Javascript没有块级语句的作用域,if( ){ } for( ){ }等没有作用域。如果有,出了这个范围:
    就应该被销毁了。就算重新声明同一个变量也不会改变它的值。

    Javascript不会提醒你是否多次声明了同一个变量:遇到这种情况,它只会对后续的声明视而不见(如果初始化
    了,当然还会执行的)。使用模仿块级作用域可避免这个问题。

    //模仿块级作用域(私有作用域)
    (function(){
    //这里是块级作用域
    })();

    //使用块级作用域(私有作用域)改写
    function box(count){
    (function(){
    for(var i = 0 ; i<count ; i++){}
    })();
    alert(i); //报错,无法访问
    }

    使用了块级作用域(私有作用域)后,匿名函数中定义的任何变量,都会在执行结束时被销毁,这种技术经常在
    全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽可能
    少向全局作用域中添加变量和函数。在大型项目中,多人开发的时候,过多的全局变量和函数很容易导致命名冲
    突,引起灾难性的后果。如果采用块级作用域(私有作用域),每个开发者既可以使用自己的变量,又不会担心
    搞乱全局作用域。

    (function(){
    var box = [1,2,3,4];
    alert(box); //box出来就不认识了
    })();

    //全局变量

    var age = 100;
    alert(age);

    //全局变量用完之后必须使用null清空等待回收,而使用私有作用域不需要,因为会自动销毁,从而很好的限制
    变量的使用

    //私有作用域来表示

    (function(){
    var age = 100;
    alert(age);
    })();

    在全局作用域中使用块级作用域可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行
    完毕,就可以立即销毁其作用域链了。

    私有变量
    Javascript没有私有属性的概念;所有的对象属性都是公有的。不过,却有一个私有变量的概念。任何在函数中
    定义的变量,都可以认为是私有变量,因为不能在函数的外部访问这些变量。
    function box(){
    var age = 100; //私有变量,外部无法访问
    }

    而通过函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。而利用这一点,可以创建用
    于访问私有变量的公有方法。

    function Box(){
    this.age = 100; //属性,公有的
    this.run = function(){ //方法,公有的
    return '运行中...';
    };
    }
    var box = new Box();
    alert(box.age);

    //私有就不能用this了
    function Box(){
    var age = 100; //私有变量
    function run(){ //私有函数
    return '运行中...';
    }
    this.get = function(){ //对外公共的特权方法
    return age+run();
    };
    }
    var box = new Box();
    alert(box.get());

    还可以通过构造方法传参来访问私有变量。
    function Person(value){
    var user = value;
    this.getUser = function(){
    return user;
    };
    this.setUser = function(value){
    user = value;
    };
    }
    var person= new Person('journey');
    alert(person.getUser());

    但是对象的方法,在多次调用的时候,会多次创建,可以使用静态私有变量来避免这个问题。

    静态私有变量

    通过块级作用域(私有作用域)中定义私有变量和函数,同时可以创建对外公共的特权方法。

    (function(){
    var user = '';
    Person = function(value){
    user = value;
    };
    Person.prototype.getUser=function(){
    return user;
    };
    Person.prototype.setUser=function(){
    user = value;
    }
    })();
    var person =new Person('journey');
    alert(person.getUser());

    使用了prototype导致方法共享了,而user也改变了静态属性了。(所谓静态属性,即共享于不同对象中的属性
    )。

    模块模式
    之前采用的都是构造函数的方式来创建私有变量和特权方法。那么对象字面量方式就采用模块模式来创建。
    什么叫单例:就是永远只实例化一次,其实就是字面量对象声明方式
    var box = { //字面量对象,也是单例对象,无法第二次实例化
    age : 100; //这是公有属性,将要改成私有
    run : function(){ //这是公有函数,将要改成私有
    return '运行中...';
    };
    };

    私有化变量和函数:
    var box = function(){
    var age = 100;
    function fun(){
    return '运行中...';
    }
    return{ //直接返回对象
    go : function(){
    return age+fun();
    }
    };
    }();

    上面的直接返回对象的例子,也可以这么写:
    var box = function(){
    var age = 100;
    function run(){
    return '运行中...';
    }
    var obj = {
    go : function(){
    return age + run();
    }
    };
    return obj;
    }();

    字面量的对象声明,其实在设计模式中可以看作是一种单例模式,所谓单例模式,就是永远保存对象的一个实例
    增强的模块模式,这种模式适合返回自定义对象,也就是构造函数。
    function Desk(){};
    var box = function(){
    var age = 100;
    function run(){
    return '运行中...';
    }
    var desk = new Desk(); //可以实例化特定的对象
    desk.go = function(){
    return age + run();
    };
    return desk;
    }();
    alert(box.go());

  • 相关阅读:
    20200209 ZooKeeper 3. Zookeeper内部原理
    20200209 ZooKeeper 2. Zookeeper本地模式安装
    20200209 Zookeeper 1. Zookeeper入门
    20200206 尚硅谷Docker【归档】
    20200206 Docker 8. 本地镜像发布到阿里云
    20200206 Docker 7. Docker常用安装
    20200206 Docker 6. DockerFile解析
    20200206 Docker 5. Docker容器数据卷
    20200206 Docker 4. Docker 镜像
    Combining STDP and Reward-Modulated STDP in Deep Convolutional Spiking Neural Networks for Digit Recognition
  • 原文地址:https://www.cnblogs.com/journey-IT/p/5260261.html
Copyright © 2011-2022 走看看