JS 函数

  • 函数声明和函数表达式有什么区别 (*)
    • 解析器会率先读取函数声明,并使其在执行任何代码之前可以访问;函数表达式则必须等到解析器执行到它所在的代码行才会被执行。
    • 函数声明:
console.log(sum(10,10));
function sum(num1, num2) {
      return num1 + num2;
}

上述代码完全可以运行。因为在代码执行之前,解析器就已经通过一个名为函数声明提升的过程,读取并将函数声明添加到执行环境中。在执行sum(10,10)时JavaScript引擎会声明函数并将它们放到源代码树的顶部。等价于:

function sum(num1, num2) {
      return num1 + num2;
}//函数放置在顶部被声明
console.log(sum(10,10));
  • 函数表达式:
console.log(sum(10,10));
var sum = function(num1, num2) {
      return num1 + num2;
}

上述代码运行将会出错,因为函数处在一个var初始化语句中,而不是一个函数声明。等价于

var sum;
console.log(sum(10,10));
sum = function(num1, num2) {
      return num1 + num2;

在执行到函数所在的语句前,变量sum中不会保存有对函数的引用,这时var sum = undefined;那么alert(undefined(10,10))是没有任何意义的,所以会出错。

  • 什么是变量的声明前置?什么是函数的声明前置 (**)
    • 变量的声明前置:
      JavaScript引擎的工作方式是先解析代码,获取所有被声明的变量,然后再一行一行地运行。所有的变量声明语句都会被提升到代码顶部。
console.log(a);
var a = 123;

上述代码输出为undefined,因为在执行时先解析了var a,a声明后没有赋值所以为undefined,然后再执行console语句,最后给a赋值为3。可以写成:

var a;
console.log(a);
a = 123;
  • 函数的声明前置:
    JavaScript引擎将函数名视同变量名,所以采用function
    命令声明函数时,整个函数会像变量声明一样,被提升到代码顶部。
fn();
function fn() {}

上述代码不会报错,因为在fn()执行前,函数function fn() {}就被提升到代码的顶部,在执行fn()前就被声明了。可以写成:

function fn() {}
fn();

如果采用赋值语句(函数表达式)定义函数就会报错:

fn();
var fn = function (){};
//error

上述代码在执行时变量fn只是被声明了并没有被赋值为函数,可以写成:

var fn;
fn();
fn = function (){}

这里的fn被声明后为undefined,执行到fn()时其实是undefined(),这个是没有意义的所以会报错。

  • arguments 是什么 (*)
    • arguments对象:
      它与数组类似但不是Array实例。arguments可以使用类似数组的语法访问它每一个元素(第一个元素为arguments[0],第二个元素为arguments[1]),也可以用length属性来确定传递了多少参数。
function say() {
      console.log("Hello " + arguments[0] + ", " + arguments[1]);
}
say("world", "morning"); //Hello world, morning
  • arguments对象的length属性:
    通过访问arguments对象的length属性可以知道有多少个参数传递给了函数。
function howManyArgs() {
      console.log(arguments.length);
}
howManyArgs("str", 123);//2
howManyArgs();//0
howManyArgs(123);//1
howManyArgs("str", 321, "str1");//3
  • 我们可以利用arguments对象的length属性让函数能够接受不同个数的参数并分别实现不同的功能。
function add() {
      if(arguments.length == 1) {
             console.log(arguments[0] + 10);
       }  else if (arguments.length == 2){
             console.log(arguments[0] + arguments[1]);
       }
}
add(10); //20
add(20, 30); //50
  • 上述代码中当只传递给函数一个参数时给这个参数加上10,当传递给函数两个参数时则这两个参数相加。arguments对象可以与命名的参数一起使用,所以我们还可以写成下面这种形式
function add(num1, num2) {
      if(arguments.length == 1) {
             console.log(num1 + 10);
       }  else if (arguments.length == 2){
             console.log(arguments[0] +num2);
       }
}
add(10); //20
add(20, 30); //50
  • 这里我们可以知道num1与arguments[0]的值相同,它们可以互换。上例代码也告诉我们,不管你给函数命名了多少参数,JavaScript是不会管你给函数传递了多少参数的。
  • arguments的值与对应函数中命名参数的值保持同步:
function add(num1, num2) {
        arguments[1] = 10;
        console.log(arguments[0] + num2);
}
add(20, 30); //30
  • 上述代码每次执行到add()函数都会重写第二个参数,将第二个参数改写为10。arguments对象的值会对应到命名参数(arguments[1]与num2的值相同)所以修改了arguments[1]也就修改了num2,结果就是它们都变成了10。这里arguments[1]与num2的值虽然相同,但它们的内存空间是不同并且独立的,只是它们的值会同步。
  • 如果我们在这里只传递了一个参数,那么arguments[1]设置的值不会反映到命名参数里。因为arguments对象的长度只是由传递到函数的参数个数决定(add(20, 30)),不是由定义函数时命名的参数个数决定(function add(num1, num2) {})。


    只传递一个参数但赋值arguments[1]
  • 这个图里我们可以发现,执行函数时只传递了一个参数,在这样的情况下没有传递值的命名参数(num2)被自动赋为undefined值。与只声明了变量但又没有初始化赋值一样。即使我们给arguments[1]赋值为10,num2的值仍然还是undefined(只是值同步访问的空间是独立的)。
  • 函数的重载怎样实现 (**)
    • 我们可以利用检查传入函数中参数的类型和数量来做出不同的反应,用来模仿重载
    • 以通过处理传入的参数来实现类似重载的效果
function printPeopleInfo(name, age, sex) { 
if (name) { 
console.log(name); 
} 
if (age) { 
console.log(age); 
} 
if (sex) { 
console.log(sex); 
}
}
printPeopleInfo('Byron', 26);
printPeopleInfo('Byron', 26, 'male');
  • 通过遍历 arguments来实现类似重载的效果
function sum(){
var sum = 0;
for (var i = 0; i < arguments.length; i++) { 
sum = sum + arguments[i];
}
return sum;
}
console.log(sum(1,3,5));
  • 根据传入参数的不同类型来实现重载
function addOrSay(num1, num2) {
      for (var i = 0; i < arguments.length; i++) {
              if(arguments.length == 1 && !isNaN(arguments[i])) {
                     console.log(num1 + 10);
               }  else if (arguments.length == 2 && !isNaN(arguments[i])) {
                     console.log(arguments[0] +num2);
               }  else {
                     console.log(arguments[i]) ;
               }
       }
}
addOrSay(10);//10
addOrSay(20, 30);//50
addOrSay("Hello");//Hello
不同类型重载
  • 立即执行函数表达式是什么?有什么作用 (***)
    • 立即执行函数表达式(IIFE),是指定义函数后立即执行函数;为了避免歧义,javascript规定,如果function关键字出现在行首,一律解释为语句,下面是两种立即执行函数表达式的写法,它们都是以圆括号开头的,javascript引擎就会认为后面是一个表达式而不是函数定义的语句
    • 立即执行函数的语法错误
function() {}()//这里出错是因为没把明确告诉圆括号运算符这里是一个表达式。
                      解析器把这段语句当成了一个函数声明。
                      函数声明又必须要有标识符作为函数名称。
function fn() {}()//加上标识符结果成了声明了函数fn  结果出错。                       
function fn() {}(1) //1   末尾的括号作为运算符,又必须要提供表达式做为参数。
  • 写成下列方式直接调用时可以的
var fn = function() {}()//这样调用函数是可以的
  • 那么当要使用立即执行函数时可以使用括号来引导解析器,指明括号运算符附近是一个表达式
 ( function() {}() );
 [ function() {}() ];//当括号出现在匿名函数的末尾想要调用函数时,
                          它会默认将函数当成是函数声明。 function fn() {}
 ( function() {} )();//默认为函数表达式 var fn = function() {}
  • 也可以使用一元运算符来写立即执行函数引导解析器
    ~ function() {}();
    ! function() {}();
    + function() {}();
    - function() {}();
  • 甚至可以写成这样
delete function() {}();
typeof function() {}();
void function() {}();
new function() {}();
new function() {};
var f = function() {}();
--
1, function() {}();
1 ^ function() {}();
1 > function() {}();
  • 什么是函数的作用域链 (****)
    • 全局:在web浏览器中,全局执行环境就是window对象,所有的全局变量和函数都是作为window对象的属性(变量)和方法(函数)创建的。
    • 函数:每个函数都有自己的执行环境,由{}与全局的window对象分开。
    • 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链保证对执行环境有权访问的所有变量和函数的有序访问。
var color = "blue";
function changeColor() {
      if (color === "blue") {
          color = "red";
      }  else  {
          color = "blue";
      }
}
changeColor();
console.log("Color is now " + color);//Color is now red

在上述代码中,changeColor()函数的使用了全局环境下的变量color。函数可以在内部访问color就是因为可以在这个作用域链中找到它。

var color = "blue";
function changeColor() {
         var anotherColor = "red";
         console.log(anotherColor, color);
         function swapColor() {
             var tempColor = anotherColor;
             anotherColor = color;
             color = tempColor;
             //这里可以访问color, anotherColor, tempColor
             console.log(tempColor, anotherColor, color);
          }
          //这里可以访问color, anotherColor,
          swapColor();
}
//这里只能访问color
console.log( color);
changeColor();
运行效果
  • 以上代码共有三个执行环境:
    1 window全局环境:有一个变量color和一个函数changeColor()
    2 changeColor()的局部环境:有一个变量anotherColor和一个swapColor()函数,但也可以访问全局环境中的变量color
    3 swapColor()的局部环境:只有一个变量tempColor,在这个函数内可以访问到全局环境下的变量color,也可以访问changeColor()局部环境的anotherColor,因为其他两个环境是它的父执行环境。


    作用域链示意图
  • 参考:作用域链

  • 代码
    1 以下代码输出什么? (难度**)

  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('hunger', 28, '男');
    getInfo('hunger', 28);
    getInfo('男');
--
name: hunger
age: 28
sex: 男
["hunger", 28, "男"]
name valley
--
name: hunger
age: 28
sex: undefined
["hunger", 28]
name valley
--
name: 男
age: undefined
sex: undefined
["男"]
name valley

2 写一个函数,返回参数的平方和?如 (难度**)

function sumOfSquares(){
    var result = 0;
    for (var i = 0; i < arguments.length; i++) {
        result = result + arguments[i] * arguments[i];
    }
    return result;
   }
   sumOfSquares(2,3,4);   // 29
   sumOfSquares(1,3);   // 10

3 如下代码的输出?为什么 (难度*)

 console.log(a); 
 var a = 1;
 console.log(b);
//undefined   在浏览器中变量a被提升至顶部先被声明,执行到console.log(a)语句时变量a还没有被赋值。
//error   在整个全局中b并没有被赋值也没有被声明。

4 如下代码的输出?为什么 (难度*)

    sayName('world');
    sayAge(10);
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };
//hello world  声明函数function sayName(name)被提升到顶部进行声明
所以执行sayName('world');时可以正常运行。
//error  函数表达式var sayAge = function(age){}中var sayAge被提升到顶部进行声明
这时候sayAge为undefined,执行到sayAge(10);时为undefined(10)没有意义。

