【73】JS(6)——函数②进阶

★文章内容学习来源:拉勾教育大前端就业集训营


本篇学习目标:
1.更深层次理解函数;
2.掌握函数表达式——另一种定义函数的方式;
3.清除函数数据类型;
4.理解arguments对象;
5.熟练运用函数递归;
6.理解变量类型(全局/局部)、作用域及作用域链;
7.理解预解析的好处;
8.掌握IIEF自调用函数实现方法。

目录

  • 一、 函数表达式
    • 1. 含义
    • 2. 方法
    • 3. 调用
  • 二、函数数据类型
    • 1. 具体情况
    • 2. 可参与其他程序
  • 三、arguments对象
    • 1. 含义
    • 2. 案例
  • 四、函数递归
    • 1. 含义
    • 2. 案例
  • 五、作用域
    • 1. 含义
    • 2. 函数的作用域
  • 六、全局or局部变量
    • 1. 含义
    • 2. 参数也是局部变量
    • 3. 不写var关键字的影响
  • 七、作用域链
    • 1. 含义
    • 2. 案例
    • 3. 遮蔽效应
  • 八、预解析
    • 1. 含义
        • (1) 预解析过程
        • (2) 执行过程
    • 2. 变量声明提升
    • 3. 函数声明提升
    • 4. 先提升变量声明,再提升函数声明
    • 5. 函数表达式进行的是变量声明提升
    • 6. 应用
  • 九、IIEF自调用函数
    • 1. 含义
    • 2. 调用方式
    • 3. 启发
    • 4. 函数矮化成表达式的方法
        • (1) 数学运算符
        • (2) 逻辑运算符:
    • 5. 注意


一、 函数表达式

1. 含义

  • 函数定义的另外一种方式

2. 方法

  • 定义方法:就是将函数的定义、匿名函数赋值给一个变量
  • 函数定义赋值给一个变量,相当于将函数整体矮化成了一个表达式。
  • 匿名函数:函数没有函数名。

3. 调用

  • 调用函数表达式,方法是给变量名()执行,不能使用函数名()执行
//函数表达式——定义函数的另一种方式
//将函数fun1赋值给一个变量foo1
var foo1 = function fun1 ( ){
     
    console.log("今天天气很好");
}; //注意结束处有分号

//调用——用变量名加括号,不用函数名了
foo1();
//因为调用也用不到函数名,所以其实函数名可以省略
//将匿名函数赋值给一个变量foo1
var foo1 = function (){
     
    console.log("今天天气很好");
}; //注意结束处有分号

//调用——用变量名加括号,不用函数名了
foo1();


二、函数数据类型

1. 具体情况

  • 函数是一种单独的数据类型 Function(object的一种)。
//检测函数数据类型
function fun1 () {
     
    console.log(1);
}
console.log(typeof(fun1));  //function

//检测函数数据类型
var foo1 = function (a,b){
     
    console.log(a + b);
}; 
console.log(typeof(foo1));//function

2. 可参与其他程序

  • 由于函数是一种数据类型,可以参与其他程序。
  • 例如,可以把函数作为另一个函数的参数,在另一个函数中调用。
//将函数作为另一个函数的参数
setInterval(function () {
     
    console.log(1);
},1000);
//以上setInterval函数有两个参数,第一参数位置是匿名函数(实现控制台输出1),第二个参数位置是1000;
//其中1000表示1000毫秒也就是1秒
//总体的实现的功能:每隔1秒执行一下第一个参数位置的值。
  • 或者,可以把函数可以作为返回值从函数内部返回。


三、arguments对象

1. 含义

  • JavaScript 中,arguments 对象是比较特别的一个对象,实际上是当前函数的一个内置属性; 也就是说所有函数都内置了一个 arguments 对象;

  • 有了arguments对象,形参不会成为实参的限制。

  • arguments 对象中存储了传递的所有的实参。

  • arguments 是一个伪数组,因此及可以进行遍历

  • 总结一句是: 函数的实参个数和形参个数可以不一致,所有的实参都会存储在函数内部的 arguments 类数组对象中。

//arguments对象
function sum1(a,b) {
     
    console.log(a+b);
}
sum1(1,5);//6
sum1(2,6,6,7); //8,只加前两个
sum1(2); //2+undefined=NaN

【73】JS(6)——函数②进阶_第1张图片

function fun(a) {
     
    console.log(a);
}
fun (1,2,3,4,5,6,7);  //实参的个数是远远大于形参的
function fun() {
     
    console.log(arguments);
    console.log(arguments.length);//7
    //使用数组遍历可以把所有实参都遍历一遍
    for (var i = 0 ; i < arguments.length ; i++) {
     
        console.log(arguments[i]);
    }
}

