zoukankan      html  css  js  c++  java
  • 深入理解递归和闭包

    函数表达式的几种不同的语法形式

    var functionName = function(arg0, arg1, arg2){ 
        //函数体
    };

    这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量 functionName。这种情况下创建的函数叫做匿名函数(拉姆达函数),因为 function关键字后面没有标识符,

    eg1 
    函数表达式与其他表达式一样,在使用前必须先赋值。以下代码会导致错误。
    sayHi(); //错误:函数还不存在 
    var sayHi = function(){
        alert("Hi!");
    };
    eg2 * :
    if(true){
        function sayHi(){
            alert("Hi!");
      }
    } else {
        function sayHi(){
            alert("Yo!");
      } 
    }

    表面上看,以上代码表示在为 true 时,使用一个 sayHi()的定义;否则,就使用另 一个定义,实际上,浏览器尝试修正错误的做法并不一致。如果是使用函数表达式,那就没有什么问题了。

    var sayHi;
    if(true){
        sayHi = function(){
            alert("Hi!");
        };
    } else {
        sayHi = function(){
            alert("Yo!");
        };
    }

    递归函数

    递归函数是在一个函数通过名字调用自身的情况下构成的 

    function factorial(num){
       if (num <= 1){
          return 1;
       } else { 
          return num * factorial(num-1);
       }
    }
    factorial(4); //24

    下面是一个经典的递归阶乘函数,先把 factorial()函数保存在变量 anotherFactorial 中,然后将 factorial 变量设 置为 null,factorial 已经不再是函数,就会导致错误,

    function factorial(num){
       if (num <= 1){
           return 1;
       } else { 
           return num * factorial(num-1);
       }
    }
    var anotherFactorial = factorial; 
    factorial = null;
    alert(anotherFactorial(4));
    为了防止调用途中factorial被改变,下面这个这个方式可以解决这个问题
    eg:
    function factorial(num){
        if (num <= 1){
            return 1;
        } else {
            return num * arguments.callee(num-1);
        } 
    }
    factorial(4); //24
    eg2
    function factorial(num){
        if (num <= 1){
            return 1;
        } else {
            return num * arguments.callee(num-1);
        } 
    }
    var anotherFactorial = factorial; 
    factorial = null;
    alert(anotherFactorial(4));

    因此,在编写递归函数时,使用 arguments.callee 总比使用函数名更保险。 但在严格模式下,不能通过脚本访问 arguments.callee,访问这个属性会导致错误

    eg
    'use strict'; 
    function factorial(num){
        if (num <= 1){
            return 1;
        } else {
            return num * arguments.callee(num-1);
        } 
    }
    var anotherFactorial = factorial; 
    factorial = null;
    alert(anotherFactorial(4));//出错

    可以使用命名函数表达式来达成相同的结果

    eg
    var factorial = (function f(num){
        if (num <= 1){
            return 1;
        }else {
            return num * f(num-1);
        } 
    });

    这种方式在严格模式和 非严格模式下都行得通。

     

    闭包

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

    白话解释:闭包是一种特殊的对象。 它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。 当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。

    对于那些有一点 JavaScript 使用经验但从未真正理解闭包概念的人来说,理解闭包可以看作是某种意义上的重生,突破闭包的瓶颈可以使你功力大增。

    eg1:
    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。即使这个内部函数被返回了,而且是在其他地方被调用了,但它仍然可 以访问变量 propertyName。之所以还能够访问这个变量,是因为内部函数的作用域链中包含 createComparisonFunction()的作用域 ,而有关如何创建作用域链以及作用域链有什么作用的细节,对彻底 理解闭包至关重要 

    function foo() {
        var a = 20;
        var b = 30;
    
        function bar() {
            return a + b;
        }
    
        return bar;
    }
    
    var bar = foo();
    bar();

    上面的例子,首先有执行上下文foo,在foo中定义了函数bar,而通过对外返回bar的方式让bar得以执行。当bar执行时,访问了foo内部的变量a,b。因此这个时候闭包产生。

    闭包与变量

    for (var i=1; i<=5; i++) { 
        setTimeout( function timer() {
            console.log(i);
        }, i*1000 );
    }
    
    输出的值
    6
    6
    6
    6
    6

    我们直接这样写,根据setTimeout定义的操作在函数调用栈清空之后才会执行的特点,for循环里定义了5个setTimeout操作。而当这些操作开始执行时,for循环的i值,已经先一步变成了6。因此输出结果总为6

    利用闭包,修改下面的代码,让循环输出的结果依次为1, 2, 3, 4, 5

    for (var i=1; i<=5; i++) { 
        (function(i){
            setTimeout( function timer() {
                console.log(i);
            }, i*1000 );
        })(i)
    }

    借助闭包的特性,每次循环时,将i值保存在一个闭包中,当setTimeout中定义的操作执行时,则访问对应闭包保存的i值即可。而我们知道在函数中闭包判定的准则,即执行时是否在内部定义的函数中访问了上层作用域的变量。因此我们需要包裹一层自执行函数为闭包的形成提供条件。 因此,我们只需要2个操作就可以完成题目需求,一是使用自执行函数提供闭包条件,二是传入i值并保存在闭包中。

    闭包中的this对象

    匿名函数的执行环境具有全局性,因此其this对象通常指向window。但有时候由于编写闭包的方式不同,这一点可能不会那么明显 

    var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
    } };
    alert(object.getNameFunc()()); //"The Window"(在非严格模式下)

    由于 getNameFunc() 返回一个函数,因此调用 object.getNameFunc()()就会立即调用它返回的函数,结果就是返回一个字符串,为什么匿名函数没有取得其包含作用域(或外部作用域)的 this 对象呢?

    每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量

    内存泄漏

    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 变量设置为 null。这样就能够解除对 DOM 对象的引 用,顺利地减少其引用数,确保正常回收其占用的内存。

  • 相关阅读:
    Luogu 1080 【NOIP2012】国王游戏 (贪心,高精度)
    Luogu 1314 【NOIP2011】聪明的质检员 (二分)
    Luogu 1315 【NOIP2011】观光公交 (贪心)
    Luogu 1312 【NOIP2011】玛雅游戏 (搜索)
    Luogu 1525 【NOIP2010】关押罪犯 (贪心,并查集)
    Luogu 1514 引水入城 (搜索,动态规划)
    UVA 1394 And Then There Was One / Gym 101415A And Then There Was One / UVAlive 3882 And Then There Was One / POJ 3517 And Then There Was One / Aizu 1275 And Then There Was One (动态规划,思维题)
    Luogu 1437 [HNOI2004]敲砖块 (动态规划)
    Luogu 1941 【NOIP2014】飞扬的小鸟 (动态规划)
    HDU 1176 免费馅饼 (动态规划)
  • 原文地址:https://www.cnblogs.com/wzndkj/p/7804221.html
Copyright © 2011-2022 走看看