3

执行上下文(也称执行环境)堆栈

执行上下文是javascript最重要的一个概念,执行上下文定义了变量或函数有权访问的其他数据,决定了它们各自的行为。而在javascript中有三种执行上下文: 全局执行上下文, 函数执行上下文, eval执行上下文。代码在其执行上下文中执行。在javascript中只有一个全局执行环境(根据宿主环境的不同,全局执行环境的对象也不一样)。可以有许多函数和eval执行环境的实例,每次调用一个函数或eval,都会进入对应执行环境执行代码。注意一个函数可能会产生无限上下文集合,因为每次函数调用自身都会产生一个新的执行上下文

ec-stack.png

执行上下文可以激活另一个执行上下文,例如函数调用另一个函数(或者全局执行上下文调用全局函数)等等,逻辑上就成了一个堆栈。这被称为执行上下文堆栈。
当执行流进入一个函数时,函数的上下文就会被推入一个栈中,如果在当前函数中调用另一个函数,当前函数就会暂停执行,并将执行流传递给被调用函数(被调用函数同事可能是其他函数的调用者),被调用者被推入堆栈。当被调用者的上下文结束后,将执行流交还给调用者,调用者的继续运行代码,直到结束后,栈将上下文弹出。

ec-stack-changes.png

执行上下文

每个执行上下文可以抽象成一个对象,都包含了一组属性。
execution-context.png

变量对象(variable object)

每个执行上下文都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象。但是不包含函数表达式和this(因为他不是一个变量)。

var foo = 10;
function bar(){};
(function baz(){})

console.log(baz); //eror

全局上下文的变量对象
variable-object.png

如果执行上下文是一个函数,则将其活动对象作为变量对象,除了变量和函数声明之外,他还存储形式参数和arguments对象。

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

activation-object.png

我们发现baz不在活动对象里。

作用域链(scope chain)

作用域链本质上就是根据名称查找变量(标识符名称)的一套规则。规则非常简单,在自己的变量对象里找不到变量,就上父级的变量对象查找,当抵达最外层的全局上下文中,无论找到还是没找到,查找过程都会停止。查找会在找到第一个匹配的变量时停止,被称为遮蔽效应

var x = 10;
(function foo(){
    var y = 10;
    (function bar(){
        var z = 10;
        console.log(x+y+z)![scope-chain.png][6]
    })
})

如下图:
scope-chain.png

作用域知识点

词法作用域

词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的,无论函数在哪里被调用,也无论他如何被调用,他的词法作用域只由函数被声明位置决定

欺骗词法

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

function foo(str, a){
    eval(str); //欺骗
    console.log(a, b);
}
var b = 2;
foo("var b = 3;", 1); //1, 3
函数作用域

函数作用域有两种方式

//函数声明
function foo(){
    var a = 3;
    console.log(a);
}
//函数表达式
(function foo(){
    var a = 2;
    console.log(a);
})

两者的区别在于它们的名称标识符会被绑定到何处,第一段代码中会被绑定到所在作用域中,第二段代码被绑定在函数表达式自身的函数中而不是所在作用域中。

块作用域

在javascript中没有块作用域,也就是说在{...}中声明的变量会泄露到外面作用域

if(true){
    var foo = 'dog'
}
console.log(foo); //dog

function dosomething(i){
    console.log(i);
}

for(var i = 0; i < 10; i++){
    dosomething(i);
}
console.log(i);

而ES6中新增的let可以将变量绑定到所在的任意作用域(通常是{...}内部),换句话说,let为其声明的变量隐式的劫持了所在的块作用域。

if(true){
    var foo = 'dog'
}
console.log(foo); //dog

function dosomething(i){
    console.log(i);
}

for(let i = 0; i < 10; i++){
    dosomething(i);
}
console.log(i); //error

总结

作用域其实是有执行上下文中的变量对象和作用域链共同构成的。


殇柒
116 声望4 粉丝

前端小白