函数(二)
函数也叫方法,在面向对象的语言里面函数就叫方法
函数的定义
第一种:function 关键字的定义
function 函数名(...参数?){
//函数代码体
}
另一种方式:函数表达式的定义
var 函数名 = function (...参数?){
//函数代码体
}
函数的参数
函数里面的参数分为两种类型
- 形参:定义方法的时候的那个参数名,叫形式参数
- 实参:调用方法的时候的那个参数,它是一个具体的值
之前讲函数的基础的时候,我们提过了一点,形参与实参之间没有必要形成一一对应的关系,所以下面的代码它是对的
function add(a,b){
var sum = a+b;
return sum;
}
var x = add(1,2); //3
var y = add(1,2,4); //3 实参的个数大于形参
var z = add(1); //NaN 参数b没有赋值,就是默认的undefined 实参小于形参
ECMAScript 函数的参数与大多数其他语言中函数的参数有所不同。ECMAScript 函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不会有什么怨言。之所以会这样,原因是 ECMAScript 中的参数在内部是用一个数组来表示的。
arguments实参数组
根据上面的理解,函数的内部会将所有的实参形成一个数组,无论你传递多个数都可以。你如果不传递,这个数组就是空数组,你如果传递了,我就将你的实参放在数组里面。而个数组就是arguments
function method1(a,b){
console.log(a);
console.log(b);
//arguments就是实参数组
console.log(arguments[2]); //arguments[2]就是拿到第3个实参,也就是c
}
method1("a","b","c");
arguments解析
我们刚刚已经看到了怎么去使用arguments
,但是它具体是什么我们还要仔细的学习
function method1(a,b){
console.log(arguments);
console.log(Array.isArray(arguments));
}
method1("a","b","c");
当我们在方法的内部去打印这个Array.isArray(arguments)
的时候,我们发现得到的结果是false
,这就说明arguments
它不是一个数组。这是什么呢?我们现在将arguments
与数组Array
来做一个对比
arguments |
Array |
---|---|
我们将两个东西进行了对比以后我们发现
- 数组Array会有数组的基本操作方法,而
arguments
则没有【不同点】 - 它们都有索引,这们也有属性
length
。而这些索引与length
都是数组才有特征【相同点】
总结:在JavaScript
里面,如果一个对象具备数组的特征(索引与长度),但是不具备数组的方法,这种对象人们就叫类数组。
同时arguments
只能函数内部使用,出了函数就不能使用了
?小技巧根据上在的arguments
的特性,我们可以得出下面的一个应用点
function add() {
//函数的内部,所有的实参最终都会放在arguments
var sum = 0;
for (var i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
//实参与形参没有必要形成一一对应的关系
add(1, 2, 3);
add(1, 2, 3, 4);
add(1, 2);
add(1, 2, 3, 4, 5);
函数的调用
之前讲到函数的时候已经知道了,函数在调用的时候是通过函数名()
来调用,这个时候也请看好,它还有其它的调用方式。这几种调用方式都属于特别的调用方法
var userName = "小夫";
function abc(){
var userName = "biaogege";
console.log("*****************");
console.log("hello world");
console.log("*****************");
}
abc();
上面的函数定义与函数调用都是最基本的调用方式
立即执行函数
当一个函数定义好了以后立即执行,我们就可以把它看成是立即执行函数
var userName = "小夫";
!function abc() {
var userName = "biaogege";
console.log("*****************");
console.log("hello world "+userName);
console.log("*****************");
}();
前面的
!
可以换成+
号,相当于这个函数abc
定义好了以后立即执行了上面的立即执行函数有一个非常大缺点,它不能返回内部的值到外边
函数表达式执行
var abc = function () {
var userName = "biaogege";
console.log("*****************");
console.log("hello world " + userName);
console.log("*****************");
}
//这个时候的abc就是方法名
abc();
上面的代码是一个函数表达式,我们可以通过方法名abc
加上括号去调用这个方法,这个时候的abc还只是一个方法名。现在请看下面的代码
var abc = function () {
var userName = "biaogege";
console.log("*****************");
console.log("hello world " + userName);
console.log("*****************");
return 123;
}();
这个时候函数会立即执行,同时abc已经变成了123
,因为这个时候的abc接收了后面立即执行函数的返回值
当我们在函数表达式的后面添加了一个
()
立即去执行这个函数以后,这段代码就发生了本质的变化
- abc由函数名变成了函数的返回值
- 函数由原来的普通函数变成了匿名函数
闭包调用
function a(){
var userName = "biaogege";
console.log("hello world");
}
a();
上在的代码中,a代表的是函数名,我们只需要将这个函数名加上括号就可以执行了,所以上面的代码其实可以演变成下面的代码
(function (){
var userName = "biaogege";
console.log("hello world");
})();
这个时候我们就可以看到上面的代码已经立即执行了,这各写法,我们叫闭包函数。闭包函数也可以接收参数
(function (a, b) {
console.log(a + b);
})(11, 23);
闭包函数还可以有返回值,并且可以接收这个返回值
var x = (function (a, b) {
var c = a + b;
return c;
})(11, 23);
这个x就是接收了内部返回的值c
?总结:上面的三种函数都是将函数定义好了以后,直接调用,它们的用法最多的就是隔绝内外变量,让代码立即执行
函数的调用者与函数的引用
所谓的函数的调用者指的是这个函数由谁在调用。如张三定义了一个函数abc
与另一个函数def
,如果abc
的方法内部调用了def
,则我们就认为def
的调用者是abc
函数的调用者caller
function a() {
console.log("hello");
console.log(a.caller); //a.caller代表的就是谁在调用a
}
function b() {
console.log("world");
console.log(b.caller); //b.caller代表的就是谁在调用b
}
a();
b();
caller
指的就是当前函数的调用者,如果我们直接在全局的环境下面去调用函数,则caller
的结果是null
function a() {
console.log("hello");
//console.log(a.caller); //a.caller代表的就是谁在调用a
b();
}
function b() {
console.log("world");
console.log(b.caller); //b.caller代表的就是谁在调用b
}
function c(){
console.log("你好");
b();
}
我们分别在a函数里面调用了b函数以及在c函数里面调用了b函数,这个时候
b.caller
就会分别指向a与c
function a() {
console.log("hello");
//console.log(a.caller); //a.caller代表的就是谁在调用a
b();
}
function b() {
//console.log(b.caller); //b.caller代表的就是谁在调用b
//假设这个方法只能被a调用
if (b.caller == a) {
console.log("world");
} else if (b.caller == c) {
console.log("世界");
}
}
function c() {
console.log("你好");
b();
}
函数的引用
“引用”是我们第一次接触到这个词,在
JavaScript
里面,引用可以看成是"指针"
function a() {
console.log("hello");
console.log(arguments.callee === a);
}
在上面调用a这个函数的时候,我们可以看到arguments.callee===a
这个结果是true, 这就说明它们两个是一个东西。这个时候arguments.callee
就称之为函数a的引用(arguments.callee
就是一个指针,它指向了当前函数自己)
var count = 0;
!function() {
count++;
console.log(count);
if(count<10){
arguments.callee();
}
}();
上面的代码就是通过
arguments.callee
实现了匿名函数的递归
匿名函数
匿名函数其实就是没有名子的函数,本身非常简单,但是它可以根其它的东西进行结合,我们之前所学习的闭包函数,立即执行函数以及函数表达式都可以变成匿名函数
回调函数
回调函数是把函数当成参数,传递到另一个函数里面去,它的适用场景主要有两种
- 当一个函数的返回值无法返回的时候
- 当某些功能需要分段进行的时候(熟称流水线操作)
请看下面代码
function abc(x, y, z) {
var a = x + y + z;
var b = x - y - z;
//请将a,b两个值返回到外边去
return [a, b];
}
//如果要将上在的a,b同时返回出去,就目前阶段而言,我们可以使用数组封装在一起,然后直接返回这个数组
var result = abc(22, 10, 3);
console.log("三数之和是"+result[0]);
console.log("三数之差是"+result[1]);
//这个时候的result就是数组,问题的关键点就在于,能不能不使用数组
我们现在就看到,如果要把上面的两个值a,b
返回出来,必须封装成一个数组,这知做是可以,但很麻烦
现在我们通过回调函数的形式去执行
上面的案例就是回调函数的案例,我们现在再看下面的代码
//这个方法只负责买
function buy(callBack) {
var food = "热干面";
var drink = "百事可乐";
food = "变质了的热干面";
drink = "摇了了百事可乐";
callBack(food, drink);
}
//小夫只负责吃
function xiaofu(_food, _drink) {
console.log("小夫在吃" + _food);
console.log("小夫在喝" + _drink);
}
//标哥也只负责吃
function biaogege(_food, _drink) {
console.log("标哥在吃" + _food);
console.log("标哥在喝" + _drink);
}
// buy(xiaofu);
buy(biaogege);
我们可以将上面的回调函数变成下面的代码,与匿名函数结合
//这个方法只负责买
function buy(callBack) {
var food = "热干面";
var drink = "百事可乐";
if (typeof callBack == "function") {
callBack(food, drink);
}
}
buy(function (_food, _drink) {
console.log("小夫在吃" + _food);
console.log("小夫在喝" + _drink);
});
buy(function (_food, _drink) {
console.log("标哥不爱吃" + _food);
console.log("标哥爱喝" + _drink);
});
评论区