1、函数的定义
函数(function),又称为 方法(Method),或过程(procedure)
是一段预定义好,并可以被反复使用的代码块
预定义:事先定义好,不会马上被执行
反复使用:可以被多个地方所应用
代码块:由多条可执行语句所组成的结构(函数体)
其实,函数就是一个封装代码段的对象,函数名只是一个引用函数对象的变量
函数是一个引用类型的对象
2、函数声明
语法:
function 函数名(){
//代码块(函数体)
}
函数调用:
使用 已声明好的 函数
语法:
函数名();
强调:函数只有被调用时,才会执行!
定义函数的第二种方法:
var 函数名=function(参数){函数体;return 返回值}
与第一种对比:
第一种方法,整体(函数名+函数定义)提前
第二种方法,函数定义不会被提前,仅函数名提前
定义函数的第三种方法(不常用):
用new:
var 函数名 = new Function("a","b","return a-b");
强调: 所有形参必须放在""中
3、带参数函数的声明和调用
参数变量:专门接收方法执行所必须的数据的变量
何时使用:如果一个函数,必须提供指定数据,才能正常执行时
需要提供几个数据,就定义几个参数接收
如何定义参数:不需要var,直接在函数名后的括号中定义参数名
多个参数,每个参数名之间用逗号分隔
何时,如何传入参数值:调用时,按照参数定义的个数和顺序传入
为什么使用参数:参数可让方法变的更灵活
语法
function 函数名(参数列表){
参数可以参与运算
}
参数列表:由参数名称组成,多个参数的话,用 , 分隔
声明函数时定义的参数,称之为 "形参"
调用带参数函数
函数名(参数值列表);
参数值列表:由具体数值来组成,多个数值之间,用 , 分隔。调用函数时,传递的参数值,称之为 "实参"
如果传入参数个数不符,不会报错:
个数超了,多出的没用
个数不够,未接到值得参数变量,默认值undefined
4、带返回值的函数声明和调用
返回值:函数运行完成后,带给函数调用者的"一个"数据
语法:
function 函数名(参数列表){
//函数体
return 返回值;
}
调用:
var result = 函数(参数列表);
ex : var result = parseInt("35.5");
return特点:2个:
1、return与返回值之间不能加回车
2、return不能放在其他表达式中间使用,执行完return语句,将跳出函数
如果一个表达式或函数有结果,就可直接当一个值用
function sum(a,b){
return a + b;
}
console.log(sum(3,4)); // 7
==>
console.log(3+4); // 7
5、函数重载overload
什么是函数重载?
相同函数名,不同参数列表的多个函数,在调用时,可自动根据传入参数的不同,调用对应的函数执行。
作用: 减轻调用者的负担
何时: 只要一项任务, 根据不同的参数,执行不同的流程时
问题: js不支持重载! 不允许多个同名函数同时存在,下面的函数会覆盖上面的函数。但可用arguments对象模拟重载效果
解决: arguments对象
什么是arguments: 每个函数中自带的,接收所有传入参数值的类数组对象
什么是类数组对象: 长的像数组的对象!
vs数组: 相同: 1. 下标; 2. .length属性
不同: 类型不同: 类数组对象不能用数组API
arguments Object
数组 Array
类数组对象 to Array:
var arr=Array.prototype.slice.apply/call(arguments);
function calc(){
if(arguments.length==2){
return arguments[0] + arguments[1];
}else{
return arguments[0] * arguments[0];
}
}
console.log(calc(12,23));//35
console.log(calc(12));//144
6、匿名函数
匿名函数是指在函数创建时,不指定函数名的函数
作用: 节约内存(调用前和调用后,内存中不创建任何函数对象)
何时: 只要一个函数仅执行一次时,就必须用匿名函数
如何: 2种:
1、回调函数:
arr.sort(function(a,b){return a-b})
str.replace(/reg/ig,function(kw){return 替换值;})
2、自调: 创建函数后,立刻调用自己!
何时: 如果一段代码,不希望其中的变量造成全局污染时,就要放在匿名函数中自调。
语法: 2种:
+function(){...}()
(function(){...})()
7、作用域和作用域链
1、什么是作用域(scope)
作用域指的是,变量和函数的可用范围,它控制着变量和函数的”可见性”和”生命周期”
JS中,作用域可分为:
1、函数作用域 AO(活动对象): 局部变量
局部变量: 仅函数内可用,不可重复使用
2、全局作用域 window:全局变量
全局变量: 随处可用,可反复使用
缺点:容易造成全局污染
2、局部变量
局部变量离开 函数的声明范围 就无法使用
注意:
在函数内,为没有声明过的变量赋值, 变量会被自动创建在全局-->危险
强烈建议:所有变量使用前,必须用var声明
function test(){
var num = 15;//num 为局部变量,只能在 test()中使用
name = "xiaoli";
console.log(num);
}
console.log(name);//xiaoli
console.log(num);//报错
3、全局变量
注意:声明全局变量时,尽量放在所有的 function 之外,不在要function内省略var 关键字去声明。
4、内存中函数生命周期
执行环境栈:ECS(Execute Contect Stack),保存全局以及每个函数的执行环境的栈结构
执行环境:EC,调用函数时,创建的引用函数资源的对象,窗口一打开,默认ECS中压入一个全局EC,全局EC引用了window对象 VO , window对象中的变量都是全局变量
变量对象:VO,专门存储变量的对象
生命周期:
1、开始执行前:
创建执行环境栈(数组),临时保存正在执行的函数的执行环境
向执行环境栈中压入第一个默认函数main()
创建全局作用域对象window
第一步:
2、定义函数时:
创建函数对象,封装函数定义
声明函数名变量,引用函数对象
函数对象的scope属性引用回创建函数时的作用域
第二步 :
3、调用函数时:
ECS中压入一个新的元素(执行环境)记录新函数的调用
创建一个活动对象,保存本次函数调用用到的局部变量
ECS中的新执行环境元素,引用活动对象
活动对象中的parent属性引用函数的scope指向的父级作用域对象
执行过程中: 优先使用活动对象中的局部变量
局部没有,才延parent向父级作用域找
第三步1:
第三步2:
4、函数调用后:
执行环境栈中本次函数的执行环境出栈
导致活动对象被释放
导致局部变量一同释放
第四步:
5、作用域链
由多级作用域连续引用形成的链式结果
掌管着一切变量的使用顺序: 先在局部找。如果没有,就延作用域链向父级作用域找
8、其他
1、声明提前
JS在正式执行前,会将所有var声明的变量和function声明的函数,预读到所在作用域的顶端。但是,对变量的赋值保留在原位置。
2、按值传递
两变量间赋值或向函数中传递参数时,都是将实参变量值复制一份副本传给方法的形参作为参数
3、函数递归
递归:在函数内又调用了一次自己
递归调用的内层函数,是在外层函数还未结束时就已经开始了,外层额函数的调用,就会被阻塞
缺点:算法复杂度太高,且浪费内存
解决:绝大多数的递归,都可以被循环所替代