作用域(Scope)和作用域链,执行上下文

291 阅读4分钟

作用域(Scope)

**定义:**是代码运行中,一些变量,函数,对象的可访问性;也就是作用域决定了变量及其他资源的可见性。

**作用:**作用域是一块独立的区域, 能保证这个区域内的变量,其他区域无法访问。隔离变量,

不同作用域下的变量不会冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域,es6之后引入块级作用域,用let和const可以实现

**全局作用域:**在代码中任何地方都能访问到的对象拥有全局作用域,也就是定义在window对象上面的变量。

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

    function outFun2() { variable = "未定义直接赋值的变量"; var inVariable2 = "内层变量2"; } outFun2(); console.log(variable); //未定义直接赋值的变量 console.log(inVariable2); //inVariable2 is not defined

  • 所有 window 对象的属性拥有全局作用域,如: window.name、window.location、window.top

弊端:全局变量会污染全局命名空间, 容易引起命名冲突。

函数作用域(局部作用域):是指声明在函数内部的变量。局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。

function doSomething(){
    var blogName="浪里行舟";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //错误
innerSay(); //错误

**块级作用域:**通过let和const定义的变量,且被{}包裹的变量, 只能被{}里面的区域所访问。

  • 在一个函数内部
  • 在一个代码块(由一对花括号包裹)内部

let和const不存在变量提升。只会存在定义的代码块中。

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

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

作用域链

  当代码在一个环境中执行时,会**创建变量对象的一个****作用域链**。

作用域链的用途是保证对执行环节有权访问的所有变量和函数的有序访问

作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象作为变量对象。活动对象在最开始的时候只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

——《JavaScript高级程序设计》(第3版)

但是在当前的作用域中某个变量没有定义, 就会在父级作用域查找是否存在此变量,若果不存在,就会找父级的父级作用域, 一直到全局执行环境结束。若到全局也没有找到,则报错:Uncaught ReferenceError: x is not defined.

例:下面代码,一共有三个执行环境:全局环境、changeName()的局部环境和 swapName() 的局部环境。

var name1 = "haha";
function changeName(){
    var name2="xixi";
    function swapName(){
        console.log(name1);//haha
        console.log(name2);//xixi
        var tempName=name2;
        name2=name1;
        name1=tempName;
        console.log(name1);//xixi
        console.log(name2);//haha
        console.log(tempName);//xixi
    }
    swapName();
    console.log(name1);//haha
    console.log(name2);//xixi
    //console.log(tempName);抛出错误:Uncaught ReferenceError: tempName is not defined
}
changName();
console.log(name1);
//console.log(name2); 抛出错误:Uncaught ReferenceError: name2 is not defined
//console.log(tempName);抛出错误:Uncaught ReferenceError: tempName is not defined

执行环境(execution context)

  执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为。每个执行环境都有与之对应的变量对象(variable object),保存着该环境中定义的所有变量和函数。

执行环境如其名是在运行和执行代码的时候才存在的,所以我们运行浏览器的时候会创建全局的执行环境,在调用函数时,会创建函数执行环境。

执行上下文

JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段。

解释阶段:

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

执行阶段:

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

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,

但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。

而作用域访问的变量是编写代码的结构确定的。

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

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

同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值