js中的作用域,你真的了解吗?

281 阅读5分钟

作用域,听起来非常简单的名词;顾名思义,一个能让变量起作用的范围。在JavaScript中的作用域决定了变量、函数等标识符的可访问性,所以理解js中的作用域对于代码的编写起着至关重要的作用。今天就让我们一起来了解清楚javaScript中的作用域规则。

作用域

在JavaScript主要有三种类型的作用域:全局作用域函数作用域,以及ES6引入的块级作用域。接下来会一一介绍:

全局作用域

全局作用域,是指在程序中的一个特定范围,在这个范围内定义的变量、函数或对象可以被任何部分的代码访问。在js中,全局作用域是最宽泛的作用域层级,也就是说不论在函数内或函数外都能访问到全局作用域内定义的变量。

在作用域中还有一个规则:内部作用域可以访问外部作用域,反之则不行

var a = 1

function foo() {
    var b = 2
    console.log(a);
}

foo();

这是因为作用域的调用栈结构的先进后出特性,导致只能内部作用域可以访问外部作用域,反过来则不行。

lQLPJwL2XdrLY4nNBDjNB4Cw2kAWpx5to_4GOvp8kBIvAA_1920_1080.png

如上图,声明在全局作用域的变量会被最先放进调用栈中,再就是函数foo形成的函数作用域里声明的变量入栈;此时执行函数foo,foo需要打印a,而foo函数体里面并没有声明变量a,根据调用栈结构,会从上往下查找,找到了声明在全局作用域的变量a,然后就会打印a的值。

函数作用域

函数作用域是指在函数内部定义的变量、函数以及其他声明的可访问范围局限于该函数内部。换句话说,函数作用域内的标识符(变量、函数等)只在函数执行期间存在,并且只对该函数及嵌套在该函数内部的任何函数可见。

function foo(a) {
    var b = a * 2

    function bar(c) {
        console.log(a,b,c);
    }
    bar(b*3)
}

foo(2)

每一个函数都能够形成一个属于自身的作用域;并且外部不能访问。

image.png

块级作用域

在JavaScript中,块级作用域是由任何一对花括号 {} 包围的代码区域定义的,{}内用let关键字定义的变量会和{}形成块级作用域;在这个块级作用域里面的变量,外面是访问不到的,如同形成了暂时性死区

let a = 1
{
    // 暂时性死区
    let a = 2
    console.log(a);
}

上面代码的运行结果会输出2。再看:

{ 
let blockVar = "我是块级作用域变量"; 
console.log(blockVar); // 输出 "我是块级作用域变量" 
} 
console.log(blockVar); // 这里会报错,因为blockVar在此处不可见

所以说,在这个块级作用域里面的变量,外面是访问不到的。

词法作用域

词法作用域,又称静态作用域,是编程语言中确定变量、函数等标识符可访问性的规则;在具有词法作用域的语言中,一个变量的词法作用域是由其在源代码中的位置所决定的,并且在编译阶段就已经确定,不会在程序运行时改变。这意味着变量的访问权限取决于变量在代码中的声明位置,而不是在哪个函数或代码块中调用它。简单来说,词法作用域就是变量声明的地方

var a = 1

function foo() {
    var a = 2
}

foo();

console.log(a);

在如上代码中,可以看出,函数foo声明在全局作用域中,所以它的词法作用域是全局;而foo里面声明的变量a,它的词法作用域则在函数体foo内。词法作用域只取决于它声明在的地方

欺骗词法作用域

在js中有两种方法可以欺骗词法作用域,那就是eval()和with(){}。那它们有什么区别呢?让我们一起来看看:

eval()

eval() 函数在JavaScript中是一个非常特殊且强大的函数,它的作用是将传入的字符串作为JavaScript代码进行解析并执行。这意味着通过eval(),你可以在运行时动态地执行任意的JavaScript代码。

function foo(a, str) {
  eval(str);  // var b = 2  eval函数的作用是将一个字符串作为JavaScript代码进行解析和执行
  console.log(a,b);  
}

foo(1,'var b = 2')

在上述代码中,执行函数foo(1,'var b = 2')时,本并不能打印出a,b的值,但由于eval函数的功能,让原本不属于这里的代码,变得好像天生就定义在了这里一样,所以才能够在函数foo内找到b的值并打印。

with(){}

with语句在JavaScript中是一种用于临时改变代码执行上下文(作用域)的语句,它允许你在指定的对象的作用域中执行一系列语句,无需重复引用对象名称。使用with可以在代码块内部直接访问该对象的属性和方法,如同它们是当前作用域内的变量一样。

var obj = {
    a: 1,
    b: 2,
    c: 3
};
// obj.a = 2;
// obj.b = 3;
// obj.c = 4;
with(obj){
    a = 2;
    b = 3;
    c = 4;
}
console.log(obj);

当你想要修改对象中属性的值时,就可以调用with语句,更快速地修改属性值。

不过当你使用with方法修改一个对象中不存在的属性时,这时候with就会把这个不存在的属性给泄露到全局,变成全局变量

function foo(obj) {
    with(obj){
        a = 2;
    }
}
var o1 = {
    a: 1
}
var o2 = {
    b: 1
}
foo(o2)
console.log(a);

image.png

如上代码就会输出a的值为2。

以上就是js中的三大作用域,你明白了吗?

image.png