递归、作用域详解及作用域面试题

递归和作用域

递归:指的就是函数内部直接或间接调用自己;

    function test(){
        test();
    }

    test();

这里值得一说的是递归主要有两个要素

  • 自己调用自己
  • 要有结束条件 (如果只是自己调用自己而没有结束条件,最后会内存泄露,程序崩溃掉);

递归主要用到的化归思想

  • 化归思想:将一个问题由难化易,由繁化简,把一个困难的问题拆分成很多步来做

下面来几个用到递归的例子

例如:

求前n项和(1 - n)

    前5项和: 1 + 2 + 3 + 4 + 54项和 + 54项和: 1 + 2 + 3 + 43项和 + 43项和: 1 + 2 + 32项和 + 32项和: 1 + 21项和 + 21项和: 1                      1

    fn(n) = fn(n - 1) + n
    结束条件:
    当n=1的时候直接返回1


        function sum(n){

            if(n == 1){

                return 1;
            }
            return sum(n - 1) + n;
        }

        console.log(sum(5));  //15

再例如:


求n的m次方:

求 n的 m次方

    求n的5次方: n * n * n * n * n
    求n的4次方: n * n * n * n 
    求n的3次方: n * n * n 
    求n的2次方: n * n 
    求n的1次方: n 

    由上面我们可以得出规律: fn(n,m) = fn(n,m-1)*n;

    所以申明函数如下:

    function pow (n,m){

        if(m == 1){
            return n
        }

        return pow(n,m-1) * n;
    }

在斐波那契数列上更实用


1 1 2 3 5 8 13 21 34 55

仔细观察它的规律,我们能得出:fn(n) = fn(n-1) + fn(n-2);

当n是1或者n是2的时候都可以直接return 1


function fib (n){

    if(n == 1 || n == 2){
        return 1;
    }
    return fib(n - 1) + fn( n - 2) 
}

递归实现通过id名获取元素原理

    // 获取一个元素的后代元素
    function getChildren (ele){
        var list = [];
        var children = ele.children;
        // 获取到每一个子元素
        for(var i = 0 ; i < children.length; i++ ){
            var child = children[i];
            list.push(child);   
            var temp =  getChildren(child); // 把每一次获取到的子元素保存起来;
           list = list.concat(temp);        // 存到list数组里面去;
        }

        return list;
    }
    //通过id名获取到这个元素
    function getElementById(id){
        var list = getChildren(document.body); //找到body下的子元素集合;
        for(var i = 0; i < list.length; i++){

            if(list[i].id == id){

                return list[i];
            }
        }

        return null;
    }

    通过这个两个函数我们就实现通过id名获取元素的原理了;
 ```

 ## 作用域 

 ### 作用域:顾名思义,变量起作用的范围,就是变量的作用域

 * 在js当中有且只有函数可以创建作用域;
 * 块级作用域 (通过代码块限定的作用域)(js中没有块级作用域)
 * 变量的作用域只和函数的声明位置有关系,和函数的具体调用没任何关系,这种作用域就是:词法作用域(静态作用域),而js中的作用域就是这种作用域;

```js

var num = 123;

function f1 (){

    console.log(num);
 }

 function f2 (){

     var num = 456;
     f1();
 }

 f2(); // 123 因为js是静态作用域





<div class="se-preview-section-delimiter"></div>

js作用域中的变量提升 (hoisting)

在js代码的与解析阶段,系统会将代码中所有的变量声明以及函数声明提升到其所在的作用域的最顶上!
        func();  // 123 
        function func(){
            var num = 123;
            console.log(123);
        }




        var a; 
        console.log(a);   // a函数代码块
         a = 1;
        function a (){
            console.log(123);
        }

        a();  // a is not a function (a已经被赋值为1了)





<div class="se-preview-section-delimiter"></div>

js作用域变量提升六种特别情况

1. 函数同名的情况:同名的函数都会被提升,但是后面的函数声明会将前面的函数声明给覆盖掉

    func(); // 第二个func

    function func(){
        console.log("第一个func");
    }

    function func(){
        console.log("第二个func");
    }




<div class="se-preview-section-delimiter"></div>
2. 变量和函数同名的情况:只提升函数,忽略掉变量声明

console.log(typeof a);

        console.log(typeof a); // function

        var a = 123;
        function a(){
            console.log("我是一个函数");
        }

        // 提升后的代码
        var a;
        function a(){
            console.log("我是一个函数");
        }
        console.log(a);
        a = 123;





