JavaScript 函数
函数
定义
函数声明
函数表达式
组成形式
1. 函数名称
2. 参数
形参
实参
3. 返回值
为什么需要函数?
数学里面有函数,数学里面函数代表什么意思?代表公式、代表关系。
数学里面定义一个函数代表谁乘谁、再除几、再加几一些列运算得一个数,等于一个等式,管这个叫函数,叫一个关系的集合。
换句话说抽象起来,数学管这个关系, 把这个东西抽象出来,叫一些列功能的"组合”,一系列功能的"集合",或者说一些列功能的"通式"叫做函数,js里的函数和数学里的函数比较像。
一、函数定义
从函数的诞生开始
程序有可能满足一定条件之后要执行一个功能(一块代码),而且这个条件满足了很多次,或者说任意满足一个条件都执行这个功能,满足那个条件都执行这个功能(下面这三条语句)。
if(1 > 0){ document.write('a'); document.write('b'); document.write('c'); } if(2 > 0){ document.write('a'); document.write('b'); document.write('c'); } if(3 > 0){ document.write('a'); document.write('b'); document.write('c'); }
上面面代码的重复程度非常高,就像CSS写属性一样,第一个元素宽高都是100px绿色,第二个元素宽高100px是红色,第三个元素宽高100px是其它颜色,宽高100px都是重复的,CSS里就用分组选择器给提出来。
js里面这种重复也是不能容忍的,我们管这种重复叫耦合,耦合翻译一下叫重复、冗余,这种耦合代码叫低效代码,编程要讲究一个原则 高内聚、弱耦合 ,什么意思?
意思是把相同功能的代码抽取出来,放到一个黑匣子里面。每次用的时候调用这个黑匣子就好,不用每次都自己写,跟CSS的分组选择器差不多,js里面也存在一个能封装成黑匣子的东西,我们就管他叫做函数。
下面写一个函数的样子,function 就是一个关键字,跟这个var 没有什么太本质的区别,然后后面跟着一个函数名test,函数的样子就是加括号加大括号,这就代表一个函数了。
function test(){ }
这个函数能干什么呢?
把三条语句的功能写到函数里面,想要执行这三条语句功能的时候,直接写函数名后边加上一个括号就可以执行 test()
function test(){ document.write('a'); document.write('b'); document.write('c'); } if(1 > 0){ test(); } if(2 > 0){ test(); } if(3 > 0){ test(); }
这样写比之前重复写那三行代码好多了,解决了一个耦合度的问题,当满是在1大于0的时候,2大于0的时候,3大于0的时候就执行这个 test() 功能就可以了,这是一个函数最基本的一个用法叫简化代码。
函数还能干嘛呢?
把代码写到函数里面,执行 test() 之后就让函数里面的四条语句执行了,打印 579
function test(){ var a = 123; var b = 456; var c = a + b; document.write(c); } test(); // 579
也就是说函数里面的代码是等到我们让它执行它才执行,不写这个test()它就是一个正常的函数,函数是干嘛的?
就跟变量一样就是一个框,只不过函数这个框里面装了很多条语句,变量那个框装着数据,函数这个框简单的说装了很多条语句,其实函数也是个引用值。
函数里面装了多条语句,但是我们不让它执行的时候不执行,让它执行的时候它才会把里面的代码执行,怎么让它执行?这么调用test(),而且可以让它执行多次,这是函数的一个基本应用。
1、函数声明
正式开始学习函数,函数分为几种定义形式,就像变量一样变量定义,先得声明变量,函数也得的定义。
定义函数最基本的形式就是先写一个function,这个function就跟var一样是个关键字,然后定义一个变量要加一个变量名,定义一个函数也要加一个函数名(test)。
其实函数和变量没有太大区别,函数就是另一种类型的变量,函数和数组对象差不多都是引用值,它在栈内存存的是地址。
比如下面声明一个函数test,这个test也是一个变量,只不过声明形式不用var,这个test变量里面存的是一个函数体,变量也有栈内存堆内存,栈内存里存地址,堆内存里存内容
function test(){ // 大括号里写的是函数体,也就是代码体…… }
说一下函数名
函数名和变量名命名方式规则基本上差不多,不能以数字开头可以下划线等等…,开发规范要求无论是函数名还是变量名,如果是多个单词拼接必要符合小驼峰原则,小驼峰原则就是第一个单词的首字母小写,往后单词的首字母都大写,比如系统函数 hasOwnProperty()
这是函数第一种定义方式叫"函数声明"
function theFirstName(){ }
然后打印出这个 "theFirstName" 看看是什么?打印出它的函数体 function theFirstName(){ console.log(666); } ,也就是说"theFirstName"指代是这个的函数体,让它去执行才是去执行这个函数。
function theFirstName(){ console.log(666); } console.log(theFirstName); // ƒ theFirstName(){ console.log(666); } document.write(theFirstName); // function theFirstName(){ console.log(666); }
这个和c语言和c++不一样,c语言c++要打印一个指针,输出的是指针里面的地址(包括java),弱类型语言永远都输出不了地址,这种解释性语言永远都不可能输出地址,它有地址但不输出地址,它输出地址指向的那个房间。
这是是第一种方式叫函数声明,函数有两种定义形式,下面是第二种形式叫“函数表达式”
2、函数表达式
第二种函数定义方式"函数表达式"是这样写
var test = function test(){ document.write('a'); } test(); // a
第二种方式“函数表达式”是不是有点像定义一个变量,其实第二种方式还可以延伸出第三种形式,第二种方式后面的的函数名 test ,写和不写没有什么太大的分别,要是不写也行。
匿名函数表达式
所以由第二种方式延伸出第三种方式是匿名的表达式,就是括号后面的函数名不写
var demo = function() { document.write('b'); } test(); // a
第二种方式和延伸出的第三种方式那个好有什么区别呢?下面挨个看一下
先看第一个,后面的函数名abc起不起有什么用呢,是test代表函数体,还是abc代表函数体?
var test = function abc(){ document.write('a'); }
控制台Console执行就相当于js代码块,相当于在程序逻辑的最下面<script>代码块</script>,也就是说在程序执行完之后再执行控制台Console,控制台Console执行的时候一定是在前面的东西都执行完了,Console在控制台看起来非常方便它可以直接输出
控制台直接打test回车输出,test指代的是函数abc的函数体
控制台打abc输出报错信息"abc is not defined",abc没有定义,abc不是写了吗,为什么会报错?
这种语法形式(下面红色的部分)叫表达式,表达就是忽略它的名字的
var test = function abc(){
document.write('a');
}
表达的意思就是这块(上面红色的部分 function abc(){ document.write('a');} )它定义完之后也会变成匿名的,它充当了表达式就不能充当一个正常的函数体了,它不是函数声明所以写abc也没有用。
所以要执行还是要执行test(),也就是说后面的abc没有什么用
var test = function abc(){ document.write('a'); } test(); // a
既然没有用那干脆就用匿名的好一些
var demo = function(){ document.write('b'); } test(); // b
第一种方式test 和 第二种方式demo其实还有点小区别
test上面有一个属性叫name属性,test.name 打印出是abc,也就是说test函数的函数名是后面的abc
var test = function abc(){ document.write('a'); } console.log(test.name); // abc
而demo的函数名字呢?demo.name 输出的是 demo
var demo = function (){ document.write('b'); } console.log(demo.name); // demo
函数都有名字吗?函数都有名字
function test(){ } console.log(test.name); // test
也就是说"函数表达式的第二种方式"和"函数声明"的函数名都是自己,而"函数表达式的第一种方式"函数名是后面的abc
// 函数表达式第一种方式叫 "命名函数表达式" var test = function abc(){ document.write('a'); } // 函数表达式第二种方式 "匿名函数表达式" var demo = function (){ document.write('b'); } // 函数声明 function test(){ }
"函数表达式"的一种定义方式叫"命名函数表达",第二种叫"匿名函数表达式",由于第二种匿名函数表达式非常常用,后来就叫简化成函数表达式了,以后函数表达式都是指匿名函数表达式。
学了两种形式定义函数,第一种定义形式叫函数声明,第二种定义形式叫函数表达式,哪种方法都能定义函数,具体有什么区别,后面的课程会讲到,那是预编译环节的一个区别。
二、函数的组成形式
函数组成形式以"函数声明"来说,必须有函数的关键字function、必须要有函数名、必须有括号、必须有大括号这是组成形式的其一,
其二函数的组成形式里面可有,可没有的一个东西叫参数,这个参数才真正让函数变的神奇了。
参数a、b就相当于隐式的在函数体里面声明了两个没有值的变量a和b
function test(a, b){ // var a; // var b; console.log(a + b); } test(1, 2); // 在函数执行的时候,通过传递参数的形式给这两个变量赋值 // PS: c语言、c++包括java里面test(string a),在js里面不能加var加了就报错了,不需要加
有了参数之后,为什么说这个函数变的神奇了?
现在下面函数sum实现的功能是两个数相加,不看函数执行就看sum这个函数体,相当于把两个数相加的规则给抽象出来了,a和b就跟变量一样,因为每次执行的时候,传的参数都可以不同。
function sum(a ,b){ var c = a + b; document.write(c); } // 有了参数每次运算的结果都不一样,所以它代表功能了 // 参数配合里面的代码,可以组合成无尽种用法可以去用,因为每次传的参数都可以不同,所以我们可以根据不同的参数进行不同的处理 sum(1, 2); // 3 sum(3 ,4); // 7
这个函数就跟数学里面的函数一样了,数学里面的函数为了抽象规则的什么x、y随便代换值。这里的变量就是参数,所以说有了参数之后这个函数真正变成抽象规则了,而不是原来聚合代码的作用了,原来只是聚合代码不让代码重复,这时候还有抽象规则的一个作用,这时候的函数才真正叫函数,有了参数之后函数才真正变的强大。
1、参数
sum(a, b) 里面的参数不代表实际的值,占位的一个东西一样所以叫它“形式参数”,它不是一个数它里面传的值才是真正的数,它可以代表任何的数,它只代表一种形式所以叫"形式参数",
sum(1 ,2) 实际执行时候,传的参数才是实际的值叫“实际参数”,
所以上面的简称行参,下面的简称实参
用参数做一例子:
如果第一个数大于10,就输出第一个数减去第二个数的运算结果,
如果第一个数小于10,就输出第一个数加上第二个数的运算结果,
如果两个数相等于就输出10
function sum(a ,b){ if(a > 10){ document.write(a - b); }else if(a < 10){ document.write(a + b); }else{ document.write(10); } } sum(1, 2); // 3 sum(11, 2);// 9
js参数最强大的一点是它不限制位数,在其它语言里面如果形参写两个,实参必须也要传两个进去,或者说行参写一个实参只传一个不能传多了。
比如java里 public static void fn(int x, ...){} 点点点代表多个参数(叫不定参),在js里天生是不定参随便,形参比实参多可以,实参比形参多没问题。
比如写一个行参a,传三个实参进去了不会报错,不会报错怎么用啊,形参a到底是什么值啊?a找对应的第一个传进来的参数 11,后面的两个参数没地方传就拉倒
function sum(a){ console.log(a); } sum(11, 2 ,3); // 11
那行参多,实参少行不行?形参a、b、c 都有值,d是什么呢?d返回undefined
function sum(a, b, c, d){ // var a,b,c,d; console.log(d); } sum(11, 2 ,3); // undefined
形参就相当于在里面声明了a, b, c, d( var a,b,c,d ), a, b, c被赋值了,d没赋值无所谓返回undefined反正不会报错
js这种形式,有没有什么好处呢?
眼前没什么好处,无论实参怎么往里传,无论行参有没有把实参表示出来,这个实参都有地方去放。
在每一个函数里都有一个隐式的东西(系统创建好了)叫arguments是类数组(类数组,是类似于数组的类数组,理解成数组就行了),arguments叫做实参列表(arguments是数组所以必须要加s,也是这么定义的)
function sum(a){ // arguments -> [11,2,3] 无论形参有没有接收完实参,arguments都会把实参当做数组存起来 console.log(arguments);// 打印类数组arguments,[11, 2, 3, callee... console.log(arguments.length); // 数组有长度返回 3 for(var i = 0; i < arguments.length; i++){ // 有数组有长度就可以拿for循环遍历 console.log(arguments[i]); } } sum(11, 2, 3); // 虽然写了一个形参接收实参,多出的实参2、3能在arguments里找到
实参长度能求 arguments.length ,形参长度能求吗?
函数自己也有一个length属性,这个 sum.length 就代表"形参"的长度
function sum(a, b, c, d){ console.log(sum.length); // 4 } sum(11, 2, 3);
做一个小功能:
当形参大于实参的时候打印形参多了,
当实参大于行参的时候打印实参多了
function sum(a ,b, c){ if(arguments.length > sum.length){ console.log('实参多了'); }else if(arguments.length < sum.length){ console.log('形参多了'); }else{ console.log('相等'); } } // 参数传什么都行,什么类型都无所谓,传undefined都行,只要是变量只要是变量的值,无论什么值类型都行,js不限制类型 sum(1,2,3,4,5,undefined,"abc"); // 实参多了 sum(1,2); // 形参多了 sum(1,2,3); // 相等
下面看不固定形参列表的好处
写一个功能,Excel表格有个sum功能叫求和,我们定义一个函数有个功能,求和任意个数求和,如果定义任意的数求和没法定义形参,因为形参是有限的
现在就要传多少个数就加多少个数,用函数怎么定义这个功能?
实参有数组存起来了,把数组的每一位加起来就可以了
function sum(){ var result = 0; for(var i = 0; i < arguments.length; i++){ result += arguments[i]; } console.log(result); } sum(1,2,3,4,5,6,7,8,9); // 45 sum(1,2,3,4,5,6,7,8,9,10); // 55
不定参比定参强大多了,这个功能只有不定参才能求出来,定参除非传数组当成一位来算,这是不定参的好处
探究一个东西就探究到最深,下面的形参第一位a和arguments[0]是不是相互对应的同一个人?
function sum(a, b){ // arguments里存的是实参列表里面是[1, 2] // 形参a里面存的也是1,a还是一个变量相当于隐式 var a = 1; // 变量能访问也能赋值,那变量a赋值变成2,然后后看看arguments[0]第零位变没变? a = 2; console.log(arguments[0]); // 2 } sum(1, 2);
arguments[0]第一位返回2,跟着变量a变了,a变了arguments也变了
下面让arguments[0]第零位等于3
function sum(a, b){ arguments[0] = 3; // 现在让arguments[0]第零位等于3,看看a变不变? console.log(a); // 3 } sum(1, 2);
这是什么情况?
只有引用值才能两个东西指向同一个地点吧,arguments[0]里面的这一位3,显然不是引用值,这是整数是原始值,怎么可能一个变,另一个跟着变呢?
形参和arguments确实是一个变另外一个跟着变,它俩确实有这样的绑定规则,但是它俩不是同一个变量,系统内部有一条绳叫映射规则,映射规则就是我变你也得跟我变,你变我也跟你变,这叫映射规则但是它俩就是两人。
但是…
掌握一个知识,把知识掌握到透彻显着扎实
有一种特殊情况,实参只传了一个,形参却有两个,然后arguments[1]第一位就没值了,没值了它还映射吗?
function sum(a, b){ b = 2; // 现在让b等于2 console.log(arguments[1]); // undefined } sum(1);
实参列表出生的时候有几个,arguments就有几个,让b等于2也不往arguments里加了,因为arguments根本就没有,这时候b就当一个变量,它跟实参不映射。
因为形参比实参多了一位b,只有它俩完全相等的时候它们才会有映射的规则,不相等的状态形参多了,不对应实参了它俩不映射。
如果实参有两个参数对应上它俩映射了,或者说argument[0]和a现在映射,让a等于2,arguments[0]也变成2
function sum(a, b){ a = 2; console.log(arguments[0]); // 2 b = 2; // b没有实参传进来,b就是一个变量当变量使,它不会对应实参的,b和arguments[1]它俩不会映射的 console.log(arguments[0]); // undefined } sum(1);
还有一个比较关键的东西,函数的结束条件加返回值叫return
2、返回值
程序必须识别一个语句叫return函数才能终止,我们自己要没写程序内部会隐式的加一个return,在逻辑的最后终止这个函数
function sum(a){ // return ; }
这个return有终止函数的意思,sum函数里面手动写个return,肯定打印出a和b停了,不写return也停了
function sum(){ console.log('a'); console.log('b'); return ; } sum(); // a b
现在return写到打印a后面,在打印a后终止了函数,下面的b就不执行了,return下面的语句跟没写一样,所以return不能瞎写
function sum(){ console.log('a'); return; console.log('b'); } sum(); // a
这是return的一点功能终止函数,它还有一点功能才是最常用的叫返回值,return的本意思是把一个值返回到函数外部,什么意思呢?
其实学习js数据类型时已经看过很多函数了,Number()这叫函数的执行,这个函数只不过是系统内部定义的,执行的时候返回一个东西,比如填一个字符串"123"它会返回一个数字类型的123
var num = Number("123"); console.log(num); // 123
我们自己定义的函数也能通过这个return返回,下面我们也实现这样一个功能
function sum(){ return 123; } var num = sum(); console.log(num); // 123
写了返回值,是不是就代表不终止函数了?不是,一个return有两个作用,又有返回值、又有终止函数
我们写一个自己的Number函数,功能是传一个东西尽量返回数字,怎么写?
function myNumber(target){ // 任意类型转成数字用Number函数,如果不用Number函数呢? // 隐式的调用Number,正号"+target"和调用Number没有区别,加号是隐式的调用的是Number return +target; } var num = myNumber('123'); console.log(typeof(num) + " " + num); // number 123
一般情况下这个函处理完一个操作之后,都不是作为打印用的。return返回给我们以便于后续利用,一般用变量接收一个个函数的返回值