作用域
作用域:变量和代码能够被访问到的区域,即可见性。
分为全局作用域、函数作用域和块级作用域。
全局作用域
不在函数和代码块内声明的变量,都是在全局作用域下。在全局作用域中,var声明的变量会挂在window对象上,let和const不会。
函数作用域
即在函数中声明的变量,只能在函数中访问。
块级作用域
在代码块里用let/const声明的变量,只能在该代码块里访问。
闭包
词法作用域
词法环境:变量在创建时就已经有了作用域,而不是等到被调用的时候才有。Js就是依赖词法环境的。
闭包
闭包是指:内层函数中可以访问外层函数声明的变量。(内层函数访问到外层函数的作用域)
这就是因为当外层函数中的该变量被声明后,它的词法环境就被创建了,内层函数就可以访问到该变量。
闭包原理
函数执行会分成两个阶段(预编译阶段和执行阶段)。
- 预编译阶段:如果发现内部函数使用了外部函数的变量,就会在内存中创建一个“闭包”对象并保存对应的变量值,如果已经创建了闭包则只需要增加对应的属性值即可。
- 执行阶段:执行完成后,函数的执行上下文会被销毁,函数对“闭包”对象的引用也会被销毁,但是其内部函数还持有该“闭包”的引用,所以内部函数可以继续使用“外部函数”中的变量。
闭包引用了函数作用域链的特性,一个函数内部定义的函数会将包含外部函数的活动对象添加到它的作用域链中,函数执行完毕后,其执行作用域链被销毁,但因内部函数的作用域链仍然在引用这个活动对象,所以其活动对象不会被销毁,直到内部函数被销毁后活动对象才会被销毁。
function outside() {
var a = 1;
return function inside1() {
a++;
console.log(a);
}
}
var a = 0;
let increment = outside();
increment(); // 2
console.log(a); // 0
闭包使用场景
- 柯里化函数: 大量含有部分相同的参数的函数调用可以简化成只用传入不同参数。
function Area(width) {
return function(height) {
return width * height;
}
}
let boxWidthTen = Area(10);
let box1 = boxWidthTen(20);
let box2 = boxWidthTen(30);
console.log(box1, box2);
- 模拟私有变量
var makeCounter = function() {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
}
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
console.log(Counter1.value()); /* logs 0 */
Counter1.increment();
Counter1.increment();
console.log(Counter1.value()); /* logs 2 */
Counter1.decrement();
console.log(Counter1.value()); /* logs 1 */
console.log(Counter2.value()); /* logs 0 */
Counter1和Counter2各自维护自己的privateCounter,当改变它的值的时候,改变它所处闭包的词法环境,不会影响另外一个变量。
闭包的缺点
- 可能会导致内存泄漏。
- 降低运行速度。
var,let,const
前面也说了,let和const若是在代码块内声明,则在块外无法访问到。
再来看看其他区别。
变量提升
var声明和函数声明会提到作用域内的最开始。let和const不会
console.log(a); // undefined
var a = 1;
暂时性死区
是let和const没有变量提升带来的问题:在let/const声明变量之前,该变量不可用。
console.log(a); // Uncaught
let a = 1;
重复声明
var可以在同一作用域内重复声明,let和const不可以。
const 只读
const声明的对象具有只读性:这个只读性是单对于对象地址来说的,修改该对象的属性是没有问题的,但不能对该对象重新赋值。