Go to comments

JavaScript 函数

函数


为什么需要函数?

数学里面有函数,数学里面函数代表什么意思?代表公式、代表关系。

数学里面定义一个函数代表谁乘谁、再除几、再加几一些列运算得一个数,等于一个等式,管这个叫函数,叫一个关系的集合。

换句话说抽象起来,数学管这个关系, 把这个东西抽象出来,叫一些列功能的"组合”,一系列功能的"集合",或者说一些列功能的"通式"叫做函数,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的函数体

image.png

控制台打abc输出报错信息"abc is not defined",abc没有定义,abc不是写了吗,为什么会报错?

image.png

这种语法形式(下面红色的部分)叫表达式,表达就是忽略它的名字的

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(,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返回给我们以便于后续利用,一般用变量接收一个个函数的返回值



Leave a comment 0 Comments.

Leave a Reply

换一张