<div class="se-preview-section-delimiter"></div>
3. 预解析是分作用域的

 //   console.log(f); // 会报错 f is not defined

    function func(){

        console.log(f);

        function f(){
            console.log("我是在局部作用域中声明的函数");
        }
    }
    func(); // f函数代码块





<div class="se-preview-section-delimiter"></div>
4. 预解析是分段的 “段”指的script标签

    func(); //  我是第一个script标签中的函数 (本script标签中有,则不会去后面的              script标签中)

    function func(){
        console.log("我是第一个script标签中的函数");
    }

    var b = 10; // 本script标签中没有b,则会去下个script标签中找





<div class="se-preview-section-delimiter"></div>
5.条件式函数声明(不推荐使用) 在条件判断语句中声明的函数,可以将其当做是一个函数表达式来处理,只提升函数名,函数体不会被提升!
    console.log(func);
    if(false){
        function func(){
        }
    }

    //相当于做下面的代码处理
    console.log(func);
    if(false){
        var func = function(){};
    }

    //  一般情况下 我们遇到一个函数名因为两种原因要实现两种功能时,可以做如下处理

    var isDog = true;
    var bark = null;
    if(isDog){
        bark = function (){
            console.log("汪汪汪");
        }
    }else{
        bark = function (){
            console.log("喵喵喵");
        }
    }
    bark();





<div class="se-preview-section-delimiter"></div>
6. 函数中存在形参和变量或者函数同名的时候的提升情况,函数中形参的声明和赋值过程优先于变量提升,并且不参与变量提升
    function test(a){  //参数a就相当于 var a = 100,但是不参与变量提升

        console.log(typeof a); // 因为参数a不参与变量提升 所以是function类型
        function a(){

        }
    }

    test(100);





<div class="se-preview-section-delimiter"></div>

最后又是我们的面试题环节了 @_@ ,下面的面试题基本都在上面六种情况内,我就只写答案,不做分析了哈~!

1.
    function foo() {
        var num = 123;
        console.log(num); //
    }
    foo();    //   123
    console.log(num); // 报错



2.

    var scope = "global";
    function foo() {
        console.log(scope);     // undefined
        var scope = "local";
        console.log(scope);     // "local"
    }

    foo();



 3.  

    if("a" in window){
         var a = 10;
    }
    alert(a); //    10



4. 

    if(!"a" in window){
         var a = 10;
    }
    alert(a); // ?   undefined


5. 

    var foo = 1;
    function bar() {

        if(!foo) {
            var foo = 10;
        }
        alert(foo);    // 10
    }
    bar();


6.
    var num = 123;

    function f1() {
        console.log(num);
    }

    function f2() {
        var num = 456;
        f1();
    }
    f2();  // 123 



7.

    var num = 123;
    function f1(num) { // 参数理解为:函数内部的一个变量
        console.log(num); 
    }


    function f2() {
        var num = 456;
        f1(num); 
    }

    f2();  // 456



8. 

    var num = 123


    function f1() {
        console.log(num);
    }

    f2();   // 456


    function f2() {
        num = 456;  // 提升为全局变量,num被修改为456
        f1();
    }

    console.log(num);   // 456



9.

    (function (a) {

        console.log(a);    // a函数代码块
        var a = 10;
        function a(){}
    }( 100 )); 




10.  这个因为涉及到原型和对象,可能会有点难,但也不是太难,我们一步一步分析就能解决它;

        function Foo() {
             getName = function(){ alert(1); };
            return this;
        }

        Foo.getName = function() { alert(2); };
        Foo.prototype.getName = function(){ alert(3); };

        var getName = function() { alert(4); };
        function getName(){ alert(5); }


        Foo.getName(); //  2 
        getName(); //    4

        //这个我们要分析一下了:由于变量提升,var getName变量会被函数getName覆盖, 但是后面getName又被赋值了一个函数 

        /*
             var getName = function() { alert(4); };
            function getName(){ alert(5); }

            上面代码在作用域解析的时候可以看成下面这样:
              var getName;
              function getName(){ alert(5); }
              getName = function() { alert(4); };
              所以最后还是 alert(4)的那个函数 这个时候答案就很明显了
        */

        Foo().getName(); // ?  1
        // Foo()函数执行的时候  getName = function(){ alert(1); }; getName函数就被赋值成alert(1)的函数了
        // Foo()的返回值是this,指的window, window.getName() = getName()  所以答案是1

        getName(); // ?  还是1

        new Foo.getName(); // 2  ? 不要管new出来什么对象,Foo.getName被alert(2)的函数赋值了 因为始终会执行,答案是2

        new Foo().getName(); // 3    ? new出来的对象没有.getName属性因此会顺着原型链往上级找,所以是3

        new new Foo().getName(); // 3   跟上面是一个道理 还是3

