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 对象的引 用,顺利地减少其引用数,确保正常回收其占用的内存。

  • 相关阅读:
    生成器
    IO调度算法
    进程与线程
    磁盘阵列(RAID)实例
    KVM虚拟化技术(七)虚拟机配置文件
    查询快递单号-京东快递接口
    快递单号查询快递鸟API接口-优速快递
    查询快递单号-德邦快递
    快递单号查询快递鸟API接口-EMS
    快递单号查询快递鸟API接口--安能快递
  • 原文地址:https://www.cnblogs.com/wzndkj/p/7804221.html
Copyright © 2011-2022 走看看