【73】JS(6)——函数②进阶_第2张图片


2. 案例

定义一个求和函数,如果传入 1 个参数,返回它自己;如果传入两个参数,返回他们的和;如果传入三个参数,先比较前两个的大小,大的与第三个参数求和返回;如果传入 4 个及以上,输出错误提示。

//方法1:用多分支if语句
function sum(a,b,c) {
     
    if (arguments.length == 1) {
     
        return a
    } else if (arguments.length == 2) {
     
        return a + b;
    } else if (arguments.length == 3) {
     
        return a > b ? a+c : b+c ;
    } else {
     
        throw new Error("您输入的参数超过3个!");
    }
}
console.log(sum(1)); //1
console.log(sum(1,2)); //3
console.log(sum(1,2,3)); //5
//console.log(sum(1,2,3,4)); //您输入的参数超过3个!
//方法2,用switch语句
function sum(a,b,c) {
     
    switch (arguments.length) {
     
        case 1:
            return a;
        break;
            case 2:
                return a+b;
        break;
        case 3:
            return a>b?a+b:b+c;
        break;
        default:
           throw new Error("您输入的参数超过3个!");
    }
}
console.log(sum(1));
console.log(sum(1,2));
console.log(sum(1,2,3));
console.log(sum(1,2,3,4));

【73】JS(6)——函数②进阶_第3张图片



四、函数递归

1. 含义

  • 函数内部可以通过函数名调用函数自身的方式,就是函数递归现象。
  • 递归的次数太多容易出现错误(RangeError):超出计算机的计算最大能力。
  • 更多时候,使用递归去解决一些数学中的现象,如下举例:

举例:如果实参为1,返回1;如果实参是大于1的数,返回实参+函数调用上一项。

//函数递归举例
function fun (a) {
     
    if (a == 1) {
     
        return 1;
    } else if (a > 1) {
     
        return a + fun(a-1);
    }
};
console.log(fun(1)); //1
console.log(fun(2));// 2+ fun(1)=2+1=3
console.log(fun(3));//3+fun(2)=3+2+1=6

【73】JS(6)——函数②进阶_第4张图片


2. 案例

输出斐波那契数列的某一项的值。
斐波那契数列:后面的一项数据是前两项数据之和。1,1,2,3,5,8,13,21,34,55……

//其中参数a代表斐波那契数列的第a项,feibo(a)输出的结果是第a项的值
function feibo(a) {
     
    if (a == 1) {
     
        return 1;
    } else if (a == 2) {
     
        return 1;
    } else if (a > 2) {
     
        return feibo(a-1)+feibo(a-2);
    }
}
console.log(feibo(1)); //1
console.log(feibo(2)); //1
console.log(feibo(3)); //2
console.log(feibo(4)); //3
console.log(feibo(5)); //5
console.log(feibo(6));  //8
console.log(feibo(7));  //13
console.log(feibo(8));  //21
console.log(feibo(9));   //34
console.log(feibo(10));  //44
//函数部分,也可以稍微省略一点写成这样
function feibo(a) {
     
    if (a == 1 || a ==2) {
     
        return 1;
    }  else if (a > 2) {
     
        return feibo(a-1)+feibo(a-2);
    }
}


五、作用域

1. 含义

  • 作用域:变量可以起作用的范围。
  • 如果变量定义在一个函数内部,只能在函数内部被访问到,在函数外部不能使用这个变量,函数就是变量定义的作用域。
//定义一个函数
function fun() {
     
    //其中有个局部变量a
    var a = 2;
    //在函数内部是可以调用的
    console.log(a);
}
//调用函数
fun ();//2
//外部调用函数内部的局部变量a,会出现错误!
console.log(a); //错误

【73】JS(6)——函数②进阶_第5张图片

  • 任何一对花括号 { } 中的结构体都属于一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域

但是现阶段(目前在学js5),可以认为 JavaScript 没有块级作用域
因为在es6之前没有块级作用域的的概念,只有函数作用域


2. 函数的作用域

  • 函数也有自己的作用域,定义在哪个作用域内部,只能在这个作用域范围内被访问,出了作用域不能被访问的。
  • 函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写。
//目前没有块级作用域,只有函数作用域概念。
//函数定义在另一个函数内部,如果外部函数没有执行时,相当于内部代码没写。
function outer() {
     
    var a = 1;
    console.log(a);
    function inner() {
     
        var b = 2;
        console.log(b);
    }
    //因为inner函数定义在outer函数内部,所以也要在outer函数内部调用
    inner();  //但是只有外部的outer函数调用后,这个才会起作用
}
outer(); //outer函数是可以在外部调用的
inner();//inner函数就不可以在外部调用了,会出现错误

