作用域的分类
静态作用域
动态作用域
静态作用域
JavaScript 就是静态作用域,静态作用域也称为词法作用域。
JavaScript
let x = 10
function f(){
return x
}
function g(){
let x = 20
return f()
}
console.log(g())
// 输出10
// 解答:上述代码中,函数f返回的x是外层定义的x,也就是10,我们调用g的时候,虽然g里面也有个变量x,但是在这里我们并没有用它,用的是f里面的x。也就是说我们调用一个函数时,如果这个函数的变量没有在函数中定义,就去定义该函数的地方查找,这种查找关系在我们代码写出来的时候其实就确定了,所以叫静态作用域。这是一段很简单的代码,大家都知道输出是10,难道还能输出20?还真有输出20的,那就是动态作用域了!动态作用域
所谓动态作用域就是我们调用一个函数时,如果这个函数的变量没有在函数中定义,就去调用该函数的地方查找。因为一个函数可能会在多个地方被调用,每次调用的时候变量的值可能都不一样,所以叫动态作用域。动态作用域的变量值在运行前难以确定,复杂度更高,所以目前主流的都是静态作用域,比如JS,C,C++,Java这些都是静态作用域。
作用域链
作用域链其实是一个很简单的概念,当我们使用一个变量时,先在当前作用域查找,如果没找到就去他外层作用域查找,如果还没有,就再继续往外找,一直找到全局作用域,如果最终都没找到,就报错。比如如下代码:
JavaScript
let x = 1
function fn1() {
function fn2() {
console.log(x)
}
fn2()
}
fn1()
// 输出:1
// 答:这个查找链条就是作用域链作用域链延长
JavaScript
let x = 10
try {
return x + y
} catch (e) {
console.log(e)
}
// 上述try会报错,然后catch会捕获到这个错误,
// catch会在当前作用域的最前面增加一个e,这是当前的错误对象,这就相当于作用域链延长了。这个e会在catch执行后销毁。with语句(操作作用域链)
with语句可以操作作用域链,可以手动将某个对象添加到作用域链最前面,查找变量时,优先去这个对象查找,with块执行完后,作用域链会恢复到正常状态。
JavaScript
function f(obj, x) {
with (obj) {
console.log(x)
}
console.log(x)
}
f({ x: 1 }, 2)
// 输出:1 2
// 答:with语句可以操作作用域链,可以手动将某个对象添加到作用域链最前面,查找变量时,优先去这个对象查找,with块执行完后,作用域链会恢复到正常状态。