执行上下文(也称执行环境)堆栈
执行上下文定义了变量或函数有权访问得其他数据,决定了它们各自得行为。而在JavaScript中有三种执行上下文: 全局执行上下文,函数执行上下文,eval执行上下文。 代码在其执行上下文中执行。在JavaScript中只有一个全局执行环境(根据宿主环境的不同,全局执行环境的对象也不一样)。可以有许多函数和eval执行环境的实例,每次调用一个函数或eval,都会进入对应执行环境代码。注意 一个函数可能会产生无限上下文集合,因为每次函数调用自身都会产生一个新的执行上下文
执行上下文可以激活另一个执行上下文,例如函数调用另一个函数(或者全局执行上下文调用全局函数)等等,逻辑上就成了一个堆栈。这被成为执行上下文堆栈。
当执行流进入一个函数时,函数的上下文就会被推入一个栈中,如果在当前函数中调用另一个函数,当前函数就会被暂停执行,并将执行流传递给被调用函数(被调用函数同时可能是其他函数的调用者),被调用者函数推入堆栈。当被调用者的上下文结束后,将执行流交还给调用者,调用者的继续运行代码,直到结束,栈将上下文弹出。
作用域链(scope chain)
作用域链本质上就是根据名称查找变量(标识符名称)的一套规则。规则很简单,在自己的变量对象里找不到变量,就上父级变量对象查找,当抵达最外层全局上下文中,无论找到还是没找到,查找过程都会停止。 查找会在找到第一个匹配的变量时停止,被称为遮蔽效应
var x = 10;
(function foo(){
var y = 10;
(function bar(){
var z = 10;
console.log(x+y+z)![scope-chain.png][6]
})
})
复制代码
词法作用域
词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的, 无论函数在哪里被调用,无论它如何被调用,它的词法作用域只由函数被声明位置决定。
欺骗词法
在JavaScript中的eval函数可以接受一个字符串为参数,并将其中的内容视为在书写时就存在于程序中这个位置的代码。
function foo(str, a){
eval(str); //欺骗
console.log(a, b);
}
var b = 2;
foo("var b = 3;", 1); //1, 3
复制代码
全局作用域
全局作用域在页面打开时被创建,页面关闭时被销毁;在全局作用域中有全局对象window,代表一个浏览器窗口,由浏览器创建,可以直接调用;全局作用域中声明的变量和函数会作为window对象的属性和方法保存.
var a = 10;
b = 20;
function an(){
console.log('an')
}
var bn = function(){
console.log('bn')
}
console.log(window)
复制代码
函数作用域
函数作用域有两种方式
//函数声明
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
复制代码
总结
作用域其实是有执行上下文中的变量对象和作用域链共同构成的。