【73】JS(6)——函数②进阶_第6张图片



六、全局or局部变量

1. 含义

  • 局部变量:定义在函数内部的变量,只能在函数作用域内部被访问到,在外面没有定义的。
  • 全局变量:从广义上来说,也是一种局部变量,定义在全局的变量,作用域范围是全局,在整个 js 程序任意位置都能够被访问到。
  • 局部变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁。

2. 参数也是局部变量

  • 函数的参数本质是一个变量,也有自己的作用域,函数的参数也是属于函数自己内部的局部变量,只能在函数内部被使用,在函数外面没有定义。

3. 不写var关键字的影响

  • 在函数内部想要定义新的变量,如果不加关键字 var,相当于定义的全局变量。如果全局也有相同的标识符,会被函数内部的变量影响,局部变量污染全局变量。
  • 注意:每次定义变量时都必须写 var 关键字,否则就会定义在全局,可能污染全局。


七、作用域链

1. 含义

  • 只有函数可以制造作用域结构, 那么只要是代码,就至少有一个作用域, 即全局作用域。
  • 凡是代码中有函数,那么这个函数就构成另一个作用域。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域。
  • 将这样的所有的作用域列出来,可以有一个结构: 函数内指向函数外的链式结构,就称作作用域链。

2. 案例

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

【73】JS(6)——函数②进阶_第7张图片

function f1() {
     
    var num = 123;
    function f2() {
     
        console.log(num); 
    }
    f2();
}
var num = 456;
f1();

【73】JS(6)——函数②进阶_第8张图片


3. 遮蔽效应

  • 程序在遇到一个变量时,使用时作用域查找顺序,不同层次的函数内都有可能定义相同名字的变量,一个变量在使用时,会优先从自己所在层作用域查找变量,如果当前层没有变量定义会按照顺序从本层往外依次查找,直到找到第一个变量定义。
  • 整个过程中会发生内层变量遮蔽外层变量的效果,叫做“遮蔽效应”。
//遮蔽效应
var a = 1;//全局作用域中的变量a
function outer() {
     
    var a = 2; //外部函数作用域中的变量a
    console.log(a); //2//从本层往外依次查找,直到找到第一个变量定义
    function inner() {
     
        var a =3;  //内部函数作用域内的a
        console.log(a);  //3//从本层往外依次查找,直到找到第一个变量定义
    }
    console.log("再执行内部函数");
    inner();  //3
}
console.log("先执行外部函数");
outer();  //2  

【73】JS(6)——函数②进阶_第9张图片



八、预解析

1. 含义

  • JavaScript 代码的执行是由浏览器中的 JavaScript 解析器来执行的。
  • JavaScript 解析器执行 JavaScript 代码的时候,分为两个过程:预解析过程和代码执行过程。

(1) 预解析过程

1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
3. 先提升 var,再提升 function。

(2) 执行过程

  • 在预解析之后,根据新的代码顺序,从上往下按照既定规律执行 js 代码。

2. 变量声明提升

  • 在预解析过程中,所有定义的变量,都会将声明的过程提升到所在的作用域最上面,在将来的代码执行过程中,按照先后顺序会先执行被提升的声明变量过程。
  • 提升过程中,只提升声明过程,不提升变量赋值
  • 相当于变量定义未赋值,变量内存储 undefined 值。
  • 因此,在 js 中会出现一种现象,在前面调用后定义的变量,不会报错,只会使用 undefined 值,如下例:
//变量声明提升
console.log(a); //虽然先调用,但是没有报错,而是输出了undefined值
var a = 1;

【73】JS(6)——函数②进阶_第10张图片

  • 其实这是因为经历的预解析过程,会把变量声明提升,但是没有提升变量赋值,所以相当于代码是如下顺序执行的:
//相当于是如下执行的
var a;
console.log(a);  //undefined
a = 1;

3. 函数声明提升

  • 在预解析过程中,所有定义的函数,都会将声明的过程提升到所在的作用域最上面,在将来的代码执行过程中,按照先后顺序会先执行被提升的函数声明过程。
  • 在预解析之后的代码执行过程中,函数定义过程已经在最开始就会执行,一旦函数定义成功,后续就可以直接调用函数。
  • 因此,在 js 中会出现一种现象,在前面调用后定义的函数,不会报错,而且能正常执行函数内部的代码,如下例:
//函数声明提升
fun (); //先调用也可以正常执行,输出12
function fun () {
     
    console.log(12);
}

