函数是一组执行特定功能的语句集合,通常将经常执行的操作封装成函数,便于使用。
定义函数
定义函数通常使用function
关键字,一个函数通常包含以下四部分
- function关键字
- 函数名
- 函数的参数列表
- 被
{}
括起来的函数语句
function person (name, age /* 函数的参数可以有一个,也可以有多个甚至没有,如果有多个参数,每个参数之间用,分隔 */) {
console.log(name);
console.log(age);
}
函数表达式
还可以像下面这样声明一个函数
const f = new function() {
console.log("Hello, world");
}
这种形式声明了一个匿名函数,可以通过f()
的形式调用它。当然,这种方式声明的函数也可以有函数名,例如下面这样
const greeting = new function greet() {
console.log("Hello, world");
}
这样的话我们可以通过greeting()
或者greet()
调用它
调用函数
以上文中定义的person
函数为例,调用该函数的方式如下所示
person();
只需要在函数名后加上(),并且在()
中写入需要的参数,就可以调用该函数。
以函数表达式形式声明的函数可以这样调用,这里以上文中定义的greet
函数为例
// 这两种方式都是可以的
greeting();
greet();
递归调用
函数自己调用自己,被称为递归调用,这是一种类似于循环的调用方式
function factorial(n) {
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
}
以factorial(5)
为例,它实际上相当于这样
factorial(5) {
return 5 * factorial(4) { // return 5 * 4 * 3 * 2 *1;
return 4 * factorial(3) { // return 4 * 3 * 2 * 1;
return 3 * factorial(2) { // return 3 * 2 * 1;
return 2 * factorial(1) { // return 2 * 1;
return 1;
}
}
}
}
}
函数提升
与var关键字声明的变量类似,函数的作用域是可以提升的。即使调用函数的位置未声明该函数,只要在后面定义该函数了,那么是可以使用该函数的
console.log(factorial(5)) // 控制台输出120
// 该函数在调用之后声明
function factorial(n) {
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
}
请注意,函数提升只适用于function
关键字声明的函数,如果您是用函数表达式声明的函数,它不会进行作用域的提升
console.log(f(5)); // Uncaught ReferenceError: can't access lexical declaration 'f' before initialization
const f = function (n) {
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
}
函数的作用域
定义在一个函数内的变量不能在函数为被访问,但一个函数可以访问所有定义在它作用域内的变量和函数。即函数内部定义的函数可以访问外部函数定义的所有函数和变量以及外部函数可以访问的所有函数和变量;全局范围定义的函数可以访问全局范围内定义的函数和变量
var a = 10;
var b = 20;
consol.log(multiply(a * b)); // 输出200
function multiply () { // 该函数是一个全局范围内定义的函数,因此它可以访问全局范围内定义的变量和函数
return a * b;
}
下面是一个函数内部定义的函数访问外部函数的所有函数和变量的例子
var a = 10;
var b = 20;
console.log(f1()()); // 输出200
function f1() {
function f2 () {
return a * b;
}
return f2;
}
在该例子中,f2
是定义在f1
内部的函数,它可以访问f1可以访问的所有函数和变量
闭包
当一个函数嵌套在另一个函数中时,内部函数就会形成闭包。内部函数可以访问外部函数作用域中的所有变量和函数以及外部函数可以访问的所有变量和函数,但外部函数不能访问内部函数的变量的和函数。
function f1(x) {
function f2(y) {
return x + y;
}
return f2;
}
由于函数f2形成一个闭包,因此您可以通过调用函数f1传入两个参数,为函数f1和函数f2同时传入参数,如下所示
f1(5)(3); // 返回8
请注意,由于每一次调用函数f1传入的参数都可能不同,因此每一次调用函数f1都会形成新的闭包。
闭包也具有继承性,例如,函数a包含函数b,函数b包含函数c,那么c可以访问函数b可以访问的所有变量和函数,b又可以访问a可以访问的所有变量和函数,因此c也可以访问a可以访问的所有变量和函数
function a(name) {
function b() {
function c() {
console.log(name);
}
c();
}
b();
}
a("张国强");
name是a的参数,由于b能访问a中的参数name,c能访问b中的参数以及b能访问的所有变量,所以c也能访问a中定义的所有变量和函数
arguments对象
arguments对象是一个类似数组的对象,但请注意,它不是数组。访问arguments对象的数据方式类似于数组
arguments[i];
和数组类似,其索引也是从0开始。可以使用arguments.length
属性判断arguments对象的元素个数。
function myConcat(separator) {
let result = "";
// 请注意,虽然myConcat函数已经有自己的参数separator,但第一个传入的参数,还是会作为arguments对象的一个元素
for (let i = 1; i < arguments.length; i++) {
result += arguments[i] + separator;
}
return result;
}
函数参数
js中的函数参数可以分为普通参数、默认参数、rest参数
普通参数
function(name) {
console.log(name);
}
像上面这个函数,name即该函数的默认参数
默认参数
默认参数是在声明时就已经被初始化的参数
function f(name = "jack") {
console.log(name);
}
在该函数中,name参数就是一个默认参数
rest参数
rest参数类似于Java中的可变参数
function f(...names) {
for (let i = 0; i < names.length; i++) {
console.log(names[i]);
}
}
rest参数允许传入任意数量的实参,其访问形式类似于数组,都是通过参数名[下标]
的形式进行访问,同时也可以使用length
属性确定数组元素的个数
箭头函数
箭头函数是匿名的,没有自己的this、arguments、super或new.target等
一个箭头函数如下所示
const a = ["jack", "lucy", "lubo"];
const b = a.map((s) => s.length);
console.log(b);
上述语句的作用是遍历输出数组a
中每个元素的长度
function Person() {
// this指向的是一个Person对象实例
this.age = 0;
setInterval(function growUp() {
// 这个this跟Person的this不同
this.age++;
}, 1000);
}
const p = new Person();
如果想要访问Person中this指代的那个对象可以这么做
function Person() {
this.age = 0;
setInterval(() => {
this.age++;
}, 1000);
}
const p = new Person();
console.log(p.age);
这个例子中箭头函数的this
跟Perosn()构造器中的this
相同,因为箭头函数没有自己单独的this