本文详细解析JavaScript中的变量提升与函数提升机制,阐述ES6前仅存在全局和函数作用域的特点。深入探讨闭包的定义、形成条件及其在对象模式中的应用,同时指出滥用闭包导致的内存泄露风险及变量作用域链的关键概念,帮助开发者规避常见陷阱。
变量提升与函数提升示例解析
首先通过一个示例来引入话题:
<script>
console.log(typeof a)//undefined
var a='littlebear';
console.log(a)//littlebear
</script>
<script>
console.log(typeof a)//string
var a=1;
console.log(a)//1
</script>
在第一个script标签中,可以观察到变量a被提升到了代码顶部,而赋值操作a = 'littlebear'则保留在原始位置执行。
在第二个script标签中,之所以没有先打印undefined,是因为变量a此前已经被var声明过,因此var a不会再次执行提升操作。
接着观察另一个示例:
<script>
console.log(a)//function a(){}
var a=1;
console.log(a)//1
function a(){}
console.log(a)//1
</script>
结果显示函数a(){}被提升到了代码的最顶端。这说明了函数声明同样存在提升现象,且其优先级或行为与变量提升有所不同。
变量提升与函数提升机制详解
1. 变量提升
在ES6标准发布之前,JavaScript并不具备块级作用域(即一对花括号{}不构成独立的块级作用域),仅存在全局作用域和函数作用域。变量提升是指将变量的声明语句提升到其所在作用域的最开始处执行。参考上述示例:
console.log(global); // undefined
var global = 'global';
console.log(global); // global
function fn () {
console.log(a); // undefined
var a = 'aaa';
console.log(a); // aaa
}
fn();
之所以出现上述打印结果,根本原因在于JavaScript的变量提升机制。实际上,上述代码在引擎内部的执行顺序如下:
var global; // 变量提升,全局作用域范围内,此时只是声明,并没有赋值
console.log(global); // undefined
global = 'global'; // 此时才赋值
console.log(global); // 打印出global
function fn () {
var a; // 变量提升,函数作用域范围内
console.log(a);
a = 'aaa';
console.log(a);
}
fn();
2. 函数提升
在JavaScript中,创建函数主要有两种方式:函数声明式和函数字面量式。需要注意的是,只有函数声明式才会发生函数提升。例如:
console.log(f1); // function f1() {}
console.log(f2); // undefined
function f1() {}
var f2 = function() {}
之所以产生上述打印结果,是由于JavaScript中的函数提升机制导致代码实际按照以下顺序执行:
function f1() {} // 函数提升,整个代码块提升到文件的最开始
console.log(f1);
console.log(f2);
var f2 = function() {}
闭包定义、作用域与经典用法
3. 什么是闭包
闭包是指有权访问另一个函数作用域中变量的函数。
通俗来说,JavaScript允许在另一个函数内部定义内部函数(即函数定义和函数表达式位于外部函数的函数体内)。这些内部函数可以访问其所在外部函数中声明的所有局部变量、参数以及其它内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,便形成了闭包。
4. 变量的作用域
要深入理解闭包,首先必须掌握变量作用域的概念。
变量的作用域主要包含两种:全局变量和局部变量。
JavaScript语言的一个显著特性是,函数内部可以直接读取全局变量。
内部函数之所以能够访问外部函数的变量,是因为内部函数的作用域链中包含了外部函数的作用域;
也可以这样理解:内部函数的作用范围延伸并覆盖了外部函数的作用范围;
var n=999;
function f1(){
alert(n);
}
f1(); // 999
另一方面,在函数外部无法直接读取函数内部的局部变量。
function f1(){
var n=999;
}
alert(n); // error
这里有一个关键点需要注意:在函数内部声明变量时,必须使用var命令。如果省略var,则实际上是在声明一个全局变量!
function f1(){
n=999;
}
f1();
alert(n); // 999
5. 闭包的写法和用法
var Circle={
"PI":3.14159,
"area":function(r){
return this.PI * r * r;
}
};
alert( Circle.area(1.0) );//3.14159
起初并未意识到,以这种形式编写对象实际上也是一种闭包的应用。回顾来看,这正是闭包的经典用法之一!
闭包使用的注意事项与风险
6. 使用闭包的注意点
1)由于闭包会导致函数中的变量长期保存在内存中,造成较大的内存消耗,因此不能滥用闭包,否则可能引发网页性能问题,在IE浏览器中甚至可能导致内存泄露。解决方法是在退出函数之前,手动将不再使用的局部变量设置为null或将其删除。
2)闭包会在父函数外部改变父函数内部变量的值。因此,如果将父函数当作对象(object)使用,将闭包当作其公用方法(Public Method),将内部变量当作其私有属性(private value),此时务必小心,避免随意修改父函数内部变量的值。