函数可以创建作用域,函数中又可以声明函数,这样就形成了作用域嵌套作用域的链式结构

  • 首先在当前使用这个变量的作用域中进行查找,如果有该变量(有声明),就直接使用当前作用域中的这个变量,但是如果没有
  • 就去上一级作用域中进行查找,如果有该变量(有声明),就直接使用当前作用域中的这个变量,但是如果没有
  • 就继续沿着作用域链向上查找,直到找到全局为止

## 最后又是我们的面试题环节了 @_@ ,下面的面试题基本都在上面六种情况内,我就只写答案,不做分析了哈~!

```js
1.
    function foo() {
        var num = 123;
        console.log(num); //
    }
    foo();    //   123
    console.log(num); // 报错



2.

    var scope = "global";
    function foo() {
        console.log(scope);     // undefined
        var scope = "local";
        console.log(scope);     // "local"
    }

    foo();



 3.  

    if("a" in window){
         var a = 10;
    }
    alert(a); //    10



4. 

    if(!"a" in window){
         var a = 10;
    }
    alert(a); // ?   undefined


5. 

    var foo = 1;
    function bar() {

        if(!foo) {
            var foo = 10;
        }
        alert(foo);    // 10
    }
    bar();


6.
    var num = 123;

    function f1() {
        console.log(num);
    }

    function f2() {
        var num = 456;
        f1();
    }
    f2();  // 123 



7.

    var num = 123;
    function f1(num) { // 参数理解为:函数内部的一个变量
        console.log(num); 
    }


    function f2() {
        var num = 456;
        f1(num); 
    }

    f2();  // 456



8. 

    var num = 123


    function f1() {
        console.log(num);
    }

    f2();   // 456


    function f2() {
        num = 456;  // 提升为全局变量,num被修改为456
        f1();
    }

    console.log(num);   // 456



9.

    (function (a) {

        console.log(a);    // a函数代码块
        var a = 10;
        function a(){}
    }( 100 )); 




10.  这个因为涉及到原型和对象,可能会有点难,但也不是太难,我们一步一步分析就能解决它;

        function Foo() {
             getName = function(){ alert(1); };
            return this;
        }

        Foo.getName = function() { alert(2); };
        Foo.prototype.getName = function(){ alert(3); };

        var getName = function() { alert(4); };
        function getName(){ alert(5); }


        Foo.getName(); //  2 
        getName(); //    4

        //这个我们要分析一下了:由于变量提升,var getName变量会被函数getName覆盖, 但是后面getName又被赋值了一个函数 

        /*
             var getName = function() { alert(4); };
            function getName(){ alert(5); }

            上面代码在作用域解析的时候可以看成下面这样:
              var getName;
              function getName(){ alert(5); }
              getName = function() { alert(4); };
              所以最后还是 alert(4)的那个函数 这个时候答案就很明显了
        */

        Foo().getName(); // ?  1
        // Foo()函数执行的时候  getName = function(){ alert(1); }; getName函数就被赋值成alert(1)的函数了
        // Foo()的返回值是this,指的window, window.getName() = getName()  所以答案是1

        getName(); // ?  还是1

        new Foo.getName(); // 2  ? 不要管new出来什么对象,Foo.getName被alert(2)的函数赋值了 因为始终会执行,答案是2

        new Foo().getName(); // 3    ? new出来的对象没有.getName属性因此会顺着原型链往上级找,所以是3

        new new Foo().getName(); // 3   跟上面是一个道理 还是3

函数可以创建作用域,函数中又可以声明函数,这样就形成了作用域嵌套作用域的链式结构

  • 首先在当前使用这个变量的作用域中进行查找,如果有该变量(有声明),就直接使用当前作用域中的这个变量,但是如果没有
  • 就去上一级作用域中进行查找,如果有该变量(有声明),就直接使用当前作用域中的这个变量,但是如果没有
  • 就继续沿着作用域链向上查找,直到找到全局为止
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值