递归
var factorial = (function f(n){
if(n<=1){
return 1;
}else{
return n*f(n-1);
}
})
上面这段代码使用具名函数表达式来创建递归函数,保证 factorial
变量即使指向别的对象也能正确执行递归。
( )
中是什么,它就返回什么,若是函数,则其命名在全局是无法访问的,只有赋值给一个变量才能访问( 参考立即执行函数 )
闭包
闭包 是指有权访问另一个函数作用域中变量的函数
作用域链细节:
- 函数 创建 的时候,会创建一个执行环境( 执行上下文 ),以及确定作用域链,创建 变量 对象
- 函数 执行 的时候, 变量 对象转换为 活动 对象,
arguments
对象和函数内声明的 变量 的赋值,初始化 活动 对象 - 作用域链中,当前函数的 活动 对象 处于链上的最前端,然后依次是上一级函数的 活动对象,… 最后直到全局执行环境
当函数执行完毕时,函数中的变量都会被销毁,只保留全局作用域的 活动对象,但是由于闭包函数的作用域链上拥有对其链上一系列函数的 活动 对象的引用,所以这些函数的 活动 对象
不会被销毁,会被保留在内存中
闭包与变量
闭包保存的是整个 活动 对象,而不是某个变量
for(var i=0; i<5; i++){
setTimeout(function(){
console.log(i);
},1000*i);
}
这道经典的面试题,其会每秒打印一个 5
,原因就是 其实 定时器里的函数就是一个闭包,它会在同步代码执行完毕之后执行,其作用域链中保存了当前函数环境的 活动对象,其中 i
的值在当前函数执行结束之后变为了 5
,然后定时器闭包函数开始执行,然后就会每秒打印一个 5
想要获得 0, 1, 2, 3, 4
这种打印结果,就得要定时器函数能够记住当前 i
的 值,可以使用闭包解决( 函数的参数都是值传递的 )
for(var i=0; i<5; i++){
setTimeout(function(i){
return function(){
console.log(i);
}
}(i),1000*i);
}
关于 this
this
指向是在函数执行的时候才确定的,所以使用闭包的时候 可能出现问题,只要没有显示的调用者,或者使用 apply, call, bind
指定调用者,则 this
都会指向全局对象
模仿块级作用域
ES6 之前,js 是没有块级作用域的,这意味着在 if..else、for、while
等语句中声明的变量会成为当前函数或者全局的变量,即在这些代码块之外的地方,也能访问到在块中定义的变量
变量提升
JS 会在函数执行的时候将函数声明,变量声明提升到函数的最顶部先执行( 使用 var
声明 ),函数优先,如果遇到相同的名字,则会忽略后一个,但是如果在声明的地方有赋值操作,赋值操作还是会进行,只不过声明被忽略
可以使用匿名函数来模仿块级作用域
(function(){
// 这里的 a 不会被外界访问到
var a = 1
})()
函数表达式后面可以跟圆括号,可以使用圆括号将匿名函数体包裹起来转换成函数表达式
(function(){ }) // => 函数表达式