5 如下代码的输出?为什么 (难度**)

function fn(){}
    var fn = 3;
    console.log(fn);//3  声明完变量fn,又声明函数fn,最后赋值3给变量fn覆盖了

6 如下代码的输出?为什么 (难度**)

 function fn(fn2){
     console.log(fn2);
     var fn2 = 3;
     console.log(fn2);//3;
     console.log(fn);
     function fn2(){
        console.log('fnnn2');
    }
 }
fn(10);
/* function fn2(){
        console.log('fnnn2');
    } */  执行fn(10)时,参数10被传递到函数fn中并赋值给了fn2,
//在函数fn中提升声明变量fn2,并且提升声明函数fn2(){},这时候参数fn2的值被声明函数fn2覆盖,所以输出的是fn2函数
//3  执行完console.log(fn2); fn2变成变量并赋值为3
/* function fn(fn2){
   console.log(fn2);
   var fn2 = 3;
   console.log(fn2);//3;
   console.log(fn);
   function fn2(){
        console.log('fnnn2');
    }
 } */  执行console.log(fn);输出函数fn

7 如下代码的输出?为什么 (难度***)

    var fn = 1;
    function fn(fn){
         console.log(fn);
    }
    console.log(fn(fn));//error  
    typeof(fn);//number 

在JavaScript中上述代码的变量和函数声明会提升到最顶部最先声明,然后再赋值,所以可以写成:

    var fn;
     function fn(fn){
         console.log(fn);
    }
    fn = 1; 
    console.log(fn(fn));//这个时候fn是一个number,所以不能调用函数fn了。

