知识总结
一、初识函数
函数:可以实现某种功能的程序代码块(重复使用)。
1、定义
function 函数名([参数])
{
函数体语句
}
(1)function是关键字。
(2)‘函数名’是标识符,要符合JavaScript的标识符的定义规则。
(3)‘参数’:函数可以有参数也可以没有参数。无论函数是否有参数,函数名后的圆括号不能省略。
(4)’{ }':表示函数的作用范围,不能省略。
2、调用
函数名([参数])
3、函数分类
(1)根据有无返回值分
A、有返回值函数:在函数中含有 return 语句。通常使用完成某种运算,并将运算结果返回
强调:return作用
a:返回值一个值. b:从当前函数中退出.
😄例:无参函数,有return语句为返回值函数。通常函数进行运算,并将运算结果返回
function getSum(){//没有参数(无参函数)
var sum=0
for(var i=1;i<=100;i++){
sum += i
}
return sum //有return语句为返回值函数。通常函数进行运算,并将运算结果返回
}
B、无返回值函数:在函数中没有return语句。通常函数只是实现某种功能,无需返回任何值
😄例:有参函数,无返回值return函数,通常是为了实现某种功能,无需返回任何值
function showArray(arr){//有参函数
var str=''
for(var i=0;i<arr.length;i++){
for(var j=0;j<arr[i].length;j++){
str += arr[i][j]+'\t'
}
str += '\n'
}
console.log(str) //无返回值函数,通常是为了实现某种功能,无需返回任何值
}
(2)根据函数有无参数分
A、有参函数:表示函数在实现某种运算或实现某种功能时,需要外部参数的参入
☀️①代码段如下:
<script>
function showArray(arr){//arr这是一个形式参数,简称形参,因为在定义时这个函数参数没有实际值,它只是一个占位符
var str=''
for(var i=0;i<arr.length;i++){
for(var j=0;j<arr[i].length;j++){
str += arr[i][j]+'\t'
}
str += '\n'
}
console.log(str) //无返回值函数,通常是为了实现某种功能,无需返回任何值
}
var info=[
[1,2,3],
[4,5,6],
[7,8,9]
]
showArray(info)//这里的info是实在参数,简称实参:是有实际的数据,
//传递过程是实参把值传递给形参(注意:形参发生改变不会影响实参)
</script>
☀️②输出结果为:
注意: 实参将数据传递给形参,传递方向是单向的,即若形参发生了改变不会影响实参
4、 关于函数的参数
形参和实参的个数可以不同
A、当实参数量多于形参数量时,函数正常执行,多余的实参会被忽略
B、当实参数量小于形参数量时,多出来的形参类似于一个已声明未赋值的变量,其值为undefined
😄举例如下:
⭐️①代码段如下:
<script>
function fun(a,c){//函数定义:形参a和c
console.log('形参:a='+a+',c='+c)
}
fun(15,25,35)//当实参数量多于形参时,函数正常执行,多余的实参会被省略
fun(15)//函数调用,当实参数量小于形参时,多的形参值为undefined
</script>
⭐️②输出结果为:
C、arguments的使用:在定义函数时,每个函数都有一个隐含的内置对象arguments,在该对象中保存了函数调用时传递的所有实参。
😄举例如下:
⭐️①代码段如下:
<script>
function fn(){//函数定义,没有形参
console.log(arguments)//输出函数调用时传递的所有实参
console.log(arguments.length)//arguments是一个数组
console.log(arguments[2])//输出数组中索引为2元素,输出传递的第三个实参
}
fn(11,22,33)
</script>
⭐️②输出结果为:
二、函数进阶
1、利用函数求任意数组中的最大值
😄举例如下:
⭐️①代码段如下:
// 利用函数求一组序列中的最大值
function getMax(arr){
var max = arr[0]//假定数组的第一个元素是最大的
for(var i=1;i<arr.length;i++){
if(max<arr[i]){
max=arr[i]
}
}
return max
}
var info=[15,18,23,90,80,30]
console.log('Max=',getMax(info))
⭐️②输出结果为:
2、利用return提前终止函数
😄举例如下:
⭐️①代码段如下:
// 1、找出两个数的最大值
function getMax(num1,num2){
//对参数进行类型检查
if(typeof num1!='number'||num2!='number'){
return NaN //return终止函数,如果return后面没有返回值,就直接终止,如果有返回值就返回它,此例就返回的是NaN
}
return num1>num2 ? num1 : num2//利用条件运算符找到两个数的最大值
}
var m = getMax(12,'80')
console.log(m)
⭐️②输出结果为:
3、利用return返回数组
😄举例如下:
⭐️①代码段如下:
//2、利用return返回数组
function getResult(num1,num2){
return [num1+num2,num1-num2,num1*num2,num1/num2]
}
var a=getResult(5,2)
console.log(a)
⭐️②输出结果为:
4、利用函数对反转数组元素
😄举例如下:
⭐️①代码段如下:
//3、利用函数将数组元素进行逆置反转
function reverse(arr){
var i=0
var j=arr.length-1
while(i<j){
var temp=arr[i]
arr[i]=arr[j]
arr[j]=temp
i++
j--
}
return arr
}
var a=reverse([1,2,3,4,5,6])
console.log(a)
⭐️②输出结果为:
5、函数表达式
函数表达式:将声明的函数赋给一个变量,通过变量完成函数的调用和参数传递
var 变量名=function([参数]){
函数体语句
}
😄举例如下:
⭐️①代码段如下:
//5、函数表达式:将声明的函数赋值给一个变量,通过变量的调用完成函数的调用和参数传递
var sum=function(m,n){//函数表达式:将一个匿名的函数赋给变量,通过变量调用该函数
var s=0
for(var i=m;i<=n;i++){
s += i
}
return s
}
console.log('函数表达式:',sum(1,100))
⭐️②输出结果为:
6、回调函数
回调函数:指的就是一个函数A作为参数传递给一个函数B,然后在B的函数体内调用函数A。 此时,称函数A为回调函数。
😄举例如下:
⭐️①代码段如下:
// 6、回调函数,指的就是一个函数A作为参数传递给一个函数B,然后在B的函数体内调用函数A,函数A为回调函数
function cal(num1,num2,fn){//fn表示函数,是cal的参数
return fn(num1,num2)//调用fn函数:num1和num2作为参数,被调用,所以为实参
}
function add(a,b){
return a+b
}
var t = cal(12,25,add);
console.log(t)
function cal(num1,num2,fn){//fn表示函数,是cal的参数
return fn(num1,num2)//调用fn函数:num1和num2作为参数,被调用,所以为实参
}
// function add(a,b){
// return a+b
// }
var t = cal(12,25,function(a,b){ //function(a,b)是回调函数
return a+b
});
console.log(t)
var k = cal(12,25,function(a,b){ //function(a,b)是回调函数(匿名函数,没有函数名),意思就是将函数作为参数,传送给另一个参数
return a*b
});
console.log(k)
⭐️②输出结果为:
7、函数的递归调用
函数的递归调用:函数自己调用自己
(1)用递归解决问题的前提条件
A、问题可以分解:即可以将复杂问题分解成相对简单的问题
B、分解后得到一个新问题,新问题的解法与原问题的解法相同
C、分解问题时必须有明确的结束条件
(2)递归的过程
第一步:自上而下分解问题
第二步:自下而上回溯得到问题的解
😄举例如下:
⭐️①代码段如下:
//7.函数的递归调用,第一步:自上而下,分解问题;第二步:自下而上,进行回溯
function fun(n){//这个函数用于计算n的阶乘
if(n===1||n===0){//用全等,害怕字符和数字分不清,递归的结束条件
return 1
}else{
return n*fun(n-1)//递归函数:函数自己调用自己,在函数体里调用自己
}
}
console.log(fun(5))
⭐️②输出结果为:
😄举例如下:递归调用示例.定义递归函数,输出斐波那契数列的前二十项
⭐️①代码段如下:
//1 1 2 3 5 8 13...
function fib(n){
if(n===1||n===2){
return 1
}else{
return fib(n-1)+fib(n-2)//从第三项开始,每项等于前两项之和
}
}
var str = ''
for(var i=1;i<=20;i++){
str += fib(i)+'\t'
}
console.log(str)
⭐️②输出结果为:
😄举例如下:用递归函数 计算1+2+3+4+…+100的值
⭐️①代码段如下:
function sum(n){ //形参是一个占位符
if(n===1){
return 1
}else{
return n+sum(n-1)//递归函数:函数自己调用自己,在函数体里调用自己
}
}
console.log('1+2+3+4+...+100=',sum(100))
⭐️②输出结果为:
😄举例如下:定义递归函数打印一个整数的每一位数字
⭐️①代码段如下:
function num(n){
if(n>9){
num(parseInt(n/10))
}
console.log(parseInt(n%10))//顺序流
}
num(1024)
⭐️②输出结果为:
8、立即调用的函数表达式
立即调用的函数表达式:即立即调用匿名函数。
格式:
(function([参数]){ //匿名函数
函数体语句
})(); //立即调用
😄举例如下:
⭐️①代码段如下:
(function(){
var sum=0
for(let i=1;i<=100;i++){
sum+=i
}
console.log('1+2+3+4+...+100=',sum)
})()
⭐️②输出结果为:
😄举例如下:定义一个函数,计算如下数列的前二十项的值 2/1 3/2 5/3…
⭐️①代码段如下:
(function(){
var a=2
var b=1
var sum=0
var temp
var str=''
for(let i=1;i<=20;i++){
str +=(a+'/'+b+'+')
sum+=a/b
temp=b
b=a
a=temp+b
}
str +='='+sum
console.log(str)
})()
⭐️②输出结果为:
三、作用域分类
1、作用域
作用域:作用范围
2、全局变量
(1)在函数外部定义的变量
(2)在函数内部省略var关键字,声明的变量
😄举例如下:
⭐️①代码段如下:
// var sum=0 //全局变量,在函数外部定义,是显式定义,在整个程序中起作用
function getSum(n){
sum=0//全局变量,在函数内部没有使用var声明的变量,属于隐式定义
for(var i=1;i<=n;i++){
sum += i
}
}
// var sum=1 //也是全局变量,在函数外部定义,会将sum的作用域进行提升
getSum(100)
console.log(sum)//在函数外部,访问sum,因为sum是全局变量
⭐️②输出结果为:
3、局部变量
局部变量:在函数内部使用var关键字声明的变量。只在函数内部有效。
4、块级变量
块级变量:在ES6(ECMAScript6)标准中提出的。是用户let声明的变量,只在语句块({})中有效
😄举例如下:
⭐️①代码段如下:
var arr=[1,2,3,4,5,6]
var str = ''
for(var i=0;i<arr.length;i++){
str += arr[i]+'\t'
}
console.log(str)
console.log('i=',i)//i=6会被输出,造成空间浪费,因为var i,i++循环执行完后,i变量空间没有被回收
var arr=[1,2,3,4,5,6]
var str = ''
for(let i=0;i<arr.length;i++){ //let i块级变量
str += arr[i]+'\t'
}
console.log(str)
console.log('i=',i)//i不能被输出,因为块级变量是在语句块执行完后,会被销毁,减少空间内存浪费
⭐️②输出结果为:
5、作用域链
作用域链:当在一个函数内部声明另一个函数时,内层函数只能在外层函数作用域内执行,
在内层函数执行的过程中,若需要引入某个变量,首先会在当前作用域中寻找,
若未找到,则继续向上一层级的作用域中寻找,直到全局作用域,称这种链式的
查询关系为作用域链。
😄举例如下:
⭐️①代码段如下:
var s=0
function test(){
var t=1
var fun =function(){//函数表达式,将匿名函数赋值给一个变量
t++ //作用域链:当一个函数里面声明另一个函数时,内层函数只能在外层函数作用域里执行
s += t
}
fun() //函数调用,这里注意,函数表达式需等执行到该函数后,
//方可执行,不可以提前调用,因为fun未保存对函数的引用,所以需放在后面
return s
}
test()
console.log(s)
⭐️②输出结果为:
四、闭包函数
闭包函数:简称"闭包",指的是有权访问另一个函数作用域内的变量(局部变量)的函数,在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。如果函数A内部有函数B,函数B可以访问函数A的变量,那么函数B就是闭包。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
1、作用
(1) 可以在函数外部读取函数内部的变量(局部变量)
(2) 可以让函数内部局部变量的值始终保存在内存中.
😄举例如下:
⭐️①代码段如下:
function fn(){
var times = 0//局部变量,作用域为fn函数
var c = function(){//函数表达式:是一个闭包函数,它内部访问了fn的局部变量times,把局部变量times值保留下来
return ++times
}
return c;
}
var count =fn()//函数表达式
console.log('第一次调用',count())
console.log('第二次调用',count())
console.log('第三次调用',count())
console.log('第四次调用',count())
⭐️②输出结果为:
一个特点是可以保存外部函数的变量,内部函数保留了对外部函数的活动变量的引用,所以变量不会被释放。这种写法可以用在把一些不经常变动计算起来又比较复杂的值保存起来,节省每次的访问时间。
(3)模仿块级作用域
所谓块级作用域就是指在循环中定义的变量,一旦循环结束,变量也随之销毁,它的作用范围只在这一小块。而在JavaScript中没有这样的块级作用域,由于JavaScript不会告诉你变量是否已经被声明,所以容易造成命名冲突,如果在全局环境定义的变量,就会污染全局环境,因此可以利用闭包的特性来模仿块级作用域。
function X(num) {
(function(){
for(var i = 0; i < num.length; i++){
num++
}
}).call() //声明一个函数立即调用以后,浏览器刷新页面会报错,可以用一个小括号把整段函数包起来。
console.log(i)//undefined
在上面的代码中,闭包就是那个匿名函数,这个闭包可以当函数X内部的活动变量,又能保证自己内部的变量在自执行后直接销毁。这种写法经常用在全局环境中,可以避免添加太多全局变量和全局函数,特别是多人合作开发的时候,可以减少因此产生的命名冲突等,避免污染全局环境。
(4)封装私有变量
我们可以把函数当作一个范围,函数内部的变量就是私有变量,在外部无法引用,但是我们可以通过闭包的特点来访问私有变量。
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName()); // default
person.setName("abruzzi");
print(person.getName()); // abruzzi
2、闭包的优缺点
优点:
1.可以访问到函数内部的局部变量,
2.可以避免全局变量的污染,
3.这些变量的值始终保持在内存中,不会在外层函数调用后被自动清除。
变量始终存在的原因:
虽然函数A已经弹出了调用栈,JS引擎会通过逃逸分析将函数A中的变量存储在堆上,函数B访问的是堆上的变量值。
缺点:
1、对内存消耗有负面影响,因内部函数保存了对外部变量的引用,导致无法被垃圾回收,增大内存使用量,所以使用不当会导致内存泄漏。
2、对处理速度具有负面影响。闭包的层级决定了引用的外部变量在查找时经过的作用域长度。
3、可能获取到意外的值。
如何避免闭包引起的内存泄漏:
在退出函数之前,将不使用的局部变量全部删除,可以使变量赋值为null.
//这段代码会导致内存泄露
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
//解决方法为
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
3、闭包中作用域链的体现
首先要明白作用域链的概念,,在ES5中只存在两种作用域————全局作用域和函数作用域(ES6才有块级作用域),当访问一个变量时,解释器会首先在当前作用域查找标示符,如果没有找到,就去父作用域找,直到找到该变量的标示符或者不在父作用域中,这就是作用域链,值得注意的是,每一个子函数都会拷贝上级的作用域,形成一个作用域的链条。