作用域是什么 ?
作用域是一套规则,用于确定在何处以及如何查找变量,如果是查找容器本身,会使用LHS查询;如果是查找变量的值,则使用RHS查询;在非严格模式下,LHS查询失败则会隐式创建全局变量,RHS查询失败则会抛出ReferenceError异常。
词法作用域
词法作用域就是定义词法解析阶段的作用域。词法作用域意味着作用域由书写代码时函数声明的位置决定。思考以下代码:
var a = 2;
function foo() {
console.log(a);
}
function bar() {
var a = 3;
foo();
}
// 打印结果显而易见:2
// 印证了上面所说的作用域由函数声明的位置决定。
函数作用域与块作用域
函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用和复用(包括内嵌的作用域)。思考以下代码:
function foo() {
var a = 1;
function bar() {
...
}
}
// 在foo函数的作用域中任何地方都可以找到并使用a这个变量,即使在bar中也可以使用。
// a、bar属于foo函数的作用域气泡,因此如果在全局访问a活着bar则会抛出ReferenceError错误。
块作用域是指例如for、if、{}等内部形成了一个独立的作用域,在外部无法访问。
在ES6还没出来的时候,块级作用域的实现依赖立即执行函数(IIFE):
(function(){
var a = 1;
})();
console.log(a); //ReferenceError
// 这样写的好处可以隐藏和私有化变量,不会污染全局命名
ES6出来后,通过使用let关键字声明变量隐式劫持了所在块的作用域。
for(let i = 0;i < 10; i++) {}
console.log(i); // ReferenceError
// 如果换成var 则打印出 10;
提升
所有的声明(变量和函数)都会被移动到各自作用域的最顶端(只有声明操作会被提升),这个过程被称为提升。let和var关键字声明提升的区别:var 声明和初始化都被提升,let只有声明提升。这里有一个细节,函数的提升是优先于变量的提升(这个细节可以出现在有多个重复声明的代码中)。思考以下代码:
foo(); // 1
var foo;
function foo() {
console.log(1);
}
foo = function() {
console.log(2);
}
// js引擎对于重复声明的标识符,会取第一个忽略后面的。
// 所以编译之后的执行代码如下
function foo() {
console.log(1);
}
foo();
foo = function() {
console.log(2);
}
作用域闭包
何谓闭包?当函数能记住并使用所在的词法作用域,就产生了闭包,即使函数是在当前词法作用域外执行。闭包是老生长谈的问题,也是及其重要的特点。且js代码中处处存在着闭包,我们要学习如何识别和拥抱它。
思考下面的代码:
function foo() {
let a = 123;
function bar() {
console.log(a);
}
return bar;
}
let baz = foo();
baz(); // 123
这就是闭包了,在foo函数执行完毕后,我们期望foo函数内部的作用域已经全部销毁、回收(基于js中存在的垃圾回收机制),但实际上由于bar声明的位置涵盖了foo作用域的闭包,使得该作用域能够一直存活,以供bar在任何时候使用。
当然,无论使用何种方式对函数类型的值进行传递,当函数在别处调用时都可以观察到闭包。
function foo() {
let a = 123;
function baz() {
console.log(a);
}
bar(baz);
}
function bar(fn) {
fn();
}
现在,我们来看一个非常经典的例子:
for(var i = 0; i < 6; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 有js开发经验的小伙伴一眼就能看出答案,会打印6个6
// 如何在不使用ES6中的let,让其依次打印0,1,2,3,4,5呢?
// 这里可以使用闭包解决,为每一个i创立一个词法作用域并且产生闭包
for(var i = 0; i < 6; i++) {
(function (j) {
setTiemout(() => {
console.log(j);
}, 1000)
})(i);
}
练习题
// 做道经典的面试题,看看自己掌握的怎么样?
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n);
}
};
}
let a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
let b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
let c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?