作用域的概念
编程领域中作用域分为静态作用域,和动态作用域,js中使用的是静态作用域
静态作用域,也叫词法作用域,代码写完后,变量的作用域就已经确定不变了
作用域是可访问变量的集合。
声明提前
在ES6之前,js 使用的是函数作用域,使用var声明的变量在声明他的函数以及该函数体中的嵌套函数体内都可以访问
var x = 5;
function a() {
var y = 10;
console.log(x, y);
function b() {
var z = 15;
console.log(x, y, z);
}
b();
}
a();
函数内所有使用var 声明的变量在函数体内可见,意味着变量在声明之前就可以使用,这个特性叫做声明提前
即函数中的所有变量及函数声明(函数表达式不会提前 : var a=function(){})都会提升至函数体的顶部
var x = 5;
function a() {
console.log(x);
var x = 10;
}
a(); //undefined
变量声明和函数声明的提升优先级:函数声明提前高于变量声明提前
var a = 1;
function a() {}
console.log(typeof a); //number
块级作用域
ES6添加了块级作用域 指变量在指定快的作用域外无法被访问,它位于一对大括号中,使用let 或者const 声明
let
let y = 1;
if (true) {
var x = 2;
let y = 2;
}
console.log(x, y); //2 1
块级作用域禁止重复声明(限制在同一个块中)
var x = 1;
let x = 2; //Uncaught SyntaxError: Identifier 'x' has already been declared
let 存在暂时性死区
其实提升是引擎在运行语句块之前,会把块中的代码放到内存中去, 下面的代码之所以会报错,是因为引擎已经提前知道下面有使用let 声明的X了,所以提升是存在的, 这种情况下报错,是es规范规定的
let x = 1;
if (true) {
console.log(x); //Cannot access 'x' before initialization
let x = 2;
}
还有一些比较常见的代码
for (var index = 0; index < 3; index++) {}
console.log(index); //3
for (let index = 0; index < 3; index++) {}
console.log(index); //index is not defined
for (let index = 0; index < 3; index++) {
setTimeout(() => {
console.log(index); //0 1 2 如果使用var 就是3 3 3
});
}
const
使用const 声明的原始类型是常量,无法更改,声明的对象类型,无法赋值为其他类型,但是他的属性可以修改 但是也可以通过Object.freeze()来防止修改属性
const obj = Object.freeze({ a: 1 });
obj.a = 2;
console.log(obj); //{ a: 1 }
作用域链
用最开始的函数就可以理解,引擎从当前的执行作用域开始查找变量,如果找不到, 就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止
简单来讲,局部作用域(如函数作用域)可以访问到全局作用域中的变量和方法,而全局作用域不能访问局部作用域的变量和方法。
作用域链延长的情形
有些语句可以在作用域链的前端临时增加一个变量对象(variable object),该变量对象在代码执行完后被移除,
有两种情况 1 try-catch catch 2 with
try-catch
catch语句会创建一个新的变量对象,他的值是被抛出的错误对象,在代码进入catch语句时,这个错误对象会被添加到作用域的顶端, 当执行完catch语句时,该对象不在可用 下面这段代码,在try语句中给e赋值一个并不存在的变量y,此时会报错,进入到catch语句中 此时会在当前作用域的顶端添加一个错误对象e
let a = 5;
try {
e = y;
} catch (e) {
console.dir(e); //ReferenceError: y is not defined
}
with
with 可将某个对象添加到作用域的顶端,然后执行with语句块中的代码,执行完后会把作用域链恢复到原来的状态
因为with块中的对象只有在运行时才能知道,所以引擎无法提前优化,所以严格模式禁止使用
function f(foo, x, y) {
with (foo) {
console.log(x, y); //1 3 因为foo中有x,所以获取foo中的x
}
console.log(x, y); //2 3
}
f({ x: 1 }, 2, 3);