美文网首页让前端飞
JS函数的递归和闭包的注意要点

JS函数的递归和闭包的注意要点

作者: 丨程序之道丨 | 来源:发表于2019-05-10 15:16 被阅读2次

    JavaScript函数表达式——“函数的递归和闭包”的注意要点

    函数表达式的基本概念

    name属性和函数提升

    首先,name属性,通过这个属性可以访问到给函数指定的名字。(非标准的属性)如:

    function  People(){};
    
    console.log(People.name);  //People
    
    

    其次,函数声明提升,意味着可以把函数声明放在调用它的语句后面。如:

    
    sayHi();  //调用函数
    
    function  sayHi(){  //声明函数
    
        console.log("Hi");
    
    }  //不会报错
    
    

    使用函数表达式则不可以:

    sayHi();
    
    var  sayHi  =  function(){
    
        console.log("Hi");
    
    }  //报错
    
    

    创建函数的两种方式,一个是函数声明(如第一种方式);一个是函数表达式(如第二种方式)。第二种函数创建方式创建的函数叫“匿名函数”或“拉姆达函数”,因为function 关键字后面没有标识符。

    函数提升的常见错误

    需要注意的是,作为对比,下面的两种代码中,第一个是错误的(会导致各浏览器出现不同的问题);第二个才使正确的。代码如下:

    var  condition  =  true;
    
    if  (condition){
    
        function  sayHI(){
    
            console.log("hi")
    
        }                                 //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
        sayHI();  //"hello"
    
    }else{
    
        function  sayHI(){
    
            console.log("hello")
    
        }
    
        sayHI();
    
    }
    
    

    报错

    var  condition  =  false;
    
    var  sayHi;
    
    if(condition){
    
        sayHi  =  function(){
    
            console.log("hi")
    
        };                        //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
        sayHi();
    
    }else{
    
        sayHi  =  function(){
    
            console.log("hello")
    
        };
    
        sayHi();  //hello
    
    }
    
    

    没有错误

    var  condition  =  true;
    
    if(condition){
    
        var  sayHi  =  function(){
    
            console.log("hi")
    
        };
    
        sayHi();  //hi
    
    }else{
    
        var  sayHi  =  function(){
    
            console.log("hello")
    
        };
    
        sayHi();  //hello
    
    }
    
    

    这里也不会出现问题。出现上面问题的根源就是函数提升,就是函数声明和函数表达式之间的区别所导致的。

    给大家推荐一个技术交流学习圈,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。获取资料
    对web开发技术感兴趣的同学,可以加入交流圈👉👉👉1007317281,不管你是小白还是大牛都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。

    函数的递归

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

    function  factorial(num){
    
        if(num  <=  1){
    
            return  1;
    
        }else{
    
            return  num  *  factorial(num  -  1);
    
        }             //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
    }    
    
    console.log(factorial(4));  //24 4*3*2*1
    
    

    但是,函数里面包含了函数自身所以,在应该使用arguments.callee来解决该问题。如:

    function  factorial(num){
    
        if(num  <=  1){
    
            return  1;
    
        }else{
    
            return  num  *  arguments.callee(num  -  1);
    
        }
    
    }    
    
    console.log(factorial(4));  //24 4*3*2*1
    
    

    但如果使用上面这种方法,则在严格模式下行不通。不过可以使用命名函数表达式来达成相同的结果。如:

    
    var  factorial  =  (
    
        function  f(num){
    
            if(num  <=  1){
    
                return  1;
    
            }else{
    
                return  num  *  f(num  -  1);
    
            }
    
        }
    
    );    
    
    console.log(factorial(4));  //24 4*3*2*1
    
    

    即,把它包含在一个变量里面,在遇到其他这种需要使用arguments.callee的时候都可以这样做。

    函数的闭包

    闭包就是有权访问另一个函数作用域中的变量的函数。常见的创建闭包的方式就是在一个函数内部创建另一个函数。在此之前应该先掌握作用域链的概念(见《Javascript执行环境和作用域的注意要点》一文)

    作用域链

    以下面的代码为例

    function  compare(value1,value2){
    
        if  (value1  >  value2){
    
            return  1;
    
        }else  if  (value1  <  value2){
    
            return  -1;
    
        }else  {
    
            return  0;
    
        }
    
    }             //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
    var  result  =  compare(5,10);
    
    

    调用函数compare时,函数执行环境中的作用域链:


    作用域链本质上是一个指向变量对象的指针列表。

    另一个例子:

    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;
    
            }
    
        };
    
    }
    
    var  compare  =  createComparisonFunction("name");
    
    var  result  =  compare({name:  "Nicholas"},{name:  "Greg"});
    
    

    这个就相当于:

    var  compare  =  function(object1,object2){
    
        var  value1  =  object1["name"];
    
        var  value2  =  object2["name"];
    
        if  (value1  <  value2){
    
            return  -1;
    
        }else  if(value1  >  value2){
    
            return  1;
    
        }else{
    
            return  0;
    
        }                  //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
    };
    
    var  result  =  compare({name:  "Nicholas"},{name:  "Greg"});
    
    

    相当于:

    var  result  =  function(){
    
        var  value1  =  {name:  "Nicholas"}["name"];
    
        var  value2  =  {name:  "Greg"}["name"];
    
        if  (value1  <  value2){
    
            return  -1;
    
        }else  if(value1  >  value2){
    
            return  1;
    
        }else{
    
            return  0;
    
        }
    
    };
    
    console.log(result());  //1
    
    

    所以,完整的代码如下:

    var  compareNames  =  createComparisonFunction("name");
    
    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;
    
            }                 //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
        };
    
    }
    
    var  result  =  compareNames({name:  "Nicholas"},{name:  "Greg"});
    
    compareNames  =  null;
    
    

    调用compareNames()函数的过程中产生的作用域链之间的关系图如下:

    常见的闭包的模式一般都是这样:

    
    var  X  =  function  A(a){
    
        return  function(b1,b2){...a...}  //匿名函数
    
    };    
    
    var  Y  =  X(b1,b2);
    
    

    举个栗子:

    
    var  obj1  =  {
    
        name:  "co",
    
        color:  ["white","black"]
    
    };
    
    var  obj2  =  {
    
        name:  "lor",
    
        color:  ["yellow","red"]
    
    };
    
    function  displayProperty(propertyName){
    
        return  function(obj1,obj2){
    
            var  value1  =  obj1[propertyName];
    
            var  value2  =  obj2[propertyName];
    
            if(typeof  value1  ===  "string"){
    
                return  value1  +  " and "  +  value2  +  "<br/>";
    
            }else  if(value1 instanceof  Array){
    
                return  value1.toString()  +  "<br/>"  +  value2.toString();
    
            }else{
    
                return  false;
    
            }                //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
        };
    
    }
    
    var  displayP  =  displayProperty("name");
    
    var  displayStr  =  displayP(obj1,obj2);
    
    document.write(displayStr);
    
    displayP  =  null;
    
    var  displayP  =  displayProperty("color");
    
    var  displayStr  =  displayP(obj1,obj2);
    
    document.write(displayStr);    
    
    /*
    
    co and lor
    
    white,black
    
    yellow,red
    
    */
    
    

    闭包会携带他包含的函数的作用域,因此会更多的占用内存资源,建议只有在绝对必要的时候再考虑使用闭包。V8 优化后的js 引擎会尝试收回被闭包占用的内存。

    闭包与变量

    闭包的副作用是闭包只能取得包含函数中任何变量的最后一个值。

    this对象

    全局函数中this = window;当函作为位某个对象的方法调用时this = 那个对象;但是匿名函数的执行环境又全局性,this 通常指向window;但也有例外:

    var  name  =  "the window";
    
    var  obj  =  {
    
        name:  "the obj",
    
        getNameFunc:  function(){
    
            return  function(){
    
                return  this.name;
    
            };
    
        }                 //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
    };
    
    console.log(obj.getNameFunc()());  //"the window" 别忘了要写两个小括号
    
    

    因为每个函数在被调用的时候都会自动取得两个特殊的变量:this 和arguments;内部函数在搜索这两个变量时,只会搜索到其活动对象为止。

    var  obj  =  {
    
        name:  "Oliver",
    
        age:  18,
    
        friends:  ["alice","troy"],
    
        sayName:  function(){
    
            return  this.name;
    
        },
    
        sayFriends:  function(){
    
            return  function(){
    
                return  this.friends;
    
            };
    
        }
    
    };
    
    console.log(obj.sayFriends()());  //undefined
    
    

    上面这个代码就是因为闭包的问题,导致错误。又如:

    var  friends  =  "window's friends";
    
    var  obj  =  {
    
        name:  "Oliver",
    
        age:  18,
    
        friends:  ["alice","troy"],
    
        sayName:  function(){
    
            return  this.name;
    
        },
    
        sayFriends:  function(){
    
            return  function(){
    
                return  this.friends;
    
            };
    
        }                    //欢迎加入前端全栈开发交流圈一起学习交流:1007317281
    
    };
    
    console.log(obj.sayFriends()());  //window's friends 匿名函数执行环境的全局性
    
    

    解决这个问题的方法是:

    var  friends  =  "window's friends";
    
    var  obj  =  {
    
        name:  "Oliver",
    
        age:  18,
    
        friends:  ["alice","troy"],
    
        sayName:  function(){
    
            return  this.name;
    
        },
    
        sayFriends:  function(){
    
            var  outer  =  this;
    
            return  function(){
    
                return  outer.friends;
    
            };
    
        }
    
    };
    
    console.log(obj.sayFriends()());  //["alice","troy"] 这样就可以正常搜索到了
    
    

    又如:

    var  friends  =  "window's friends";
    
    var  obj  =  {
    
        name:  "Oliver",
    
        age:  18,
    
        friends:  ["alice","troy"],
    
        sayWindowFriends:  function(){
    
            return  function(){
    
                return  this.friends;
    
            };
    
        },
    
        sayFriends:  function(){
    
            var  outer  =  this;
    
            return  function(){
    
                return  function(){
    
                    return  function(){
    
                        return  outer.friends
    
                    };
    
                };
    
            };
    
        }
    
    };
    
    console.log(obj.sayWindowFriends()());  //"window's friends"
    
    console.log(obj.sayFriends()()()());  //["alice", "troy"]
    
    

    再举个栗子:

    var  obj  =  {
    
        name:  "Oliver",
    
        age:  18,
    
        friends:  ["alice","troy"],
    
        sayFriends:  function(i){
    
            var  outer  =  this;
    
            return  function(j){
    
                return  outer.friends[i]  +  outer.friends[j];
    
            };
    
        }
    
    };
    
    console.log(obj.sayFriends(0)(1));  //alicetroy
    
    

    另外,在几种特殊情况下,this 的值可能会发生改变。如:

    var  name  =  "the window";
    
    var  obj  =  {
    
        name:  "my object",
    
        getName:  function(){
    
            return  this.name;
    
        }
    
    };
    
    console.log(obj.getName());  //my object
    
    console.log((obj.getName)());  //my object 与上面的是相同的,多了个括号而已
    
    console.log((obj.getName  =  obj.getName)());  //the window
    
    

    只要不用下面两种方式调用函数即可。

    内存泄露

    经过上面的一番折腾,最明显的就是window.name 一直占用内存,无法清空。必须手动把它清理掉,用window.name = null来操作。
    感谢您的观看,如有不足之处,欢迎批评指正。

    本次给大家推荐一个免费的学习群,里面概括移动应用网站开发,css,html,webpack,vue node angular以及面试资源等。获取资料👈👈👈
    对web开发技术感兴趣的同学,欢迎加入Q群:👉👉👉1007317281👈👈👈,不管你是小白还是大牛我都欢迎,还有大牛整理的一套高效率学习路线和教程与您免费分享,同时每天更新视频资料。
    最后,祝大家早日学有所成,拿到满意offer,快速升职加薪,走上人生巅峰。

    相关文章

      网友评论

        本文标题:JS函数的递归和闭包的注意要点

        本文链接:https://www.haomeiwen.com/subject/bldaoqtx.html