【73】JS(6)——函数②进阶_第11张图片


4. 先提升变量声明,再提升函数声明

  • 预解析过程中,先提升 var 变量声明,再提升 function 函数声明。
  • 假设出现变量名和函数名相同,那么后提升的函数名标识符会覆盖先提升的变量名,那么在后续代码中出现调用标识符时,内部是函数的定义过程,而不是 undefined。
  • 如果调用标识符的过程在源代码函数和变量定义后面,相当于函数名覆盖了一次变量名,结果在执行到变量赋值时,又被新值覆盖了函数的值,那么在后面再次调用标识符,用的就是变量存的新值。
//举例:先变量声明提升,再函数声明提升
console.log(fun);
var fun = "haha";
fun();
function fun () {
     
    console.log(12);
}

【73】JS(6)——函数②进阶_第12张图片

  • 因为预解析,所以代码执行顺序相当于下面这个过程:
//相当于下面这个过程
var fun;
function fun () {
     
    console.log(12);
}
console.log(fun);//输出函数
fun = "haha";
fun();//错误类型提示,这时候fun已经被赋值成"haha",不是个函数了
  • 建议:不要书写相同的标识符给变量名或函数名,避免出现覆盖

5. 函数表达式进行的是变量声明提升

  • 在预解析过程中,函数表达式进行的是变量声明提升,而不是函数声明提升。
  • 提升后变量内部存的是一个 undefined;
  • 在前面进行函数方法调用,数据类型会提示错误
//函数表达式进行的是变量声明提升
foo1(1,5);
var foo1 = function (a,b) {
     
    console.log(a+b);
}

【73】JS(6)——函数②进阶_第13张图片

  • 建议:定义函数时,最好使用 function 关键字定义方式,这样函数声明提升可以永远生效。

6. 应用

  • 函数声明提升可以用于调整代码的顺序
  • 将大段的定义过程放到代码最后,但是不影响代码执行效果
//举例
fun1 ();
fun2 ();
fun3 ();
fun4 ();
function fun1 () {
     
    console.log(1);
    console.log(3);
    console.log(5);
} 
function fun2() {
     
    console.log(2);
    console.log(4);
    console.log(6);
} 
function fun3() {
     
    console.log(-1);
    console.log(-3);
    console.log(-5);
} 
function fun4() {
     
    console.log(-2);
    console.log(-4);
    console.log(-6);
} 

【73】JS(6)——函数②进阶_第14张图片



九、IIEF自调用函数

1. 含义

  • IIFE:immediately-invoked function expression,叫做即时调用的函数表达式,也叫做自调用函数;
  • 表示函数在定义时就立即调用

2. 调用方式

  • 正常函数调用方式:函数名函数表达式的变量名后面加 () 运算符。
  • 而其中函数名定义的形式不能实现立即执行自调用
//函数名定义的不可以实现自调用,比如这样的函数
function fun1 () {
     
    console.log(1);
}
  • 函数使用函数表达式形式可以实现立即执行
  • 原因是因为函数表达式定义过程中,将一个函数矮化成了一个表达式,后面加 () 运算符就可以立即执行。
//函数表达式的形式定义的函数可以实现自调用,方法是后面直接加()运算符
var foo1 = function() {
     
    console.log("函数表达式的形式定义的函数可以实现自调用,方法是后面直接加()运算符");
} ();

【73】JS(6)——函数②进阶_第15张图片


3. 启发

  • 如果想实现 IIFE,可以想办法将函数矮化成表达式。
  • 函数矮化成表达式,就可以实现自调用。

4. 函数矮化成表达式的方法

  • 可以让函数参与一些运算,也就是说给函数前面加一些运算符

(1) 数学运算符

+ - ()

//将函数矮化成表达式,以实现自调用
//算术运算符 + - ()
+ function fun1() {
     
    console.log("1前面加算术运算符+");
}();

- function fun2 () {
     
    console.log("2前面加算术运算符-");
}();

( function fun3 () {
     
    console.log("3整体加算术运算符()");
})();

【73】JS(6)——函数②进阶_第16张图片

(2) 逻辑运算符:

非运算

//将函数矮化成表达式,以实现自调用
//逻辑运算符!
! function fun4 () {
     
  console.log("4前面加逻辑运算符!")
}();

【73】JS(6)——函数②进阶_第17张图片


5. 注意

  • IIFE 结构可以关住函数的作用域,在结构外面是不能调用函数的
  • IIFE 最常用的是 () 运算符,而且函数可以不写函数名,使用匿名函数


下篇继续:【74】JS(7)——对象①基本介绍

你可能感兴趣的:(前端学习中,js,javascript,函数)