学了好久的 Javascript 惭愧仍然没有总结完全作用域,今天老夫就来总结一番:
涉及内容:
- 全局作用域
- 函数作用域
- 块级作用域
- 词法作用域
- 动态作用域 动态作用域跟 this 引用机制相关
全局作用域:
作用域,是指变量的生命周期(一个变量在哪些范围内保持一定值)。
全局变量:
生命周期将存在于整个程序之内。
能被程序中任何函数或者方法访问。
在 JavaScript 内默认是可以被修改的。
全局变量,虽然好用,但是是非常可怕的,这是所有程序员公认的事实。
显式声明:
带有关键字 var 的声明;
<script type="text/javascript">
var testValue = 123;
var testFunc = function () { console.log('just test') };
/**---------全局变量会挂载到 window 对象上------------**/
console.log(window.testFunc) // ƒ () { console.log('just test') }
console.log(window.testValue) // 123
</script>
复制代码
其实,我们写的函数如果不经过封装,也会是全局变量,他的生命周期也就是全局作用域;
隐式声明:
不带有声明关键字的变量,JS 会默认帮你声明一个全局变量!!!
<script type="text/javascript">
function foo(value) {
result = value + 1; // 没有用 var 修饰
return result;
};
foo(123); // 124
console.log(window.result); // 124 <= 挂在了 window全局对象上
</script>
复制代码
现在,变量 result
被挂载到 window
对象上了!!!
函数作用域:
函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!!!
function bar() {
var testValue = 'inner';
}
console.log(testValue); // 报错:ReferenceError: testValue is not defined
复制代码
通过 return 访问函数内部变量:
function bar(value) {
var testValue = 'inner';
return testValue + value;
}
console.log(bar('fun')); // "innerfun"
复制代码
函数就像一个工厂,我们输入一些东西,它在内部加工,然后给我们一个加工产物;
通过 闭包 访问函数内部变量:
function bar(value) {
var testValue = 'inner';
var rusult = testValue + value;
function innser() {
return rusult;
};
return innser();
}
console.log(bar('fun')); // "innerfun"
复制代码
关于闭包,我不会在这篇文章过多描述,因为,想要描述闭包,本身需要跟本文章一样的长度;
立即执行函数:
这是个很实用的函数,很多库都用它分离全局作用域,形成一个单独的函数作用域;
<script type="text/javascript">
(function() {
var testValue = 123;
var testFunc = function () { console.log('just test'); };
})();
console.log(window.testValue); // undefined
console.log(window.testFunc); // undefined
</script>
复制代码
它能够自动执行 (function() { //... })()
里面包裹的内容,能够很好地消除全局变量的影响;
块级作用域:
在 ES6 之前,是没有块级作用域的概念的。如果你有 C++ 或者 Java 经验,想必你对块级作用域并不陌生;
for(var i = 0; i < 5; i++) {
// ...
}
console.log(i) // 5
复制代码
很明显,用 var 关键字声明的变量,在 for
循环之后仍然被保存这个作用域里;
这可以说明: for() { }
仍然在,全局作用域里,并没有产生像函数作用域一样的封闭效果;
如果想要实现 块级作用域 那么我们需要用 let
关键字声明!!!
for(let i = 0; i < 5; i++) {
// ...
}
console.log(i) // 报错:ReferenceError: i is not defined
复制代码
在 for
循环执行完毕之后 i 变量就被释放了,它已经消失了!!!
同样能形成块级作用域的还有 const
关键字:
if (true) {
const a = 'inner';
}
console.log(a); // 报错:ReferenceError: a is not defined
复制代码
let
和 const
关键字,创建块级作用域的条件是必须有一个 { }
包裹:
{
let a = 'inner';
}
if (true) {
let b = 'inner';
}
var i = 0;
// ......
复制代码
不要小看块级作用域,它能帮你做很多事情,举个栗子:
举一个面试中常见的例子:
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 5 5 5 5 5
}, 200);
};
复制代码
这几乎是作用域的必考题目,你会觉得这种结果很奇怪,但是事实就是这么发生了;
这里的 i 是在全局作用域里面的,只存在 1 个值,等到回调函数执行时,用词法作用域捕获的 i 就只能是 5;
因为这个循环计算的 i 值在回调函数结束之前就已经执行到 5 了;我们应该如何让它恢复正常呢???
解法1:调用函数,创建函数作用域:
for(var i = 0; i < 5; i++) {
abc(i);
};
function abc(i) {
setTimeout(function() {
console.log(i); // 0 1 2 3 4
}, 200);
}
复制代码
这里相当于创建了5个函数作用域来保存,我们的 i 值;
解法2:采用立即执行函数,创建函数作用域;
for(var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 200);
})(i);
};
复制代码
原理同上,只不过换成了自动执行这个函数罢了,这里保存了 5 次 i 的值;
解法3:let
创建块级作用域
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 200);
};
复制代码
词法作用域:
词法作用域是指一个变量的可见性,及其文本表述的模拟值(《JavaScript函数式编程》);
听起来,十分地晦涩,不过将代码拿来分析就非常浅显易懂了;
testValue = 'outer';
function afun() {
var testValue = 'middle';
console.log(testValue); // "middle"
function innerFun() {
var testValue = 'inner';
console.log(testValue); // "inner"
}
return innerFun();
}
afun();
console.log(testValue); // "outer"
复制代码
当我们要使用声明的变量时:JS引擎总会从最近的一个域,向外层域查找;
再举一个一个实际的例子:
var testValue = 'outer';
function foo() {
console.log(testValue); // "outer"
}
function bar() {
var testValue = 'inner';
foo();
}
bar();
复制代码
显然,当 JS 引擎查找这个变量时,发现全局的 testValue 离得更近一些,这恰好和 动态作用域 相反;
如上图所示,下面将讲述与 词法作用域相反的动态作用域;
动态作用域:
在编程中,最容易被低估和滥用的概念就是动态作用域(《JavaScript函数式编程》)。
在 JavaScript 中的仅存的应用动态作用域的地方:this
引用,所以这是一个大坑!!!!!
动态作用域,作用域是基于调用栈的,而不是代码中的作用域嵌套;
作用域嵌套,有词法作用域一样的特性,查找变量时,总是寻找最近的作用域;
同样是,词法作用域,例子2,同一份代码,如果 是动态作用域:
var testValue = 'outer';
function foo() {
console.log(testValue); // "inner"
}
function bar() {
var testValue = 'inner';
foo();
}
bar();
复制代码
当然,JavaScript
除了this
之外,其他,都是根据词法作用域查找!!!
为什么要理解动态作用域呢?因为,这能让你更好地学习 this
引用!!!
参考和鸣谢:
- 《你不知道的JavaScript》
- 《JavaScript函数式编程》
首先,感谢这两本书的作者,我也算是结合自己的实例和理解,好好地总结了一下作用域;