什么是作用域呢
作用域其实是一套规则,这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找。换一种朴素的说法就是,一个变量阔以被查找并使用的范围。
作用域被分为词法作用域以及动态作用域,而在 javascript 当中只存在词法作用域,这也是我们主要讨论的。
javascript的作用域
我们已经清楚javascript 的作用域为词法作用域,而词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将 变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变。也即是当变量声明的时候,它的作用域就被确定了。
var a = "1"
function foo (){
var b = "2"
console.log(a)
console.log(b)
foo1()
}
function foo1(){
console.log(b)
}
console.log(a) // 1
//console.log(b) // ReferenceError: b is not defined
foo()// 1 2 ReferenceError: b is not defined
一个变量的作用域在其被声明的时候,就已经确定了。因此 foo()
被执行的时候,就会报错ReferenceError: b is not defined
。即该变量在当前作用域其父作用域中未被声明。
在 javascript 中的作用域阔以被大致的分为:
- 全局作用域
- 函数作用域
- 块级作用域
- eval()
全局作用域:
代码在程序的任何地方都能被访问,window 对象的内置属性都拥有全局作用域。
函数作用域:
在固定的代码片段才能被访问。
块级作用域
块级作用域在 es6 之后所引入的一个概念。我们阔以看下面的一个例子:
function foo (){
if(true){
var a ="2"
let b = "3"
}
console.log(a)
console.log(b)
}
foo() //2 ReferenceError: b is not defined
我们阔以 foo 函数报错了。 这是因为在 es6 中 使用let 和 const 定义的变量会让{}
变成一个块级作用域,在{}
外你引用不到{}
内部 let
所定义的变量,编译器会抛出ReferenceError
。
拓展一下,其实JavaScript的ES3规范中规定try/catch的catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效。
try {
undefined(); // 执行一个非法操作来强制制造一个异常 }
catch (err) {
console.log( err ); // 能够正常执行!
}
console.log( err ); // ReferenceError: err not found
正如你所看到的,err仅存在catch分句内部,当试图从别处引用它时会抛出错误。
eval()
eval(..)
函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
它是阔以通过代码欺骗和假装成书写时(也就是词法期)代码就在那,来实现修改词法作用域环境的。在执行eval(..)之后的代码时,引擎并不“知道”或“在意”前面的代码是以动态形式插入进来,并对词法作用域的环境进行修改的。引擎只会如往常地进行词法作用域查找。
function foo(str, a) {
eval( str ); // 欺骗!
console.log( a, b );
}
var b = 2;
foo( "var b = 3;", 1 ); // 1, 3
eval()
是一个可怕的恶魔,并且会导致执行速度变慢,所以请避免使用它。
作用域链
作用域的特点就是,先在自己的变量范围中查找,如果找不到,就会沿着作用域往上找,一直会找到全局作用域,这种一层层的关系被称为作用域链。
作用域链决定了哪些数据能被函数访问。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
如上图所示,作用域变量查找会通过作用域链的方式一层层的往上找,因此
foo()
执行后,他当前作用域有b
,父级有a
和foo1
,块级作用域中有c
,所以会打印出 1,2,3,执行foo1
,但是foo1
中和其外部作用域(全局作用域)没有b,因此会报错。