作用域,是指变量的生命周期(一个变量在哪些范围内保持一定值)。 JS作用域主要分为
- 全局作用域
- 函数作用域
- 块级作用域
- 词法作用域
- 动态作用域
全局作用域
全局变量:生命周期将存在于整个程序之内,能被程序中任何函数或者方法访问,在JavaScript内默认是可以被修改的。全局变量是BUG出现的重灾区,一定要慎用。
显示声明
var value = 123;
var testFunc = function(){
console.log("hello")
}
全局变量会被挂载到window对象上,另外我们写的函数不经过封装的话也是全局变量,生命周期也是全局作用域。
隐式声明
function test(value){
result = value++;
return result;
}
foo(1); // 2
变量result被隐式地挂载到window对象上,省略了var关键字。
函数作用域
function myfun(){
var test = "hi";
}
console.log(test); //Error
function myfunc2(){
var test = "hi";
return test + ' 掘金';
}
console.log(myfunc2()) // "hi 掘金"
函数内部的变量在函数外部是无法被访问的,只有当函数返回了某个值,我们调用函数才可以间接地访问函数内部的变量,这就是函数作用域。但是有一种特殊情况——闭包,通过闭包,我们可以间接访问函数内部变量,而且该变量还会一直存储在内存中。
function test(value){
var str = "hi";
var res = str + value;
function inner(){
return res;
};
return inner();
}
console.log(test("JS")); //hiJS
我们还可以使用立即执行函数IIFE,IIFE可以被用来分离全局作用域,构造一个封闭的模块。
(function(){
var test = 111;
var testFunc = function(){
console.log("hi");
}
}();
console.log(window.test); // undefined
console.log(window.testFunc); //undefined
IIFE可以自动执行函数的内容,消除全局变量的影响。这样就构造了一个封闭的函数作用域。
块级作用域
块级作用域是很多编译型语言自带的特性,但是JS在ES6之前是不支持这种特性的,这让JS一度非常另类。最经典的一个例子就是
for(var i = 0; i < 5; ++i){
// ...
}
console.log(i) // 5
很显然,{}并没有构造一个封闭的块级作用域,变量i在此处溢出了。如果要实现块级作用域,我们需要使用let关键字。
for (let i = 0; i < 5; ++i){
// ...
}
console.log(i) // error!
这个例子说明了变量i只存在for循环中,当循环完毕后i就被自动回收了,我们无法在外面再次访问。ES6提供了两个构造块级作用域的关键字:const和let。他们创建块级作用域还有一个前提,那就是使用{}来包裹这些变量。
为了解决var变量溢出的问题,我们有三种方法。第一种是调用函数,创建函数作用域,即没遍历一个数据,都调用那个函数,相当于使用了一个封闭的函数作用域。
for(var i = 0; i < 5; i++) {
test(i);
};
function test(i) {
setTimeout(function() {
console.log(i); // 0 1 2 3 4
}, 200);
}
第二种方法使用IIFE构造封闭模块,本质和第一种方法一样,也是构造一个函数作用域,防止var变量溢出。
for(var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 200);
})(i);
};
第三种方法最为简单,就是使用ES6提供的let关键字定义变量,自动创建一个块级作用域,这和前两种方法有本质区别。
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 200);
};
词法作用域
当我们使用声明变量时,JS引擎总会从最临近的域向外层域种查找该变量。这就是词法作用域。
动态作用域
动态作用域非常重要,也非常容易混淆。JS中唯一用到动态作用域的地方是this引用,其他地方都遵循词法作用域的特点。动态作用域的作用域是基于调用栈的,而不是代码中的作用域嵌套。