什么是作用域

作用域是运行代码中某些特定部分的变量,函数和对象的可访问性及生命周期

javascript中的作用域

在javascript中有两种类型的作用域
1.全局作用域
2.局部作用域
在函数内部定义的在局部作用域内,而在函数外定义的变量在全局作用域内,调用时的每个函数创建一个新的作用域

全局作用域

当你开始在文档中编写代码时,你已经在全局作用域内,整个js环境中只有一个全局作用域,如果在函数外部定义了一个变量,他就在全局作用域内。

// 全局作用域
var name = "哈士奇"

全局作用域的变量可以在任何其他作用域内访问或更改

// 全局作用域
var name = "哈士奇";

console.log(name); // 哈士奇

//局部作用域
function logName(){
    name = "泰迪";
    console.log(name);
}

logName();

局部作用域

在函数内定义的变量在局部作用域内,对于每个函数的调用,它们都有不同的作用域,这意味着具有相同名称变量可以用于不同的函数,只是因为这些变量被绑定到它们各自的函数。每个函数具有不同的作用域,并且在其他作用域中是不可访问的。

function foo(){
    var name = "哈士奇";
    console.log(name);
}

function bar(fn){
    var name = "泰迪";
    fn();
}

bar(foo);    // 哈士奇

无论函数在哪里被调用,也无论他如何被调用,他的作用域都只由函数声明时的位置决定的

查找

作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以可以定义同名的变量,这叫做遮蔽效应(内部的变量遮蔽了外部的变量)

function foo(){
    var name = "哈士奇"
    function bar(){
        var name = "泰迪"
        console.log(name)
    }
    bar(); //泰迪
    console.log(name); // 哈士奇
}

foo();
欺骗词法

javascript中的eval函数可以接受一个字符串为参数,并将其中的内容好像在书写时就存在于程序中这个位置的代码。

function foo(str, a){
    eval(str);
    console.log(a, b); //欺骗
}

var b = 2;

foo("var b = 3;", 1); //1, 3

在执行eval()之后的代码时,引擎并不知道或在意前面的代码是以动态形式插入进来,并对作用域的环境进行修改的,引擎只会如往常对词法作用域进行查找。

块作用域

像块语句ifswitch条件或whilefor循环不创建一个新的作用域,在块语句内定义的变量将保留在它们已经存在的作用域内

if(true){
    var name = "哈士奇"
}

console.log(name);    // 哈士奇

在ES6中新增的constlet关键字可以在块语句内声明局部作用域。

if(true){
    let name = "哈士奇";
}

console.log(name); // not defined

执行上下文堆栈

因为javascript是单线程语言,一次就只能执行一个函数,其他的函数会在执行上下文中排队,
即会形成执行上下文堆栈。

ec-stack.png

在javascript引擎开始执行代码时,它进入全局执行上下文,它是堆栈的底部和第一个元素。然后,全局代码提供一些初始化,创建所需的对象和函数。在执行全局上下文期间,其代码可能激活一些其他(已创建的)函数,这些函数将进入其执行上下文,将新函数推送到堆栈上,依此类推。初始化完成后,运行时系统正在等待一些事件(例如用户的鼠标点击),这将激活一些功能,并将进入新的执行上下文。

ec-stack-changes.png

执行上下文

每个函数都会创建自己的执行上下文,但只能有一个全局上下文。执行上下文可以被简单的表示一个对象,它拥有下面的属性
execution-context.png

变量对象(Variable object)

变量对象是一个数据容器与执行上下文相关联。这是一个存储上下文中定义的变量和函数声明的特殊对象(请注意函数表达式)不包括在变量对象。

var name = "哈士奇"
function foo(){}
(function bar(){});

console.log(this.name = "哈士奇"); //true
console.log(bar); //bar is not defined;

全局上下文的变量对象将具有以下属性

variable-object.png

激活对象

当调用函数时,将创建一个称为激活对象的特殊对象,他填充了形式参数和arguments对象,然后激活对象被用作函数上下文的变量对象。

function foo(x, y){
    var z = 30;
    function bar(){}
    (function baz(){});
}

foo(10, 20);

foo函数的上下文里有这些属性,并且函数表达式bar没有被包含在变量对象中。

activation-object.png

作用域链

在执行上下文创建的时候,在变量对象之后创建作用域链,作用域链本身就包含变量对象,当函数被调用的时候,作用域链包含的是激活对象。
当被要求解析变量时,JavaScript始终从代码嵌套的最内层开始,并保持对父作用域的连接,直到找到变量或其他所需的资源。作用域链可以简单地定义为包含其自己的执行上下文的变量对象的对象,以及父作用域的所有执行上下文。

var x = 10;
 
(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    console.log(x + y + z);
  })();
})();

我们可以用__parant__这个属性来假定作用域链的连接,它指的是链中的下一个对象。
scope-chain.png


殇柒
116 声望4 粉丝

前端小白