Function对象
js 中一切都是对象,函数也是对象,函数名其实就是引用函数定义对象的变量。
1. arguments对象
重载:程序中可定义多个相同函数名,不同参数列表的函数;调用者不必区分每个函数的参数;执行时,程序根据传入的参数个数,自动判断选择哪个函数执行。
js 语法不支持重载!—> 可用arguments对象模拟重载效果。
arguments对象:函数对象内,自动创建的专门接受所有参数值的类数组对象。(类似于数组的对象)
arguments[ i ]:获得传入的下标为 i 的参数值。
arguments.length:获得传入的参数个数。
专门在函数对象不写参数 或 不知道写几个参数时,用来接收所有参数。
即使函数定义了参数变量,arguments对象同样会收到所有参数值。
//用arguments对象模拟重载
function calc( ){
//如果用户传入一个参数
if(arguments.length==1){
alert(arguments[0]*arguments[0]);
}
//如果用户传入两个参数
else if(arguments.length==2){
alert(arguments[0] + arguments[1]);
}
}
//调用函数calc()
calc(4);
calc(4,5);
2. 函数对象本质
(1)创建函数对象
//1.声明方式
//函数名和函数定义都被提前,在调用前后位置定义都行。
function 函数名(参数){
函数体;
return 返回值;
}
//2.函数直接量
//函数名变量的声明会提前,函数定义留在本地,必须定义在函数调用前。
var 函数名 = function(参数){
函数体;
return 返回值;
}
//3.使用new创建函数类型对象
var 函数名 = new Function("a","b",...,"函数体"); //a,b为参数;最后是函数体。
(2)内存中的函数对象
1.创建函数对象时:(同时创建2个对象)
函数对象:函数的定义;
作用域链对象(类似于栈):保存了函数对象可用的变量位置,默认第一项指向window对象(即一个函数可以使用window中的任何变量)。一个函数对象都会指向自己的作用域链对象。
2.调用函数时:(又会创建1个新对象)
活动对象:专门保存(函数的)局部变量的对象
在作用域链对象中追加指向活动对象的引用
3.调用后:(默认仅释放活动对象)
作用域链中活动对象的引用出栈。
注:函数创建调用的过程见电脑本地图片(普通函数-有函数名的)!!
3. 匿名函数(节省内存空间)
定义时不指定函数名的函数。(注:匿名函数调用过程见本地图片-匿名函数!!)
何时使用:两大用途
(1)匿名函数自调:定义完,立刻执行;执行完,立刻释放。(一个对象没人调用它了,就会被垃圾回收机制收走。)
自调:定义在哪,就在哪执行,不提前。
何时使用:只有确定函数只执行一次时使用。
如何自调:下面代码
/*
(function(参数){
函数体;
})(参数值);
*/
<script>
//定义匿名函数:输出"页面开始加载"
(function(){
alert("页面开始加载");
})();
</script>
<body>
<h1>Welcome to Tj University</h1>
</body>
<script>
//定义匿名函数:输出"页面加载完成"
(function(){
alert("页面加载完成");
})();
</script>
(2)匿名函数回调:在将函数作为对象传递给另一个函数,由另一个函数自主决定在需要时调用。
何时使用:只要需要将一个函数对象作为参数传递给其他函数、方法调用时,就是用回调。
如何回调:直接将匿名函数的声明传入另一个函数中。(如下,在sort方法中直接传入一个匿名函数)
arr.sort(function(a,b){returnn a-b;});
4. 闭包(作用:保护局部变量;缺点:占内存)
问题:全局变量 和 局部变量的缺陷
全局变量:(谁都能用,谁都能改)—> 牵一发而动全身 —> 容易全局污染;
局部变量:无法共享,不能长久保存(随函数的回收被回收)。
既可以共享、长久保存(全局能实现)+ 又不污染(局部可实现)——> 闭包
解决:三步(闭包的三个特点)
(1)定义外层函数,封装被保护的局部变量(对外层函数而言)(——>不被污染);
(2)定义内层函数:执行对外层函数的局部变量的操作(对内层函数而言,外层的局部变量—>全局的—>共享、保存)
(3)外层函数返回内层函数对象;并且外层函数被调用,结果被保存在另一个全局变量中。
何时使用:既要反复使用局部变量,又要避免全局污染,就用闭包。
//定义一个取号机函数
//每调用一次,顺序成成一个永不重复的序号
function outer(){
var n=1;
function inner(){
return n++;
}
return inner;
}
var getNum = outer();
//getNum就是 --> function inner(){return n++;}
console.log(getNum()); //输出:1 因为getNum是个函数,所以可以加().
console.log(getNum()); //输出:2
console.log(getNum()); //输出:3
var n=100; //全局污染,在没采用闭包的情况下,会形成干扰。
console.log(getNum()); //输出:4 不受上面一句的干扰。如果没采用闭包,就会受干扰。
console.log(getNum()); //输出:5
注:闭包原理图见电脑本地图片(闭包)!!!(其中图片里,只要有指进来的箭头的对象,就不能释放,没有只进来的箭头的对象才能释放。)
注:一个函数使用全局变量时,如果调用函数时无参数传递,则函数内操作改变的是全局变量;如果函数调用时有参数传递,则函数内部操作修改的是局部变量。
注:一个定义好的函数,如果在别处调用 或 传递时,
(1)只写一个函数名,代表指向函数对象变量;(给你一个榨汁机,自己榨汁)
(2)写函数名 + (),代表函数在此处立刻执行。(榨好的果汁)
判断闭包3特点(满足闭包的条件):
(1)嵌套函数;
(2)内层函数操作了外层函数的局部变量;
(3)外层函数将内层函数返回到外部,被全局变量保存住。
判断闭包执行结果:
(1)外层函数被调用几次(有几个类似getNum()函数),就有几个受保护的局部变量副本;
(2)来自一个闭包的函数被调用几次(即getNum()被调用几次),受保护的局部变量就变化几次;
笔试题:
function outer(){
var arr=[];
var i=0;
for(;i<3;i++){
arr.push(function(){retturn i})
}
return arr;
}
var getFuns = outer();
//外层函数只被调用一次,故只有一个受保护的i; i最后=3.
/*
getFuns-->[function(){retturn i},
function(){retturn i},
function(){retturn i}]
*/
console.log(getFuns[0]()); //因为i=3,且只有一个被保护的i,且i在内层函数中没有变化,故结果都为3.
console.log(getFuns[1]()); //3
console.log(getFuns[2]()); //3
砍断闭包的联系:内外层函数之间,加一层函数的调用,其中加一个参数变量 i 打断内外层函数中变量的联系。
function outer(){
var arr=[];
for(i=0;;i<3;i++){
(function(i){
arr.push(function(){retturn i})
})(i);
}
return arr;
}
var getFuns = outer();
/*
getFuns-->[function(){retturn i},
function(){retturn i},
function(){retturn i}]
*/
console.log(getFuns[0]()); //0
console.log(getFuns[1]()); //1
console.log(getFuns[2]()); //2