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 */

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 1. 函数声明和函数表达式有什么区别 (*) 函数在JS中有三种方式来定义:函数声明(function decla...
    进击的阿群阅读 400评论 0 1
  • 工厂模式类似于现实生活中的工厂可以产生大量相似的商品,去做同样的事情,实现同样的效果;这时候需要使用工厂模式。简单...
    舟渔行舟阅读 7,621评论 2 17
  • 一、问答1、函数声明和函数表达式有什么区别 ()函数声明和函数表达式都是声明函数的方法。函数声明:function...
    崔敏嫣阅读 318评论 0 0
  • 概念 1、函数声明和函数表达式有什么区别? ECMAScript规定了三种声明函数方式: 构造函数首先函数也是对象...
    周花花啊阅读 426评论 1 1
  • 函数声明和函数表达式有什么区别? 函数声明和函数表达式是EMACScript规定的两种不同的声明函数的方法。1.函...
    LeeoZz阅读 327评论 0 1