第五节:JavaScript函数的认识
一.初步了解函数
为什么要有函数呢,我们先从函数的功能出发,来认识一下函数给我们带来的好处
比如:
if( 1 > 0 ){
console.log("a");
console.log("b");
console.log("c");
}
if( 2 > 0 ){
console.log("a");
console.log("b");
console.log("c");
}
if( 3 > 0 ){
console.log("a");
console.log("b");
console.log("c");
}
当我们满足一定条件要执行一块代码,要执行一个功能,这就这个功能在很多条件满足的情况下都要执行.,我要是这么写,你会发现我代码的重复性非常高,你看这些代码都是重复的.
还记得我们css中的属性重复吧,我们可以通过分组解决重复代码的复用问题
那这种重复在我们js中也是不被推荐的 ,我们管这种重复交耦合,耦合就是重复冗余的.
那如何解决这种耦合问题呢?
js中也存在一个这种封装的盒子, 来对代码进行复用, 这个封装的合资就是函数
简而言之, 就是讲一些复用的代码封装在一个函数中,通过函数在不同地方的使用来复用封装的代码
直接上代码康康:
function test(){
console.log("a");
console.log("b");
console.log("c");
}
if( 1 > 0 ){
test();
}
if( 2 > 0 ){
test();
}
if( 3 > 0 ){
test();
}
函数组基本的作用就是简化代码, 提高开发效率
1. 函数的定义和调用
函数和变量一样, 先定义(声明)在使用
//定义一个函数,函数就是一组语句的集合
function haha(){
console.log(1);
console.log(2);
console.log(3);
console.log(4);
}
//调用函数
haha();
1.1 函数的声明
定义一个函数,用关键字function来定义,
- function就是英语“功能”的意思。表示这里面定义的一个功能。
- function后面有一个空格,后面就是函数名字,函数的名字也是标识符,因此命名规范和变量命名是一样的。
- 名字后面有一对儿圆括号,里面放置参数,一会儿在介绍参数。然后就是大括号,大括号里就是封装的函数语句
定义一个函数:
function 函数名(){
}
函数如果不调用,那么里面的语句就一辈子不会执行,不调用就等于白写。
1.2 函数的调用
调用一个函数的方法非常简单,函数名后面加一个() ,() 是一个运算符,表示执行一个函数。
function theFirstName(){
}
console.log(theFirstName); // 弱类型的语言用于不输出内存地址,,输出指针指向的房间
执行函数语句:
函数名()
一旦调用了函数,函数内部的语句就会执行。
函数是一些语句的集合,仔细体会, 感觉上函数就是让一些零散语句成为一个军团,集体作战。要不出动都不出动,要出动就全出动。前提得到命令(调用)才会出动(执行)。
2. 函数的参数
2.1 参数的了解
上一节了解了函数的定义和使用, 但是会发现, 函数内部的语句都是相同,
那么如果我们需要在不同的地方, 使用不同语句, 就回有问题, 那么如何解决这个问题呢?
实际上我们可以通过“参数”这个东西,来让语句有差别。
定义函数的时候,内部语句可能有一些悬而未决的量,就是变量,这些变量,我们要求在定义的时候都罗列在小括号中:
比如:
function fun(a){
// 参数就相当于隐式的在函数体呢var a;
console.log("我第" + a + "次说爱你");
}
调用的时候,要把这个变量的真实的值,一起写在括号里,这样随着函数的调用,这个值也传给了a:
执行这个函数:
fun(88);
罗列在function小括号中的参数,叫做形式参数;调用时传递的数值,叫做实际参数。
形式参数就像占位置,先把位置站好,等你来赋值;
2.2 参数个数
参数可以有无数个,用逗号隔开。
//有多少形式参数都可以,都罗列出来
function fun(a,b){
console.log(a + b);
}
fun(3,5); //输出8
fun(8,11); //输出19
定义函数的时候,参数是什么类型的没写,不需要指定类型:
function sum(a,b){
console.log(a + b);
}
也就是说调用的时候,传进去什么什么类型,就是a、b什么类型
sum("5",12);
输出512,做的是连字符的运算。
有了参数以后,函数就实现了真正的功能
function sum(a,b){
var c = a + b;
console.log(c);
}
sum(1,3); // 这就是一个求和的功能吧
// 咱们就相当于把两个函数的规则抽象出来了
// 这个时候是不是就像我们数学中x,y, 随便代换值
// 这里面变量就是参数
// 有了参数以后这里就变成了抽象规则了,而不是原来的那种聚合代码的作用
// 原来只是为了聚合代码 不让代码重复 现在就是抽象规格的作用了
// 有参数才变得强大,没参数发现没什么用
例子:
function sum(a,b){
if( a > 10){
console.log( a - b );
}else if( a < 10){
console.log( a + b );
}else{
consol.log(10);
}
}
2.3 实参和形参个数不等(天生不定参数)
我们还可以发现,定义的时候和调用的时候参数个数可以不一样多,不报错。
sum(10); //NaN 因为计算的就是10+undefined = NaN
因为我们只传了一个参数,b就没有传递,b被隐式的var了,所以值undefined。10+undefined就是NaN
sum(10,20,32,23,22,2,4); // 30
只有前两个参数被形参接收了,后面的参数无视了。
2.4. arguments
JS有一个非常强大的东西,就是每一个函数内部,都可以使用一个arguments这个类数组对象。
这个arguments对象,就涵盖了所有实参。
调用函数的时候,比如:
fun(45,436,457,34,23,12);
此时函数内部,arguments就有一个下标,就依次等于上面调用的数:
arguments[0] // 45
arguments[1] // 436
……
arguments[5] //12
如果函数里面有形式参数列表,那么是和arguments同步的:
function fun(a,b){
arguments[0] = 8; //改变了第一个参数的值
alert(a); //8 ,弹出改变后的值
// agruments[0]和a 不是同一个变量,只是函数内部有一个绳子,叫映射规则,就是我变你也的变,你变我也得变
// 实参有几个,arguments就有几个,此时
// 没有arguments[1]
b = 2;
console.log(argumnts[1]);
// 就算此时你把b改为2 也不会往arguments里面加了,
// arguments 出生有几个就是几个
}
fun(45);
arguments的功能,是模拟函数的重载,使得同一个函数,根据参数个数的不同,有不同的作用。
那什么是重载呢?
在Java中,同一个函数名,但是参数个数不一样,视为是两个函数。
也就是说,Java中能够定义两个:
function sum(a,b){
}
function sum(a,b,c){
}
同名的两个function,都是sum函数,但是java中是允许这么做的,因为参数个数不一样,这种现象叫做重载。
JavaScript没有重载的概念:
比如现在我们想设计一个sum函数,如果传进来一个参数,就得到这个数字的加1;如果是2个参数,那么返回两个数字的和。比如
sum(10); //1
sum(3,4); //7
就要通过arguments.length 实际参数的个数,来进行判断:
function sum(a,b){
//如果实际参数的长度是1,说白了,你只传进来一个参数
switch(arguments.length){
case 1:
return ++a;
break;
case 2:
return a + b;
break;
}
}
更牛逼的,我们可以无限参数,设计一个函数sum,能够接受无限参数
sum(3,4,6,5,8,2) //28
获取实参形参个数,判断实参和形参的个数;
function sum(a,b,c,d){
if()
}
用函数算累和
function sum(){
var result = 0;
for(var i = 0; i < arguments.length;i++){
result += arguments[i]
}
console.log(result);
}
sum(1,2,3,4)
这是不是就是不定参的好出吧,系统内置的一些函数都是不定参的.
3. 返回值
3.1 函数的返回值
函数可以通过参数来接收东西,更可以通过return的语句来返回值,“吐出”东西。
既返回也终止
function sum(a,b){
return a + b; //现在这个函数的返回值就是a+b的和
}
console.log(sum(3,8)); //sum没有输出功能,就要用console.log输出
//sum(3,8)实际上就成为了一个表达式,需要计算
//计算后就是11,console.log(11);
函数只能有唯一的return,有if语句除外
程序遇见了return,将立即返回结果,返回调用它的地方,而不执行函数内的剩余的语句。
function fun(){
console.log(1);
console.log(2);
return; //返回一个空值
console.log(3); //这行语句不执行,因为函数已经return了
}
fun(); //1, 2
console.log(fun()); //undefined
function myNumber(target){
return +target;
}
var num = myNumber("123");
console.log(typeof num + " " + num);
3.2 函数作为参数使用
函数有一个return的值,那么现在这个函数,实际上就是一个表达式,换句话说这个函数就是一个值。
所以这个函数,可以当做其他函数的参数。
sum(3,sum(4,5)); //12
//输出12,实际上有两次执行了sum函数
//先执行最内层,计算出9,然后 sum(3,9)
//就是12
程序从最内层做到最外层, sum(3,9) 12
函数可以接受很多值,返回一个值,
函数的意义:
在出现大量程序相同的时候,可以封装为一个function,这样只用调用一次,就能执行很多语句。
我们在调用一个函数的时候,不用关心函数内部的实现细节,甚至这个函数是你上网抄的,可以运用。所以这个东西,给我们团队开发带来了好处。
模块化编程,让复杂的逻辑变得简单。
4. 应用函数简化编程
我们现在做一个程序,输出2~100的所有质数。所谓的质数,就是只有1、自己两个约数,没有其他约数。
要把一个复杂的问题,拆分为一个个小问题。
高层的业务,就能使用底层的函数提供的API:
约数个数函数 → 判断质数函数 → 高层业务
//约数个数函数:能够传入一个数字,吐出来它约数的个数
function yueshugeshu(a){
//计算a这个数字约数的个数
var count = 0;
for(var i = 1 ; i <= a ; i++){
if(a % i == 0){
count++;
}
}
return count; //返回这个数字的约数的个数
}
//判断是否是质数,如果一个函数的名字取is
//就暗示了将返回布尔值,要么是true要么是false。是通常做法,不是规定
//接收一个参数m,返回是否是质数t或者f
function isZhishu(m){
if(yueshugeshu(m) == 2){
return true;
}else{
return false;
}
}
//寻找1~100的质数
for(var i = 1 ; i <= 100 ; i++){
if(isZhishu(i)){
console.log(i);
}
}
5. 递归
怎么解出来不重要,这种解题方法一定要知道,这种方式叫递归
就是频繁调用自己,只有一个好处简化代码,除此之外,没有其他好处,
递归是不是实现的块啊,递归是最慢的,特别复杂的程序不能用递归,为什么慢,想想为什么慢
// !n 阶乘
function mul(){
if( n == 1 || n == 0){
return 1;
}
return n * mul(n - 1);
}
递归的规律,先执行的最后执行完
二. 函数表达式
定义函数除了使用function之外,还有一种方法,就是函数表达式。就是将函数声明赋值给一个变量
如果现在这个函数表达式中的function不是匿名的,而是有名字的:
// 命名函数表达式
var haha = function xixi(a,b){
return a + b;
}
// 这种定义方式
console.log(haha.name); // xixi
那么JS表现非常的奇怪,在外部只能用haha()
来调用,xixi()
非引发错误!此时可以console.log()函数名
console.log(haha); // 为函数体
console.log(xixi); // Uncaught ReferenceError: xixi is not defined
所以此时函数可以没有名字,称为“匿名函数”,为了今后能够调用它,我们把这个匿名函数,直接赋值给一个变量。
// 匿名函数表达式
var haha = function(a,b){
return a + b;
}
// 因为比较常用,所有以后我们讲的函数表达式就是指匿名函数表达式,
// 如果要讲命名函数表达式会特殊强调
以后想调用这个函数的时候,就可以直接使用haha变量来调用。
console.log(haha(1,3));
也就是说,JS这个奇怪的特性,给我们提了个醒,定义函数,只能用这两种方法,但是不能杂糅:
第一种,通过函数声明定义函数
function haha(){
}
第二种,通过匿名函数的赋值定义函数
var haha = function(){
}
尽量不要用:
var xixi = function haha(){
}
三. 函数声明的提升(预解析)
JS在执行前,会有一个预解析的过程,把所有的函数声明,都提升到了最最开头,然后再执行第一行语句。
所以,function定义在哪里,都不重要,程序总能找到这个函数。
//先调用
fun(); //可以弹出警告框,因为函数有函数声明头提升的特性
//然后定义
function fun(){
alert("我是函数,我执行了!");
}
不会引发错误,alert能够弹出。
函数声明会被提升,但是函数表达式却不会被提升
函数表达式提升的是变量,变量提升后并不是一个函数,所以在表达式之前执行,会报错,为类型错误,因为不是函数.
// ************************************
//函数表达式不会有提升的
fun(); //报错
var fun = function(){
alert("我是函数,我执行了!");
}
又给我们提了个醒,没有极特殊的理由,都要使用function 关键字来定义函数,而不要使用函数表达式来定义函数.
1.函数优先
aaa(); //现在这个aaa到底是函数,还是变量5呢?
//函数优先,遇见同名标识符,预解析阶段一定把这个标识符给函数
var aaa = 5; //定义一个变量,是5
function aaa(){
alert("我是aaa函数,我执行了");
}
面试很容易考:
foo();
var foo;
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
}
函数优先,现在foo这个标识符冲突了,一个函数叫做foo,一个变量也叫作foo。预解析阶段,如果遇见标识符冲突,这个标识符给函数。
函数声明的提升,是无节操的,强制提升,即使用if语句,也会提升。
四.IIFE
IIFE就是immediately-invoked function expression,即时调用函数表达式
如果一个函数,在定义的时候,我们就想直接调用它,就是一个IIFE。
我们试图在定义函数的后面,直接写圆括号:
function fun(){
alert("哈哈")
}();
控制台报错,这是因为函数是一个函数体,并不是表达式,只有表达式能够用()来执行。
所以就要把function fun(){}“降级”, 从函数体降级为表达式。方法有很多:
+function fun(){
alert("哈哈")
}();
-function fun(){
alert("哈哈")
}();
更通常更常用的:
(function fun(){
alert("哈哈")
})();
用这种方法定义的函数,名字是无效的,其他的地方想调用这个函数
fun();
所以IIFE里面的函数,都是匿名函数:
(function(){
alert("哈哈");
})();
上面就是一个标准的IIFE。
例子:设计一个函数,这个函数接收三个参数,比如sum(4,7,9);返回的是前两个数字大的那个数字,与第三个数字的和。
sum(4,2,3); //7
sum(2,4,3); //7
sum(5,4,3); //8
sum(a,b,c){
return (function(a,b){
return a >= b ? a : b;
})(a,b) + c;
}
红色部分是一个IIFE,本质上是一个表达式,表达式计算之后,就是值,什么值呢?a、b中大的那个数字。
五. 引用类型
我们之前说的,基本类型:number、string、boolean、undefined、null
function fun(){
}
console.log(typeof fun); // funtion