作用域
作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,在JavaScript中变量的作用域有全局作用域和局部作用域。
单纯的JavaScript作用域还是很好理解的,JavaScript没有块级的作用域,只有函数级作用域:变量在声明它们的函数体及其子函数内是可见的。
变量没有在函数内声明或者声明的时候没有带var就是全局变量,拥有全局作用域,window对象的所有属性拥有全局作用域;在代码任何地方都可以访问,函数内部声明并且以var修饰的变量就是局部变量,只能在函数体内使用,函数的参数虽然没有使用var但仍然是局部变量。
var a=3; //全局变量
function fn(b){ //局部变量
c=2; //全局变量
var d=5; //局部变量
function subFn(){
var e=d; //父函数的局部变量对子函数可见
for(var i=0;i<3;i++){
console.write(i);
}
alert(i);//3, 在for循环内声明,循环外function内仍然可见,没有块作用域
}
}
alert(c); //在function内声明但不带var修饰,仍然是全局变量
- 声明提前
声明提前,即JavaScript函数里的所有声明都被提前到函数体的顶部,而变量赋值操作留在原来的位置。
console.log(a);
var a = 1;
console.log(b);
实际上代码如下:
var a; //变量声明提升到函数顶部
console.log(a);
a= 1; //变量初始化依然保留在原来的位置
console.log(b);
作用域链
作用域链是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用。
作用域链是一个只能单向访问的链表,这个链表上的每个节点就是执行上下文的变量对象(代码执行时就是活动对象),单向链表的头部(可被第一个访问的节点)始终都是当前正在被调用执行的函数的变量对象(活动对象),尾部始终是全局活动对象。
var a = 1
function fn1(){
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
var fn = fn1()
fn() //2
在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。
闭包
函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函 数作用域内。
var count = (function() { //定义函数并立即调用
var counter = 0; //函数的自由状态
return function() {return counter++;};
}());
这段代码定义一个立即返回函数,返回值赋值给变量count。这个函数返回另外一个函数,也叫嵌套函数,,既可以访问作用域内的变量又可以访问外部函数变量counter。当外部函数返回之后,其他代码无法访问counter,只有内部函数可以访问。
其实理解了执行环境和作用域链后,闭包翻了变成显而易见的东西,但是也不能滥用闭包,从上面例子可以看出,闭包会使子函数保持其作用域链的所有变量及函数与内存中,内存消耗很大,在使用的时候尽量销毁父函数不再使用的变量。