函数声明
function dome(name) {
//函数体
}
关于函数声明,它的一个重要特性就是函数声明提升,意思就是在执行代码之前会先读取函数声明。这就意味着可以吧函数声明放在调用它的语句后边。
函数表达式
var dome = function(name) {
//函数体
}
这种形式看起来就像是常规的变量赋值语句,语法赋值内容也叫做匿名函数,所以说和变量一样使用前需要先赋值。
递归
递归函数是在一个函数中通过函数名调用自身的情况下构成的。
arguments.callee
返回正被执行的 Function对象,也就是所指定的Function对象的正文,这有利于匿名函数的递归或者保证函数的封装性。
使用arguments.callee替代函数名的引用,这样无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。
现在已经不推荐使用arguments.callee();因为访问arguments是个很昂贵的操作,因为它是个很大的对象,每次递归调用时都需要重新创建。影响现代浏览器的性能,还会影响闭包。不过可以使用命名函数表达式来达成相同的结果。列如:
//严格模式下使用这个属性也会导致错误
var factorial = (function f(num) {
if(num <= 1){
return 1;
} else {
return num * f(num-1);
}
});
以上代码中即便把赋值给另外一个变量,也不会影响f(),是f依然有效。
闭包
闭包是指有权访问另一个函数作用域中变量的函数。
一般来讲,当函数执行完毕后,局部活动对象就会被销毁,内存中只保存全局作用域。
function outer() {
var a = '变量1'
var inner = function () {
console.info(a)
}
return inner
// inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}
var inner = outer() // 获得inner闭包函数
inner() //"变量1"
当程序执行完 var inner=outer(),其实outer的执行环境并没有被销毁,因为他里面的变量a仍然被inner的函数作用域链所引用,当程序执行完inner(),这时候,inner和outer的执行环境才会被销毁调。
function f1(){
var s = '嘤嘤嘤';
function f2(){
alert(s);
}
return f2;
}
var result=f1();
result(); // 嘤嘤嘤
因为Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。 所以 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们就可以在 f1 外部读取它的内部变量。
function f1(){
var n=999;
var nAdd = ()=> {
n+=1
}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
注意!
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多内存。过度使用会使内存消耗很大造成网页性能问题,在IE中可能导致内存泄露。
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。