JS作用域(scope)就是变量访问规则的有效范围。
作用域外,无法引用作用域内的变量;
离开作用域后,作用域的变量的内存空间会被清除,比如执行完函数或者关闭浏览器
作用域与执行上下文是完全不同的两个概念。
1.1 全局作用域
var foo = 'foo'; console.log(window.foo); // => 'foo'
在浏览器环境中声明变量,该变量会默认成为window对象下的属性。
function foo() {
name = "bar"
}
foo();
console.log(window.name) // bar
在函数中,如果不加 var 声明一个变量,那么这个变量会默认被声明为全局变量,如果是严格模式,则会报错。
全局变量会造成命名污染,如果在多处对同一个全局变量进行操作,那么久会覆盖全局变量的定义。同时全局变量数量过多,非常不方便管理。
1.2 函数作用域
假如在函数中定义一个局部变量,那么该变量只可以在该函数作用域中被访问。
function doSomething () { var thing = '吃早餐'; } console.log(thing);
// Uncaught ReferenceError: thing is not defined
嵌套函数作用域:
function outer () {
var thing = '吃早餐';
function inner () {
console.log(thing);
} inner(); }
outer(); // 吃早餐
在外层函数中,嵌套一个内层函数,那么这个内层函数可以向上访问到外层函数中的变量。
既然内层函数可以访问到外层函数的变量,那如果把内层函数return出来会怎样?
function outer () {
var thing = '吃早餐';
function inner () {
console.log(thing);
} return inner; }
var foo = outer(); foo(); // 吃早餐
前面提到,函数执行完后,函数作用域的变量就会被垃圾回收。而这段代码看出当返回了一个访问了外部函数变量的内部函数,最后外部函数的变量得以保存。
这种当变量存在的函数已经执行结束,但扔可以再次被访问到的方式就是“闭包”。
1.3 块级作用域
很多书上都有一句话,javascript没有块级作用域的概念。所谓块级作用域,就是{}包裹的区域。但是在ES6出来以后,这句话并不那么正确了。因为可以用 let 或者 const 声明一个块级作用域的变量或常量。
比如:
for (let i = 0; i < 10; i++) { // ... } console.log(i); // Uncaught ReferenceError: i is not defined
发现这个例子就会和函数作用域中的第一个例子一样的错误提示。因为变量i只可以在 for循环的{ }块级作用域中被访问了。
1.4 词法作用域
词法作用域,也可以叫做静态作用域。意思是无论函数在哪里调用,词法作用域都只在由函数被声明时所处的位置决定。
既然有静态作用域,那么也有动态作用域。
而动态作用域的作用域则是由函数被调用时执行的位置所决定。
var a = 123;
function fn1 () {
console.log(a);
}
function fn2 () {
var a = 456;
fn1();
}
fn2();
// 123
以上代码,最后输出结果 a 的值,来自于 fn1 声明时所在位置访问到的 a 值 123。
所以JS的作用域是静态作用域,也叫词法作用域。
2.1什么是作用域链(scope chain)
在JS引擎中,通过标识符查找标识符的值,会从当前作用域向上查找,直到作用域找到第一个匹配的标识符位置。就是JS的作用域链。
var a = 1;
function fn1 () {
var a = 2;
function fn2 () {
var a = 3;
console.log(a);
}
fn2 ();
}
fn1(); // 3
console.log(a) 语句中,JS在查找 a变量标识符的值的时候,会从 fn2 内部向外部函数查找变量声明,它发现fn2内部就已经有了a变量,那么它就不会继续查找了。那么最终结果也就会打印3了。