函数
JavaScript代码要求高内聚、弱耦合,函数的封装性有助于降低代码的耦合性。
文章目录
1. 定义
函数对任何语言来说都是核心组件,因为它们可以封装语句,然后在任何地方、任何时间执行。ECMAScript中的函数使用function关键字声明,后跟一组参数,然后是函数体。
函数的定义方式有以下几种:
- 函数声明;
- 命名函数表达式;
- 匿名函数表达式。
1.1 函数声明
function theFristName() {
}
函数实际上也是一个变量,函数的声明关键词是function。
函数名规范命名方式是小驼峰命名法,首字母小写,其他相连单词首字母大写。
1.2 函数表达式
//命名函数表达式
var test = function abc() {
}
函数表达式型定义方法,=
后面的代码属于表达式,表达式会忽略其名字。这里的函数名是是abc,但是abc这里并没有什么作用。
相当于
//匿名函数表达式
var test = function () {
}
匿名函数又称为拉姆达函数。
由于用匿名表达式赋值变量的方式比较常见,所以函数表达式定义默认为匿名函数表达式定义。
2. 组成形式
2.1 函数名称
2.2 参数
ECMAScript 函数的参数跟大多数其他语言不同。 ECMAScript 函数既不关心传入的参数个数,也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你可以传一个、三个,甚至一个也不传,解释器都不会报错。主要是因为 ECMAScript 函数的参数在内部表现为一个数组。
2.2.1 形参
函数定义时的形参,相当于在函数体中写了若干var 形参
。
2.2.2 实参
形参和实参的数量可以不一致。
未被传递参数的形参,初始值为undefined。
无论函数实参有没有传递给形参,函数内都构建了一个argument(实参列表)将实参存放在内。
红宝书:在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。
arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数是 arguments[0],第二个参数是 arguments[1])。而要确定传进来多少个参数,可以访问 arguments.length
属性。
若要求形参的参数数量,可以访问 函数名.length
属性。
function sum(a) {
console.log(arguments.length); //3
console.log(sum.length); //1
}
sum(4, 5, 6);
在函数内,arguments[i]和第i+1个形参的值是同步变化的,但是他们在内存中是分开的。
function sum(a, b) {
a = 2; //
argument[0] = 3;
}
sum(1, 2);
但是,若实参数小于形参数,多出的实参是没有映射关系的。
function sum(a, b) {
//arguments [1]
b = 2;
console.log(arguments[1]); //undefined
}
sum(1);
2.3 返回值
return
函数的结束条件加返回值。
函数体中没写return时,程序会自动隐式加上return,以终止函数。
return的作用:
1.终止函数
function sum(a, b){
console.log('a');
return;
console.log('b');
}
sum(1);
此时,函数的执行结果是在控制台只打印出字符串a。
2.返回值
function sum(a, b){
console.log('a');
return 123;
console.log('b');
}
var c = sum(1); //c = 123
function myNumber(target) {
return +target;
}
var num = myNumber("123");
console.log(typeof(num) + " " + num); //number 123
3. 全局变量和局部变量
函数内部定义的变量是局部变量,在此函数以外的部分均不能访问该变量。
在script标签内直接定义的变量为全局变量,在函数内可以访问。
4. 作用域
定义:变量(变量作用域又称为上下文)和函数生效(能被访问)的区域。
每一个JavaScript函数都是一个对象,对象中有些属性我们可以访问,但是有些不可以,这些不能访问的属性仅供JavaScript引擎存取。
[[scope]]
属性就是不可访问的属性之一。
[[scope]]
指我们所说的作用域,其中存储了执行期上下文的集合。
4.1 执行期上下文
在函数执行的前一刻,会创建一个称为执行期上下文的内部对象。
一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它所产生的执行期上下文被销毁。
4.2 作用域链
[[scope]]
中存取的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
在函数中查找变量,从作用域链中找,并从顶端以此向下查找。
5. 递归
例如实现阶乘的函数,为了更符合人的逻辑,可以使用递归的思想编写。
function mul(n) {
if(n == 1 || n == 0) { //出口
return 1;
}
return n * mul(n - 1); //规律
}
实现递归的两个要素:
- 找规律
- 找出口
上例中,阶乘的规律就是n! = n * (n - 1)!
,而出口就是当n = 1时,n! = 1。
若没有出口,return语句将求不出一个确定的值而陷入死循环。
规律一定是写在return后面的。
递归的唯一优点就是使代码更加简洁。递归的运算速度很慢,复杂的程序不能用递归。