每个上下文都有一个关联的变量对象,而这个上下文中定义的所有变量和函数都存在于这个对象中。
全局上下文是最外层的上下文,在游览器中,全局上下文就是window对象,所有var定义的全局变量和函数都会成为window对象的属性和方法。使用let和const的顶级声明不会定义在全局上下文中,但在作用域解析上效果是一样的。
var a = 1;
console.log(window.a); // 1
let b = 12;
console.log(window.b); // undefined
const c = 13;
console.log(window.c); // undefined
每个函数调用都有自己的上下文,代码执行流进入函数的时候会把函数的上下文推到一个上下文栈;执行完毕后,上下文栈回弹出该函数上下文,将控制权返还给执行的执行上下文。上下文在其所有的代码都执行完毕后会被销毁,比如关闭网页,函数执行。如果通过作用域链没找到某个标识符,则通常会报错。
var color = "red";
function test1() {
let color1 = "green";
function test2() {
let color2 = "orange";
console.log(color2, color1, color); // orange green red,可以访问test2局部上下文,test1局部上下文和全局上下文
}
test2();
console.log(color1, color); // 可以访问test1局部上下文和全局上下文
}
test1();
console.log(color); // // 可以访问全局上下文
作用域增强
某些语句能在作用域最前端临时添加一个上下文,这个上下文在代码执行后会被删除。
- try/catch语句
- with语句
对with语句来说,会向作用域链前端添加指定的对象。
对catch语句来说,则会创建一个新的变量对象,这个变量对象会包含要抛出的错误对象的声明。
function buildUrl() {
let qs = "?debug=true";
with(location){ // 将 location 对象作为上下文
let url = href + qs; // 这里的href相当于location.href,qs无法在location上下文找到,则会通过作用域链向上一层查找,然后找到了qs="?debug=true"
}
return url; // 像这里使用 let 声明的变量 url,因为被限制在块级作用域,所以在 with 块之外没有定义
}
变量提升
- 使用var的函数作用域声明
使用var声明变量时,变量会被自动添加到最接近的上下文。如果没有声明就被初始化,那么就会自动添加到全局上下文。
var声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前,这种就做“提升”。
function test() {
var a = 1; // 使用var声明,会被添加到最近的上下文
b = 2; // 没有使用var声明就被初始化会被添加到全局上下文
console.log(a, b); // 1 2
}
test();
console.log(b); // 2
// console.log(a); // 报错,a is not defined 2
/*
此处相当于
var c;
console.log(c);
c=3;
console.log(c);
*/
console.log(c); // undefined
var c = 3; // 变量提升了
console.log(c); // 3
- 使用let的块级作用域声明
作用域是块级的,由最近的一对包含花括号界定。
let和var的一个不同之处是在同一作用域内let不能声明2次,否则会抛错。
let 的行为非常适合在循环中声明迭代变量。使用 var 声明的迭代变量会泄漏到循环外部,这种情况应该避免。
严格来说let在运行时也会被提升,但是因为“暂时性死区”的关系,不能在声明之前使用let变量。
- 使用const的常量声明
一经声明,在其生命周期的任何时候不能重新赋值新值。
也是具有块级作用域的。
但是对象来说引用的也是指向对象的指针,该引用值不可修改,但对象的键则不受限制如果想要整个对象都不能修改,可以使用Object.freeze(),这样给属性赋值时虽然不会报错,但也会静默失败。