美文网首页饥人谷技术博客
JavaScript常见作用域问题

JavaScript常见作用域问题

作者: zh_yang | 来源:发表于2017-08-27 17:32 被阅读0次

    1、立即执行函数表达式是什么?有什么作用

    我们都知道,一般定义一个函数有函数声明和函数表达式两种方法:
    function fnName () {…};   //函数声明
    var fnName = function () {…};   //函数表达式
    两者的区别是:

    • Javascript引擎在解析javascript代码时会‘函数声明提升'(Function declaration Hoisting)当前执行环境(作用域)上的函数声明,而函数表达式必须等到Javascirtp引擎执行到它所在行时,才会从上而下一行一行地解析函数表达式。
    • 函数表达式后面可以加括号立即调用该函数,函数声明不可以,只能以fnName()形式调用 。

    所以,要在函数体后面加括号就能立即调用,则这个函数必须是函数表达式,不能是函数声明。

    在function前面加()、!、+、-、=等运算符,都将函数声明转换成函数表达式,消除了javascript引擎识别函数表达式和函数声明的歧义,告诉javascript引擎这是一个函数表达式,不是函数声明,可以在后面加括号,并立即执行函数的代码。

    (function(){
      console.log(123)
    })()         //输出123
    
    (function(){
      console.log(123)
    }())         //输出123
    
    !function(){
      console.log(123)
    }()         //输出123
    
    +function(){
      console.log(123)
    }()         //输出123
    
    -function(){
      console.log(123)
    }()         //输出123
    
    var a=function(){
      console.log(123)
    }()         //输出123
    

    加括号是最安全的做法,因为!、+、-等运算符还会和函数的返回值进行运算,有时造成不必要的麻烦。

    那么这样做有什么作用:
    在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。

    2、求n!,用递归来实现

    利用n!等于n(n-1)!,(n-1)!等于(n-1)((n-1)-1)!,直至括号内的值为1,另外0!等于1。

    function fac(n){
      if( n===1 || n===0 ){return 1}
      return (n*fac(n-1))
    }
    console.log(fac(1))     //输出1
    console.log(fac(2))     //输出2
    console.log(fac(3))     //输出6
    console.log(fac(4))     //输出24
    console.log(fac(5))     //输出120
    

    也可以用三元运算符 ' ? ' 写作:

    function fac(n){
      return n===0 || n===1 ? 1 : n*fac(n-1)
    }
    console.log(fac(5))     //输出120
    

    另外也可以利用循环来处理

    function fac(n){
      var i =1
      if( n===1 || n===0 ){console.log(1)}
      else{
        for( var j=n;j>1;j--){
          i*=j
        }
        console.log(i)
      } 
    }
    fac(5)     //输出120
    

    3、以下代码输出什么?

    function getInfo(name, age, sex){
            console.log('name:',name);
            console.log('age:', age);
            console.log('sex:', sex);
            console.log(arguments);
            arguments[0] = 'valley';
            console.log('name', name);
        }
    
    getInfo('饥人谷', 2, '男');
    getInfo('小谷', 3);
    getInfo('男');
    

    注意:

    • console.log(…)输出的并不是括号内的返回值,而是输出括号内所有表达式的值。
    • 给函数传入参数时是按顺序传入,不会自动识别,没有传入参数则为undefined。
      所以getInfo('饥人谷', 2, '男');相当于:
    function getInfo(){
                arguments[0]='饥人谷'
                arguments[1]=2
                arguments[2]='男'
                console.log('name:','饥人谷')     
                console.log('age:', 2)
                console.log('sex:', '男')
                console.log(['饥人谷',2,'男'])
                arguments[0] = 'valley'
                console.log('name', 'valley')
    }
    getInfo()
    
    /*输出:
    name: 饥人谷
    age: 2
    sex: 男
    ["饥人谷", 2, "男"]
    name valley
    */
    

    getInfo('小谷', 3);相当于

    function getInfo(){
                arguments[0]='饥人谷'
                arguments[1]=3
                arguments[2]=undefined
                console.log('name:','饥人谷')     
                console.log('age:', 3)
                console.log('sex:', undefined)
                console.log(['饥人谷',3])
                arguments[0] = 'valley'
                console.log('name', 'valley')
    }
    getInfo()
    
    /*输出:
    name: 饥人谷
    age: 3
    sex: undefined
    ["饥人谷", 3]
    name valley
    */
    

    getInfo('男');相当于

    function getInfo(){
                arguments[0]='男'
                arguments[1]=undefined
                arguments[2]=undefined
                console.log('name:','男')     
                console.log('age:', undefined)
                console.log('sex:', undefined)
                console.log(['男'])
                arguments[0] = 'valley'
                console.log('name', 'valley')
    }
    getInfo()
    
    /*输出:
    name: 男
    age: undefined
    sex: undefined
    ["男"]
    name valley
    */
    

    四、 写一个函数,返回参数的平方和?

    function sumOfSquares(){
       }
       var result = sumOfSquares(2,3,4)
       var result2 = sumOfSquares(1,3)
       console.log(result)  //29
       console.log(result2)  //10
    

    解答思路:遍历每一个传入的参数,求它们的平方和;通过不同遍历的方法,有不同的写法。

    • for循环方法

    循环每执行一次,都要检查一次 array.length 的值,读属性要比读局部变量慢,尤其是当 array 里存放的都是 DOM 元素(像 array = document.getElementByClassName(“class”);),因为每次读 array.length 都要扫描一遍页面上 class=”class” 的元素,速度更是慢得抓狂。

    function sumOfSquares(){
        var sum=0
        for(var i=0;i<arguments.length;i++){
            sum+=arguments[i]*arguments[i]
        }
        return sum
    }
    var result = sumOfSquares(2,3,4)
    var result2 = sumOfSquares(1,3)
    console.log(result)  //29
    console.log(result2)  //10
    
    • for-in循环方法

    for-in 需要分析出 array 的每个属性,这个操作的性能开销很大

    function sumOfSquares(){
        var sum=0
        for(i in arguments){
            sum+=arguments[i]*arguments[i]
        }
        return sum
    }
      var result = sumOfSquares(2,3,4)
      var result2 = sumOfSquares(1,3)
      console.log(result)  //29
      console.log(result2)  //10
    
    • 先把数组的长度先查出来,存进一个局部变量,那么循环的速度将会大大提高
    function sumOfSquares(){
        var sum=0
        var length=arguments.length
        for(var i=0;i<length;i++){
            sum+=arguments[i]*arguments[i]
        }
        return sum
    }
      var result = sumOfSquares(2,3,4)
      var result2 = sumOfSquares(1,3)
      console.log(result)  //29
      console.log(result2)  //10
    
    • 不过我们还可以让它更快。如果循环终止条件不需要进行比较运算,那么循环的速度还可以更快
    • 把数组下标改成向 0 递减,循环终止条件只需要判断 i 是否为 0 就行了。因为循环增量和循环终止条件结合在一起,所以可以写成更简单的 while 循环
    function sumOfSquares(){
        var sum=0
        var i=arguments.length
        while(i--){
            sum+=arguments[i]*arguments[i]
        }
        return sum
    }
    var result = sumOfSquares(2,3,4)
    var result2 = sumOfSquares(1,3)
    console.log(result)  //29
    console.log(result2)  //10
    

    5、如下代码的输出?为什么

    console.log(a);
    var a = 1;
    console.log(b);
    

    由于变量提升的原则,上述代码相当于

    var a
    console.log(a)    //输出undefined
    a=1
    console.log(b)    //报错:Uncaught ReferenceError: b is not defined
    
    • 原因:先声明了变量a,a并没有复制,所以此时输出a得到undefined;b没有声明就直接调用,所以会报错。

    6、 如下代码的输出?为什么

    sayName('world');
        sayAge(10);
        function sayName(name){
            console.log('hello ', name);
        }
        var sayAge = function(age){
            console.log(age);
        };
    

    输出:"hello" "world" Uncaught TypeError: sayAge is not a function

    • 原因:由于函数声明会自动提升,而函数表达式不会;sayName('world');正常执行;sayAge(10);会调用函数sayAge但此时只声明了sayAge是变量,并未将函数声明赋值给它,所以它还不是函数,所以报错。

    7、 如下代码输出什么? 为什么

    var x = 10
    bar() 
    function foo() {
      console.log(x)
    }
    function bar(){
      var x = 30
      foo()
    }
    

    输出:10

    • 原因:函数的作用域与其定义时所在的作用域有关,与其调用时所在的作用域无关;在bar()中可以调用全局作用域中的foo(),而foo()不能访问bar()的局部变量x(x=30),只能访问全局作用域中的全局变量x(x=10),所以输出10。

    8、如下代码输出什么? 为什么

    var x = 10;
    bar() 
    function bar(){
      var x = 30;
      function foo(){
        console.log(x) 
      }
      foo();
    }   
    

    输出:30

    • 原因:变量的查找是就近原则,去寻找var定义的变量,当就近没有找到的时候就去查找外层。函数域优先于全局域,故局部变量x会覆盖掉全局变量x,所以输出30。

    9、如下代码输出什么? 为什么

    var a = 1
    function fn1(){
      function fn2(){
        console.log(a)
      }
      function fn3(){
        var a = 4
        fn2()
      }
      var a = 2
      return fn3
    }
    var fn = fn1()
    fn() //输出多少
    

    输出:2

    • 原因:先执行fn1,返回值是fn3的函数表达式,此时fn1的局部作用各变量已经赋值完毕;将fn3赋值给fn,即fn=function (){var a=4;fn2()};执行fn,调用执行fn2,fn2查找变量a为2,所以输出2。

    10、如下代码输出什么? 为什么

    var a = 1
    function fn1(){
      function fn3(){
        var a = 4
        fn2()
      }
      var a = 2
      return fn3
    }
    function fn2(){
      console.log(a)
    }
    var fn = fn1()
    fn() //输出多少
    

    输出:1

    • 原因:先执行fn1,返回值是fn3的函数表达式,此时fn1的局部作用各变量已经赋值完毕;将fn3赋值给fn,即fn=function (){var a=4;fn2()};执行fn,调用执行fn2,fn2查找变量a为1,所以输出1。

    11、如下代码输出什么?为什么

    var a = 1
    function fn1(){
    
      function fn3(){
        function fn2(){
          console.log(a)
        }
        fn2()
        var a = 4
      }
      var a = 2
      return fn3
    }
    var fn = fn1()
    fn() //输出多少
    

    输出:undefined

    • 原因:先执行fn1,返回值是fn3的函数表达式,此时fn1的局部作用各变量已经赋值完毕,但fn3中的变量并没有赋值;将fn3赋值给fn,即fn=function (){function(){console.log(a)};fn2();var a=4};执行fn,调用执行fn2,fn2查找变量a,由于变量提升,此时a已声明但没有赋值,所以输出undefined。

    12、.如下代码输出什么?为什么

    var obj1 = {a:1, b:2};
    var obj2 = {a:1, b:2};
    console.log(obj1 == obj2);   //输出false
    console.log(obj1 = obj2);    //输出{a:1, b:2}
    console.log(obj1 == obj2);   //输出true
    
    • console.log(obj1 == obj2) 因为此时obj1与obj2在内存中堆存储位置不同,所以不相等,输出false
    • console.log(obj1 = obj2) 先把obj2的引用赋给obj1,此时它们在堆内存存储位置相同,这里输出obj1本身,所以输出{a:1, b:2}
    • console.log(obj1 == obj2) 此时obj1和obj2是相同的引用,所以相等,输出true。

    13、如下代码输出什么? 为什么

    var a = 1
    var c = { name: 'jirengu', age: 2 }
    
    function f1(n){
      ++n
    }
    function f2(obj){
      ++obj.age
    }
    
    f1(a) 
    f2(c) 
    f1(c.age) 
    console.log(a)     //输出 1 
    console.log(c)     //输出 {name: "jirengu", age: 3}
    
    • f1(n)中是通过参数传递基本类型,f1(a)把a=1赋值给n,++n并不影响a的值
    • f2(obj)中是通过参数传递引用类型,f1(c)把c的引用传递给obj,obj.age发生改变,c.age也要改变。

    14、写一个深拷贝函数

    思路:深拷贝的难点是对对象中嵌套的对象进行深拷贝,而不是直接引用,如嵌套的对象、数组、函数等typeof返回值为object的属性或方法;但是null除外,虽然typeof null返回object,但是他是一个基本类型,不需要,深度拷贝。
    利用typeof,对对象深拷贝:

    var soldier1 = {
        name: '侦察兵', 
        age: 3,
        equipment:{rifle:'AK-47',pistol:'desertEagle',knife:null},
        family:['father','sister'],
        other:null
    }
    function deepCopy(oldObj) {
            var newObj = {}
            for(var key in oldObj) {
                if(typeof oldObj[key] === 'object' && oldObj[key]) {
                    newObj[key] = deepCopy(oldObj[key])
                }else{
                    newObj[key] = oldObj[key]
                }
            }
            return newObj;
        }
    var soldoer2=deepCopy(soldier1)
    console.log(soldoer2)
    
    /*输出
    {
    age:3
    equipment:{rifle: "AK-47", pistol: "desertEagle", knife: null}
    family:{0: "father", 1: "sister"}
    name:"侦察兵"
    other:null
    }
    */
    console.log(soldier1==soldoer2)     //输出 false,已经深拷贝
    

    相关文章

      网友评论

        本文标题:JavaScript常见作用域问题

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