作用域与作用域链

70 阅读4分钟

作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

作用域是静态的

作用域是静态的

作用域是静态的

全局作用域

在页面的任意的部分都可以访问的到

在全局作用域中无法访问到函数作用域的变量

在全局作用域中有一个全局对象window

创建的变量都会作为window对象的属性保存

创建的函数都会作为window对象的方法保存

所有未定义直接赋值的变量自动声明为拥有全局作用域

function outFun2() {
    variable = "未定义直接赋值的变量";
    var inVariable2 = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量
console.log(inVariable2); //inVariable2 is not defined

函数作用域

调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁

每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的

在函数作用域中可以访问到全局作用域的变量

块级作用域

块语句(大括号“{ }”中间的语句),如 if 和 switch 条件语句或 for 和 while 循环语句,不像函数,它们不会创建一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。

if (true) {
    // 'if' 条件语句块不会创建一个新的作用域
    var name = 'Hammad'; // name 依然在全局作用域中
}
console.log(name); // logs 'Hammad'

var没有块级作用域,有函数作用域

<button>测试1</button>
<button>测试2</button>
<button>测试3</button>

var btns = document.getElementsByTagName('button')
for (var i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log('第' + (i + 1) + '个')
  }
}

点击任意一个按钮,后台都是弹出“第四个”,因为var没有块级作用域,等于说是一个i被定义了四遍,会进行覆盖

let拥有独立的块级作用域

for (let i = 0; i < btns.length; i++) {
  btns[i].onclick = function () {
    console.log('第' + (i + 1) + '个')
  }
}

作用域是静态的,写出这个代码的时候就可以分析出他的结果,而不用管他是如何调用的,这就是静态作用域

作用域链

当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用

如果没有则向上一级作用域中寻找,直到找到全局作用域,如果使用了上级的作用域则形成了闭包

如果全局作用域中依然没有找到,则会报错ReferenceError

在函数中要访问全局变量可以使用window对象

内部作用域能访问的外部,取决于函数定义的位置,和调用无关

作用域链有一个非常重要的特性,那就是作用域中的值是在函数创建的时候,就已经被存储了,是静态的

自由变量

要获取当前作用域没有定义的变量,这成为自由变量

自由变量要到创建函数的那个作用域中取,无论函数将在哪里调用

要到创建这个函数的那个域中取值,这里强调的是“创建”,而不是“调用” ,切记切记,其实这就是所谓的"静态作用域"。

const food = "rice";
const eat = function () {
    console.log(`eat ${food}`);
};
(function () {
    const food = "noodle";
    eat(); // eat rice
})();


const food = "rice";
(function () {
    const food = "noodle";
    const eat = function () {
        console.log(`eat ${food}`);
    };
    eat(); // eat noodle
})();

作用域与执行上下文

JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样。

解释阶段

  • 词法分析
  • 语法分析
  • 作用域规则确定

执行阶段

  • 创建执行上下文
  • 执行函数代码
  • 垃圾回收

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。

执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是:

执行上下文在运行时确定,随时可能改变,作用域在定义时就确定,并且不会改变