1.函数声明
注意:在同一个作用域,相同名称的函数可以重复声明,但后定义的会覆盖先定义的。
def 函数名(值形参名, 默认值形参名 = 默认值, *可变形参名, **可变关键字形参名):
#函数体
2.lambda表达式
注意:在同一个作用域,相同名称的箭头函数表达式不可以重复定义,否则会报错。
注意:箭头函数没有 prototype
属性,不可以直接用作构造函数,也不可以直接使用 arguments
、super
、new.target
。
没有形参或多个形参时必须使用圆括号 ()
,只有一个值形参时可以省略圆括号 ()
,只有一个默认值形参或者只有一个剩余形参时不可以省略圆括号 ()
。
如果有花括号 {}
,则花括号 {}
内就跟普通函数一样。
如果没有花括号 {}
,则胖箭头 =>
后面就只可以为单个表达式。因为 return
关键字只可以被用在函数体内,此时不可以使用 return
关键字。而且,箭头函数会隐式地将此单个表达式的值返回给此箭头函数的调用者。 另外,如果返回值为一个对象字面量,则需要将此对象字面量放在一对圆括号 ()
中,以避免解释器分不清花括号到底是函数体的花括号还是对象字面量的花括号。
lambda 形参名1, 形参名2, 形参名N : 表达式
3.函数作用域
函数声明、函数表达式、箭头函数表达式的本质都是将函数对象赋给变量或常量,因此,与变量和常量一样,函数的作用域也是块作用域,即外围离函数最近的代码块。全局函数的作用域是定义它们的文件。
3.1全局函数
名称 | 修饰符 |
全局函数 | 参考函数声明、函数表达式、箭头函数表达式。 |
3.2成员方法
名称 | 修饰符 |
静态访问器属性(类) | static get 或 static set |
静态方法(类) | static |
静态初始化块(类) | static |
实例访问器属性(类) | get 或 set |
实例方法(类) | —————— |
构造函数(类) | —————— |
访问器属性(对象字面量) | get 或 set |
方法(对象字面量) | —————— |
3.3局部函数
名称 | 修饰符 |
嵌套函数 | 参考函数声明、函数表达式、箭头函数表达式。 |
4.函数名
函数名是第一次跟函数定义绑定的函数名或变量或常量或属性名。
4.1函数声明的函数名
function f() {}
console.log(f.name); // f
console.log( (function f() {}).name ); // f
函数声明是 Function
类型的实例,函数名就是存储这个实例的变量。
注意:函数声明的函数名是变量,不是常量,因为在赋值后可以通过重新赋值更改其值。
function f() {
console.log(1);
}
f = 2;
f(); // 报错
console.log(f); // 2
4.2函数表达式的函数名
//命名函数表达式
const f = function f2() {};
console.log(f.name); // f2
console.log( (function f2() {}).name ); // f2
//匿名函数表达式
const f = function () {};
console.log(f.name); // f
console.log( (function () {}).name ); // ""(空字符串)
注意:命名函数表达式的函数名 f2
的作用域仅仅为函数体,比如可用于函数递归,但不可以在函数体外使用。
const f = function f2() {
console.log(1);
};
f(); // 1
f2(); // 报错
4.3箭头函数表达式的函数名
//箭头函数表达式
const f = () => {};
console.log(f.name); // f
console.log( (() => {}).name ); // ""(空字符串)
5.函数引用表达式
注意:实际上,函数名可以为返回值为函数定义的任何表达式。
函数名;
6.函数定义作为值
函数定义(函数声明、函数表达式、箭头函数表达式)是 Function
类型的实例,与其它引用类型的值(即对象)一样,函数定义也可以作为值赋给变量或常量,保存为对象的属性或数组的元素,作为函数的实参或返回值,等等。
以下仅仅介绍部分使用场景。
6.1复制值
就跟变量的“复制值”章节一样,通过变量将一个函数对象赋给另一个变量时,此时这两个变量都指向同一个函数对象。
function sum(num1, num2) {
return num1 + num2;
}
console.log(sum(10, 10)); // 20
let anotherSum = sum;
console.log(anotherSum(10, 10)); // 20
6.2回调函数
函数定义可以作为函数的实参和返回值,只要满足其一,我们就可以将这种编程风格称为高阶函数风格(Higher Order Function Style),函数 callSomeFunction()
称为高阶函数(Higher Order Function),函数 add()
称为回调函数(Callback Function)。
//作为实参
function callSomeFunction(someFunction, someArgument) {
return someFunction(someArgument);
}
function add(num) {
return num + 10;
}
console.log(callSomeFunction(add, 10)); // 20
//作为返回值
function callSomeFunction() {
return function (num) {
return num + 10;
};
}
let add = callSomeFunction();
console.log(add(10)); // 20
7.形参分类
注意:调用剩余形参时,剩余形参名的前面无 ...
。
名称 | 修饰符 |
值形参 | —————— |
默认值形参 | —————— |
剩余形参 | ... |
7.1形参作用域
值形参、默认值形参、剩余形参可以想象成与在函数体内按顺序声明变量一样。
值形参、剩余形参近似于已声明未初始化的变量,默认值形参近似于已声明已初始化的变量。
//值形参
function f(a, b) {
console.log(a, b);
}
f(); // undefined undefined
function f() {
let a;
let b;
console.log(a, b);
}
f(); // undefined undefined
//默认值形参
function f(a = 1, b = 2) {
console.log(a, b);
}
f(); // 1 2
function f() {
let a = 1;
let b = 2;
console.log(a, b);
}
f(); // 1 2
因为形参是按顺序声明的,则后定义的默认值形参可以引用先定义的形参,但先定义的默认值形参不可以引用后定义的默认值形参。
//正确
function f(a, b = a + 1) {
console.log(a, b);
}
f(1); // 1 2
//错误
function f(a = b - 1, b = 2) {
console.log(a, b);
}
f(); // 报错
形参的作用域是单向的,函数体可以访问形参,但形参不可以访问函数体。
function f(a = c, b = d) {
let c = 3;
let d = 4;
console.log(a, b);
}
f(); // 报错
8.函数签名
在很多其它编程语言中,函数签名(由函数名、形参类型、形参个数组成)就好比是函数的身份证一样,可以通过比较函数签名来判断是否是同一个函数。
JavaScript 语言的函数隐式地只有一个形参,arguments
就是这个形参。arguments
为一个类似于数组的对象,为了易于理解,我们可以暂且称为 arguments
数组。arguments
数组内的元素为所有传递给函数的实参,函数只有验证传递过来的是不是 arguments
数组的机制,并没有验证 arguments
数组内元素的类型和个数的机制。
函数的显式的形参只是为了方便调用才写出来的,并不是必须写出来的,可以使用类似数组中变量名后跟方括号 []
包含索引来访问 arguments
数组中索引对应的实参。第一个实参是 arguments[0]
,第二个实参是 arguments[1]
,以此类推。
所以,JavaScript 语言的函数没有函数签名,或者说 JavaScript 语言的函数的函数签名只是函数名。
9.实参传递方式
实参的类型无需与默认值形参的类型兼容。
实参的个数可以小于、等于、大于形参的个数,甚至可以没有实参。
实参的传递不仅支持按位置从左往右依次传递方式,还支持按关键字传递方式。
实参的传递只支持按值传递方式(就跟变量的“复制值”章节一样),不支持按引用传递方式。
实参类型 | 按值传递 | 按引用传递 |
原始类型 | 函数内形参的变化不会反应到函数外的实参。 | 不支持 |
引用类型 | 函数内形参的变化会反应到函数外的实参。 | 不支持 |
//按值传递(原始类型)
function addTen(num) {
num += 10;
}
let count = 20;
addTen(count);
console.log(count); // 20
//按值传递(引用类型)
function setName(obj) {
obj.name = "张三";
}
let person = new Object();
setName(person);
console.log(person.name); // 张三
//此示例是为了证明JavaScript只支持按值传递,不支持按引用传递。
function setName(obj) {
obj.name = "张三";
obj = new Object();
obj.name = "赵六";
}
let person = new Object();
setName(person);
console.log(person.name); // 张三
注意:不给值形参传递值,相当于给值形参传递了 undefined
值。不给剩余形参传递值,相当于给剩余形参传递了空数组。这就类似于变量已声明未初始化。
注意:给默认值形参不传递或传递 undefined
值,都会触发默认值形参的默认值。给默认值形参传递 null
值,不会触发默认值形参的默认值。
10.函数返回值
参见 return
语句。
11.函数调用表达式
注意:实际上,函数名可以为返回值为函数定义的任何表达式。
#无实参
函数名()
#有实参
函数名(实参)
#按关键字传递方式
函数名(形参名 = 实参值)
12.函数返回方式
按值返回。
13.函数重载(Overload)
在很多其它编程语言中,可以定义两个同名函数,只要函数签名不同就可以实现函数重载。
但在 JavaScript 语言中,如果定义了两个同名函数,因为 JavaScript 语言的函数没有函数签名,此时会被认为是同一个函数,则后定义的会覆盖先定义的,所以 JavaScript 语言自然而然也就不支持函数重载。
可以只定义一个函数,然后通过检查实参的类型和数量,然后分别执行不同的逻辑来模拟函数重载。
function doAdd() {
if(arguments.length === 1) {
console.log(arguments[0] + 10);
}
if(arguments.length === 2) {
console.log(arguments[0] + arguments[1]);
}
}
doAdd(10); // 20
doAdd(30, 20); // 50
14.函数递归(Recursion)
一个函数定义的函数体内有此函数的函数调用,此时便形成了函数递归(Recursion)。
def tri_recursion(k):
if(k > 0):
result = k + tri_recursion(k - 1)
print(result)
else:
result = 0
return result
print("Recursion Example Results:")
tri_recursion(6)
#输出
Recursion Example Results:
1
3
6
10
15
21
15.闭包(Closure)
我们暂且将定义在函数体内顶级的变量、常量、函数称为成员。
JavaScript 语言允许函数嵌套,嵌套函数可以访问外围函数内定义的所有成员,同理,外围函数内定义的所有成员可以访问嵌套函数,但是外围函数内定义的所有成员不可以访问嵌套函数内定义的所有成员,这为嵌套函数内定义的所有成员提供了一种封装。
形成闭包有两个条件:
- 嵌套函数访问了外围函数内定义的成员。
- 嵌套函数以某种方式在外围函数之外的任何作用域被使用。
const getSomeFunc = function (name) {
const getName = function () {
//嵌套函数访问了外围函数内定义的变量name
console.log(name);
};
return getName;
};
//嵌套函数以某种方式在外围函数之外的任何作用域被使用
//外围函数调用完之后不会被销毁,因为嵌套函数需要使用外围函数内定义的变量name
const getNameFunc = getSomeFunc("张三");
getNameFunc(); // 张三
原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/python/pythonlang/35543.html