8 如下代码的输出?为什么 (难度**)

    //作用域
    console.log(j);//undefined  //for循环不处在函数中声明的变量i和变量j仍然是全局变量,但是没赋值。
    console.log(i);//undefined  //同上
    for(var i=0; i<10; i++){
        var j = 100;
    }
    console.log(i);//10  //变量i在for循环中的i<10;i++递加到10。
    console.log(j);//100  //变量j在for循环中被赋值为100。

9 如下代码的输出?为什么 (难度****)

    fn();
    var i = 10;
    var fn = 20;
    console.log(i);
      function fn(){
        console.log(i);
        var i = 99;
        fn2();
        console.log(i);
        function fn2(){
            i = 100;
        }
    }
//undefined
//100
//10

上述代码可以写成下列形式

var i;
var fn;
function fn(){
/*statement*/
}
fn()//在这里执行fn(),下面看fn函数中是什么样的
    function fn(){
        var i;
        function fn2(){
            i = 100;
        }
    }
        console.log(i);//变量i只被声明输出undefined
        i = 99;
        fn2(); //这里执行函数fn2,变量i在函数fn2中赋值为100并返回
        console.log(i); // 输出100
    }
i = 10;
fn = 20;
console.log(i); //i被赋值为10,输出10

10 如下代码的输出?为什么 (难度5*****)

    var say = 0;
    (function say(n){
        console.log(n);
        if(n<3) return;
        say(n-1);
    }( 10 ));
    console.log(say);
/* 10
     9
     8
     7
     6
     5
     4
     3
     2
     0 */

上述代码可以写成

    var say;
    (function say(n){
        console.log(n);
        if(n<3) return;
        say(n-1);
    }( 10 ));//立即执行函数,在声明时就开始给函数say传入参数10并执行say函数
当执行多次say(n-1)后n=2时被return返回并跳出函数say不在执行say(n-1)
    say = 0//say变量被赋值0并输出。
    console.log(say);
/* 10
     9
     8
     7
     6
     5
     4
     3
     2
     0 */

本博客版权归 本人和饥人谷所有,转载需说明来源

推荐阅读更多精彩内容