在 JavaScript
里面,可能会定义非常多的相同代码或者功能相似的代码,这些代码可能需要大量重复使用。虽然 for
循环语句也能实现一些简单的重复操作,但是比较具有局限性,此时我们就可以使用 JavaScript
中的函数。
函数:就是封装了一段可被重复调用执行的代码块。通过此代码块可以实现大量代码的重复使用。
在 JavaScript
中,可以使用 function
语句来定义一个函数。这种形式是由关键字 function
、函数名
加 一组参数
以及置于大括号中需要执行的 一段代码
构成的。使用 function
语句定义函数的基本语法如下:
function 函数名([参数1, 参数2,……]){
语句
[return 返回值]
}
参数说明:
例如,定义一个不带参数的函数 hello()
,在函数体中输出 Amo 你好~~~
字符串。示例代码如下:
<script>
function hello() {
console.log("Amo 你好~~~");
}
</script>
例如,定义一个用于计算商品金额的函数 account()
,该函数有两个参数,用于指定单价和数量,返回值为计算后的金额。示例代码如下:
<script>
function account(price, number) { // 定义有两个参数的函数
let sum = price * number; // 计算金额
return sum; //返回计算后的金额
}
</script>
常见错误:在同一页面中定义了两个名称相同的函数。例如,下面的代码中定义了两个同名的函数 hello()
。
<script>
function hello() { //定义函数名称为hello
document.write("Amo 你好~~~"); //定义函数体
}
function hello() { //定义同名的函数
alert("Amo 你好~~~"); //定义函数体
}
</script>
上述代码中,由于两个函数的名称相同,第一个函数被第二个函数所覆盖,所以第一个函数不会执行,因此在同一页面中定义的函数名称必须唯一。
函数定义后并不会自动执行,要执行一个函数需要在特定的位置调用函数。调用函数的过程就像是启动一个机器一样,机器本身是不会自动工作的,只有按下相应的开关来调用这个机器,它才会执行相应的操作。调用函数需要创建调用语句,调用语句包含函数名称、参数具体值。
函数调用的语法如下:
函数名(传递给函数的参数1, 传递给函数的参数2, ……);
例如,定义一个函数 outputImage()
,这个函数的功能是在页面中输出一张图片,然后通过调用这个函数实现图片的输出,示例代码如下:
<script>
function outputImage() {
document.write("");
}
outputImage()
</script>
当用户单击某个按钮或某个复选框时都将触发事件,通过编写程序对事件做出反应的行为称为响应事件,在 JavaScript
语言中,将函数与事件相关联就完成了响应事件的过程。比如,按下开关按钮打开电灯就可以看作是一个响应事件的过程,按下开关相当于触发了单击事件,而电灯亮起就相当于执行了相应的函数。
例如,当用户单击某个按钮时执行相应的函数,可以使用如下代码实现该功能。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函数title>
head>
<body>
<input type="button" value="点我试试" onclick="test();">
body>
html>
<script>
function test() {
alert("Amo 好帅呀~~~");
}
script>
在上述代码中可以看出,首先定义一个名为 test()
的函数,函数体比较简单,使用 alert()
语句输出一个字符串,最后在按钮 onclick
事件中调用 test()
函数。当用户单击 点我试试
按钮后将弹出相应对话框。运行结果如下图所示。
函数除了可以在响应事件中被调用之外,还可以在链接中被调用,在 标签中的
href
属性中使用 javascript:函数名()
格式来调用函数,当用户单击这个链接时,相关函数将被执行,下面的代码实现了通过链接调用函数。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>函数title>
head>
<body>
<a href="javascript:test();">点我弹弹弹~a>
body>
html>
<script>
function test() {
alert("Amo 好帅呀~~~");
}
script>
<a href="javascript:void(0)">a>
其它方式,示例代码如下:
<a href="#" class="demo">a>
<a href="https://www.baidu.com/" onclick="return false">跳转到百度a>
我们把定义函数时指定的参数称为形式参数,简称 形参
。而把调用函数时实际传递的值称为实际参数,简称 实参
。如果把函数比喻成一台生产的机器,那么,运输原材料的通道就可以看作形参,而实际运输的原材料就可以看作是实参。
在 JavaScript
中定义函数参数的格式如下:
function函数名(形参1,形参2,……){
函数体
}
定义函数时,在函数名后面的圆括号内可以指定一个或多个参数(参数之间用逗号 ,
分隔)。指定参数的作用在于,当调用函数时,可以为被调用的函数传递一个或多个值。如果定义的函数有参数,那么调用该函数的语法格式如下:
函数名(实参1,实参2,……)
通常,在定义函数时使用了多少个形参,在函数调用时也会给出多少个实参,这里需要注意的是,实参之间也必须用逗号 ,
分隔。例如,定义一个带有三个参数的函数,这三个参数用于指定姓名和年龄以及爱好,然后对它们进行输出,代码如下:
<script>
function personInfo(name, age, hobby) { //定义含有三个参数的函数
alert(`我的英文名是${name},今年${age}岁,爱好${hobby}`);//输出字符串和参数的值
}
personInfo("Amo", 18, "唱歌跳舞"); //调用函数并传递参数
</script>
运行结果如下图所示。
定义一个用于输出图书名称和图书作者的函数,在调用函数时将图书名称和图书作者作为参数进行传递。代码如下:
<script>
function bookInfo(bookName, author) { //定义图书相关的信息函数
alert(`图书名称: ${bookName}\n图书作者: ${author}`);
}
bookInfo("ES6标准入门", "阮一峰");
</script>
运行结果如下图所示。
在 JavaScript
中,形参的默认值是 undefined
。函数形参和实参数量不匹配时:
参数个数 | 说明 |
---|---|
实参个数等于形参个数 | 输出正确结果 |
实参个数多于形参个数 | 只取到形参的个数 |
实参个数小于形参个数 | 多的形参定义为undefined,结果为NaN |
对于函数调用,一方面可以通过参数向函数传递数据,另一方面也可以从函数获取数据,也就是说函数可以返回值。在 JavaScript
的函数中,可以使用 return
语句为函数返回一个值。语法:
return 表达式;
这条语句的作用是结束函数,并把其后的表达式的值作为函数的返回值。例如,定义一个计算两个数的和的函数,并将计算结果作为函数的返回值,代码如下:
<script>
function sum(num1, num2) { //定义含有两个参数的函数
let result = num1 + num2;//获取两个参数的和
return result;//将变量result的值作为函数的返回值
}
let a = 10;
let b = 20;
alert(`${a} + ${b} = ${sum(a, b)}`);
</script>
运行结果如下图所示。
函数返回值可以直接赋给变量或用于表达式中,也就是说函数调用可以出现在表达式中。例如,将上面示例中函数的返回值赋给变量 result
,然后再进行输出,代码如下:
<script>
function sum(num1, num2) { //定义含有两个参数的函数
let result = num1 + num2;//获取两个参数的和
return result;//将变量result的值作为函数的返回值
}
let a = 10;
let b = 20;
let result = sum(a, b); //将函数的返回值赋给变量result
alert(`${a} + ${b} = ${result}`);//输出结果
</script>
在 JavaScript
中允许使用嵌套函数,嵌套函数就是在一个函数的函数体中使用了其他的函数。嵌套函数的使用包括函数的嵌套定义和函数的嵌套调用。函数的 嵌套定义
就是在函数内部再定义其他的函数。例如,在一个函数内部嵌套定义另一个函数的代码如下:
<script>
function outFun() { //定义外部函数
function inFun(x, y) { //定义内部函数
alert(x + y); //输出两个参数的和
}
inFun(20, 50); //调用内部函数并传递参数
}
outFun(); //调用外部函数
</script>
运行结果如下图所示。
在上述代码中定义了一个外部函数 outFun()
,在该函数的内部又嵌套定义了一个函数 inFun()
,它的作用是输出两个参数的和,最后在外部函数中调用了内部函数。虽然在 JavaScript
中允许函数的嵌套定义,但它会使程序的可读性降低,因此,尽量避免使用这种定义嵌套函数的方式。
在 JavaScript
中,允许在一个函数的函数体中对另一个函数进行调用,这就是函数的嵌套调用。例如,在函数 b()
中对函数 a()
进行调用,代码如下:
<script>
function a() { //定义函数a()
alert("ES6标准入门"); //输出字符串
}
function b() { //定义函数b()
a(); //在函数b()中调用函数a()
}
b(); //调用函数b()
</script>
所谓 递归函数
就是函数在自身的函数体内调用自身,使用递归函数时一定要当心,处理不当将会使程序进入死循环,递归函数只在特定的情况下使用,比如处理 阶乘/斐波那契数列等问题
。语法:
function 函数名(参数1){
函数名(参数2);
}
例如,使用递归函数取得 10!
的值,其中 10!=10*9!
,而9!=9*8!
,以此类推,最后 1!=1
,这样的数学公式在 JavaScript
程序中可以很容易使用函数进行描述,可以使用 f(n)
表示 n!
的值,当 1
f(n)=n*f(n-1)
,当 n<=1
时,f(n)=1
。代码如下:
<script>
function f(num) { //定义递归函数
if (num <= 1) { //如果参数num的值小于等于1
return 1; //返回1
} else {
return f(num - 1) * num; //调用递归函数
}
}
alert(`5!的结果为: ${f(5)}`); //调用函数输出5的阶乘
</script>
上述代码运行结果如下图所示。
在定义递归函数时需要两个必要条件:
if(num<=1)
语句,如果满足条件则执行 return 1
语句,不再递归。return f(num-1)*num
语句,用于实现调用递归函数。变量的作用域是指变量在程序中的有效范围,在该范围内可以使用该变量。变量的作用域取决于该变量是哪一种变量。
在 JavaScript
中,变量根据作用域可以分为两种:全局变量
和 局部变量
。全局变量是定义在所有函数之外的变量,作用范围是该变量定义后的所有代码。局部变量是定义在函数体内的变量,只有在该函数中,且该变量定义后的代码中才可以使用这个变量,函数的参数也是局部性的,只在函数内部起作用。如果把函数比作一台机器,那么,在机器外摆放的原材料就相当于全局变量,这些原材料可以为所有机器使用,而机器内部所使用的原材料就相当于局部变量。
例如,下面的程序代码说明了变量的作用域作用不同的有效范围:
<script>
let a = "这是全局变量"; // 该变量在函数外声明,作用于整个脚本
function send() { //定义函数
let b = "这是局部变量"; //该变量在函数内声明,只作用于该函数体
document.write(a + "
");//输出全局变量的值
document.write(b);//输出局部变量的值
}
send();//调用函数
</script>
运行结果为:
上述代码中,局部变量 b
只作用于函数体,如果在函数之外输出局部变量 b
的值将会出现错误。错误代码如下:
<script>
let a = "这是全局变量"; // 该变量在函数外声明,作用于整个脚本
function send() { //定义函数
let b = "这是局部变量"; //该变量在函数内声明,只作用于该函数体
document.write(a + "
");//输出全局变量的值
document.write(b);//输出局部变量的值
}
send();//调用函数
document.write(b);//错误代码,不允许在函数外输出局部变量的值
</script>
运行结果为:
如果在函数体中定义了一个与全局变量同名的局部变量,那么该全局变量在函数体中将不起作用。例如,下面的程序代码将输出局部变量的值:
<script>
let a = "这是全局变量"; //声明一个全局变量a
function send() { //定义函数
let a = "这是局部变量"; //声明一个和全局变量同名的局部变量a
document.write(a); //输出局部变量a的值
}
send(); //调用函数
</script>
运行结果为:
上述代码中,定义了一个和全局变量同名的局部变量 a
,此时在函数中输出变量 a
的值为局部变量的值。全局变量和局部变量的区别:
只要是代码都在一个作用域中,写在函数内部的为局部作用域,未写在任何函数内部即在全局作用域中。如果函数中还有函数,那么在这个作用域中就又可以诞生一个作用域,根据在 内部函数可以访问外部函数变量
的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作 作用域链
。示例代码如下:
<script>
function f1() {
let num = 123;
function f2() {
console.log(num);
}
f2();
}
f1(); //123
</script>
作用域链
:采取 就近原则
的方式来查找变量最终的值。案例如下:
<script>
let a = 1;
function fn1() {
let a = 2;
let b = '22';
fn2();
function fn2() {
let a = 3;
fn3();
function fn3() {
let a = 4;
console.log(a); //a的值 4
console.log(b); //b的值 '22'
}
}
}
fn1();
</script>
JavaScript
代码是由浏览器中的 JavaScript
解析器来执行的。JavaScript
解析器在运行 JavaScript
代码的时候分为两步:预解析和代码执行。解释如下:
JS
代码执行之前,浏览器会默认把带有 var
和 function
声明的变量在内存中进行提前声明或者定义。JS
语句。一句话: 预解析会把变量和函数的声明在代码执行之前执行完成.
预解析也叫做变量、函数提升。变量提升(变量预解析):变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。示例代码如下:
函数提升: 函数的声明会被提升到当前作用域的最上面,但是不会调用函数。示例代码如下:
函数声明代表函数整体,所以函数提升后,函数名代表整个函数,但是函数并没有被调用!函数表达式创建函数,会执行变量提升,此时接收函数的变量名无法正确的调用,示例代码如下:
<script>
fn();
var fn = function () {
document.write("Amo好帅呀~~~~
");
}
</script>
上述代码执行结果如下图所示:
解释:该段代码执行之前,会做变量声明提升,fn
在提升之后的值是undefined
,而 fn
调用是在 fn
被赋值为函数体之前,此时 fn
的值是 undefined
,所以无法正确调用。如下图所示:
第一题,示例代码如下:
<script>
var x = 5;
a();
function a() {
alert(x)
var x = 10;
}
</script>
<script>
a();
function a() {
alert(x)
var x = 10;
}
alert(x)
</script>
<script>
a();
function a() {
alert(a)
}
var a;
alert(a)
</script>
<script>
alert(a)
var a = 10;
alert(a);
function a() {
alert(20);
}
alert(a);
var a = 30;
alert(a);
function a() {
alert(40);
}
alert(a);
</script>
<script>
var a = 10;
alert(a)
a();
function a() {
alert(20)
}
</script>
<script>
a();
var a = function () {
alert(1)
}
a();
function a() {
alert(2)
}
a()
var a = function () {
alert(3)
}
a()
</script>
<script>
var a = 0;
function fn() {
alert(a)
var a = 1;
alert(a)
}
fn();
alert(a)
</script>
在 JavaScript
中提供了一种定义匿名函数的方法,就是在表达式中直接定义函数,它的语法和 function
语句非常相似。其语法格式如下:
var 变量名 = function(参数1,参数2,……) {
函数体
};
这种定义函数的方法不需要指定函数名,把定义的函数赋值给一个变量,后面的程序就可以通过这个变量来调用这个函数,这种定义函数的方法有很好的可读性。例如,在表达式中直接定义一个返回两个数字和的匿名函数,示例代码如下:
<script>
let sum = function (x, y) { //定义匿名函数
return x + y;//返回两个参数的和
};
alert(`10+20=${sum(10, 20)}`);//调用函数并输出结果
</script>
运行结果如下图所示。
在以上代码中定义了一个匿名函数,并把对它的引用存储在变量 sum
中。该函数有两个参数,分别为 x
和 y
。该函数的函数体为 return x+y
,即返回参数 x
与参数 y
的和。
除了在表达式中定义函数之外,还有一种定义匿名函数的方法-----使用 Function()
构造函数来定义函数。这种方式可以动态地创建函数。Function()
构造函数的语法格式如下:
let 变量名 = new Function("参数1","参数2",……"函数体");
使用 Function()
构造函数可以接收一个或多个参数作为函数的参数,也可以一个参数也不使用。Function()
构造函数的最后一个参数为函数体的内容。Function()
构造函数中的所有参数和函数体都必须是 字符串类型
,因此一定要用双引号或单引号引起来。
例如:使用 Function()
构造函数定义一个计算两个数字和的函数,代码如下:
<script>
let sum = new Function("x", "y", "alert(x+y);"); // 使用Function构造函数定义函数
sum(10, 20); // 调用函数
</script>
运行结果如下图所示。
上述代码中,sum
并不是一个函数名,而是一个指向函数的变量,因此,使用 Function()
构造函数创建的函数也是匿名函数。在创建的这个构造函数中有两个参数,分别为 x
和 y
。该函数的函数体为 alert(x+y)
,即输出 x
与 y
的和。
当不确定有多少个参数传递的时候,可以用 arguments
来获取。JavaScript
中,arguments
实际上它是当前函数的一个内置对象。所有函数都内置了一个 arguments
对象,arguments
对象中存储了传递的所有实参。arguments
展示形式是一个伪数组,因此可以进行遍历。伪数组具有以下特点:
length
属性push
, pop
等方法注意:在函数内部使用该对象,用此对象获取函数调用时传的实参。例如,求任意个数数字之和,示例代码如下:
<script>
function getSum() {
// console.log(arguments.length);
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
let [a, b, c, d] = [1, 2, 3, 4];
let [x, y, z] = [8, 9, 10];
console.log(getSum(a, b, c, d)); // 10
console.log(getSum(x, y, z)); // 27
</script>
ES6
之前,不能直接为函数的参数指定默认值,只能采用变通的方法。示例代码如下:
<script>
function f(x, y) {
y = y || "Cool";
console.log(x, y);
}
f("Amo");
f("Amo", "So Cool~~~");
f("Amo", '');
</script>
上面代码检查函数 f
的参数 y
有没有赋值,如果没有,则指定默认值为 Cool
。这种写法的缺点在于,如果参数 y
赋值了,但是对应的布尔值为 false
,则该赋值不起作用。就像上面代码的最后一行,参数 y
等于空字符,结果被改为默认值。ES6
允许为函数的参数设置默认值,即直接写在参数定义的后面。示例代码如下:
<script>
function f(x, y = "World") {
console.log(x, y);
}
f("Amo");
f("Amo", "So Cool~~~");
f("Amo", '');
</script>
ES6
引入 rest
参数(形式为 ...
变量名),用于获取函数的多余参数,这样就不需要使用 arguments
对象了。rest
参数搭配的变量是一个数组,该变量将多余的参数放入数组中。示例代码如下:
<script>
function add(...values) {
let sum = 0;
for (let val of values) {
sum += val;
}
return sum;
}
console.log(add(2, 5, 3)); // 10
</script>
上面代码的 add
函数是一个求和函数,利用 rest
参数,可以向该函数传入任意数目的参数。注意,rest
参数之后不能再有其他参数(即只能是最后一个参数
),否则会报错。示例代码如下:
ES6
允许使用 箭头=>
定义函数。示例代码如下:
<script>
// 传统js函数的写法
let f1 = function (v) {
return v;
}
console.log(f1(3));
// ES6箭头函数
let f2 = v => v;
console.log(f2(4));
</script>
如果箭头函数
不需要参数或需要多个参数,就使用一个圆括号代表参数部分。示例代码如下:
<script>
let f1 = () => 5; //==> let f1 = function(){return 5};
let sum = (num1, num2) => num1 + num2;
//等同于如下代码
/*let sum = function (num1, num2) {
return num1 + num2;
}*/
</script>
如果 箭头函数
的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return
语句返回。示例代码如下:
由于大括号被解释为代码块,所以如果 箭头函数
直接返回一个对象,必须在对象外面加上括号,否则会报错。如下图所示:
箭头函数的一个用处是简化回调函数。例子如下图所示:
箭头函数有几个使用注意点。
this
对象,就是定义时所在的对象,而不是使用时所在的对象。new
命令,否则会抛出一个错误。arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest
参数代替。yield
命令,因此箭头函数不能用作 Generator
函数。上面四点中,第一点尤其值得注意。this
对象的指向是可变的,这些 this
的指向,是当我们调用函数的时候确定的。调用方式的不同决定了 this
的指向不同,如下图所示:
但是在箭头函数中,它是固定的。关于 this
的指向问题,在后续高级语法中在进行详细的讲解。关于其它函数的扩展具体可以参照 ECMAScript6 入门。